Files
routeros-suricata-tzsp/scripts/configure-routeros-tzsp-hybrid.sh
T
2026-08-16 15:34:53 +02:00

166 lines
5.5 KiB
Bash
Executable File

#!/bin/sh
set -eu
cd "$(dirname "$0")/.."
CONFIG_FILE="${DEPLOY_CONFIG:-deploy-routeros.env}"
if [ -f "$CONFIG_FILE" ]; then
# shellcheck disable=SC1090
case "$CONFIG_FILE" in
/*) . "$CONFIG_FILE" ;;
*) . "./$CONFIG_FILE" ;;
esac
fi
: "${ROUTER_HOST:=192.168.88.1}"
: "${ROUTER_USER:=admin}"
: "${ROUTER_PORT:=22}"
: "${ROUTER_IDENTITY_FILE:=}"
: "${ROUTER_SCP_DIR:=/}"
: "${CONTAINER_IP:=172.31.255.2/30}"
: "${TZSP_PORT:=37008}"
: "${CONFIGURE_IPV4_MANGLE:=true}"
: "${CONFIGURE_L2_SNIFFER:=true}"
: "${START_L2_SNIFFER:=true}"
: "${TZSP_L2_INTERFACE:=}"
: "${TZSP_L2_MAC_PROTOCOL:=!ip}"
TZSP_L2_FILTER_INTERFACE="${TZSP_L2_INTERFACE:-all}"
: "${KEEP_REMOTE_RSC:=false}"
: "${DRY_RUN:=false}"
case "$ROUTER_PORT" in *[!0-9]*|'') echo "ROUTER_PORT must be numeric" >&2; exit 2 ;; esac
case "$TZSP_PORT" in *[!0-9]*|'') echo "TZSP_PORT must be numeric" >&2; exit 2 ;; esac
for bool_pair in \
"CONFIGURE_IPV4_MANGLE=$CONFIGURE_IPV4_MANGLE" \
"CONFIGURE_L2_SNIFFER=$CONFIGURE_L2_SNIFFER" \
"START_L2_SNIFFER=$START_L2_SNIFFER" \
"DRY_RUN=$DRY_RUN"
do
case "${bool_pair#*=}" in
true|false) ;;
*) echo "${bool_pair%%=*} must be true or false" >&2; exit 2 ;;
esac
done
check_ros_value() {
label="$1"
value="$2"
case "$value" in
*'"'*|*'\\'*|*'$'*|*';'*|*'`'*)
echo "$label contains a character not supported by this helper: $value" >&2
exit 3
;;
esac
}
check_ros_value CONTAINER_IP "$CONTAINER_IP"
check_ros_value TZSP_L2_INTERFACE "$TZSP_L2_INTERFACE"
check_ros_value TZSP_L2_MAC_PROTOCOL "$TZSP_L2_MAC_PROTOCOL"
TARGET="${ROUTER_USER}@${ROUTER_HOST}"
ssh_run() {
if [ -n "$ROUTER_IDENTITY_FILE" ]; then
ssh -i "$ROUTER_IDENTITY_FILE" -p "$ROUTER_PORT" "$TARGET" "$1"
else
ssh -p "$ROUTER_PORT" "$TARGET" "$1"
fi
}
scp_put() {
src="$1"
dst="$2"
if [ -n "$ROUTER_IDENTITY_FILE" ]; then
scp -i "$ROUTER_IDENTITY_FILE" -P "$ROUTER_PORT" "$src" "${TARGET}:$dst"
else
scp -P "$ROUTER_PORT" "$src" "${TARGET}:$dst"
fi
}
CONTAINER_IP_ONLY="${CONTAINER_IP%/*}"
ID="$(date -u +%Y%m%d%H%M%S)"
mkdir -p build
LOCAL_RSC="build/configure-tzsp-hybrid-${ID}.rsc"
REMOTE_NAME="configure-tzsp-hybrid-${ID}.rsc"
REMOTE_PATH="${ROUTER_SCP_DIR%/}/${REMOTE_NAME}"
[ "$ROUTER_SCP_DIR" = "/" ] && REMOTE_PATH="/${REMOTE_NAME}"
cat > "$LOCAL_RSC" <<RSC
# MikroSuricata hybrid TZSP capture.
# Generated by scripts/configure-routeros-tzsp-hybrid.sh at ${ID} UTC.
RSC
if [ "$CONFIGURE_IPV4_MANGLE" = "true" ]; then
cat >> "$LOCAL_RSC" <<RSC
# Routed IPv4: high-throughput path. Only the MikroSuricata-owned rule is replaced.
/ip/firewall/mangle/remove [find where comment="MikroSuricata TZSP IPv4"]
:if ([:len [/ip/firewall/mangle/find]] > 0) do={
/ip/firewall/mangle/add chain=forward action=sniff-tzsp sniff-target="${CONTAINER_IP_ONLY}" sniff-target-port=${TZSP_PORT} comment="MikroSuricata TZSP IPv4" place-before=0
} else={
/ip/firewall/mangle/add chain=forward action=sniff-tzsp sniff-target="${CONTAINER_IP_ONLY}" sniff-target-port=${TZSP_PORT} comment="MikroSuricata TZSP IPv4"
}
RSC
fi
if [ "$CONFIGURE_L2_SNIFFER" = "true" ]; then
cat >> "$LOCAL_RSC" <<RSC
# Non-IPv4 Ethernet: low-volume Packet Sniffer complement.
# Empty TZSP_L2_INTERFACE intentionally means all interfaces; no bridge name is assumed.
/tool/sniffer/stop
:if ("${TZSP_L2_INTERFACE}" != "") do={
:if ([:len [/interface/find where name="${TZSP_L2_INTERFACE}"]] = 0) do={
:error "TZSP_L2_INTERFACE not found: ${TZSP_L2_INTERFACE}"
}
}
/tool/sniffer/set only-headers=no max-packet-size=2048 streaming-enabled=yes filter-stream=yes filter-interface="${TZSP_L2_FILTER_INTERFACE}" filter-mac-address="" filter-src-mac-address="" filter-dst-mac-address="" filter-mac-protocol=${TZSP_L2_MAC_PROTOCOL} filter-ip-address="" filter-src-ip-address="" filter-dst-ip-address="" filter-ipv6-address="" filter-src-ipv6-address="" filter-dst-ipv6-address="" filter-ip-protocol="" filter-port="" filter-src-port="" filter-dst-port="" filter-vlan="" filter-cpu="" filter-size="" filter-direction=any filter-operator-between-entries=or
:do {
/tool/sniffer/set streaming-server=${CONTAINER_IP_ONLY}:${TZSP_PORT}
} on-error={
/tool/sniffer/set streaming-server=${CONTAINER_IP_ONLY} streaming-port=${TZSP_PORT}
}
RSC
if [ "$START_L2_SNIFFER" = "true" ]; then
cat >> "$LOCAL_RSC" <<'RSC'
/tool/sniffer/start
RSC
fi
fi
cat >> "$LOCAL_RSC" <<'RSC'
/ip/firewall/mangle/print stats where comment="MikroSuricata TZSP IPv4"
/tool/sniffer/print
RSC
if [ "$DRY_RUN" = "true" ]; then
cat "$LOCAL_RSC"
echo "[capture] dry run only; RouterOS was not modified"
exit 0
fi
command -v ssh >/dev/null 2>&1 || { echo "ssh is required" >&2; exit 2; }
command -v scp >/dev/null 2>&1 || { echo "scp is required" >&2; exit 2; }
echo "[capture] preflight ${TARGET}"
ssh_run '/ip/firewall/mangle/print' >/dev/null
if [ "$CONFIGURE_L2_SNIFFER" = "true" ]; then
ssh_run '/tool/sniffer/print' >/dev/null
fi
echo "[capture] uploading $REMOTE_PATH"
scp_put "$LOCAL_RSC" "$REMOTE_PATH"
echo "[capture] applying hybrid capture"
ssh_run "/import file-name=\"${REMOTE_NAME}\""
if [ "$KEEP_REMOTE_RSC" != "true" ]; then
ssh_run "/file/remove [find where name=\"${REMOTE_NAME}\"]" || true
fi
echo "[capture] done"
echo "[capture] IPv4: mangle sniff-tzsp -> ${CONTAINER_IP_ONLY}:${TZSP_PORT}"
if [ "$CONFIGURE_L2_SNIFFER" = "true" ]; then
if [ -n "$TZSP_L2_INTERFACE" ]; then
echo "[capture] non-IPv4: Packet Sniffer on ${TZSP_L2_INTERFACE} -> ${CONTAINER_IP_ONLY}:${TZSP_PORT}"
else
echo "[capture] non-IPv4: Packet Sniffer on all interfaces -> ${CONTAINER_IP_ONLY}:${TZSP_PORT}"
fi
fi