Files
routeros-suricata-tzsp/scripts/deploy-routeros.sh
T
2026-08-17 10:07:19 +02:00

534 lines
22 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
VERSION="$(tr -d '[:space:]' < VERSION)"
[ -n "$VERSION" ] || { echo "VERSION is empty" >&2; exit 2; }
case "$VERSION" in
*[!A-Za-z0-9._-]*) echo "VERSION contains unsupported characters: $VERSION" >&2; exit 2 ;;
esac
CONTAINER_NAME="suricata_${VERSION}"
ROOT_DIR="/containers/${CONTAINER_NAME}/root"
# Backward-compatible aliases for pre-0.11.2 deploy-routeros.env files.
if [ "${CONFIGURE_TZSP_CAPTURE+x}" != x ] && [ "${CONFIGURE_SNIFFER+x}" = x ]; then
CONFIGURE_TZSP_CAPTURE="$CONFIGURE_SNIFFER"
fi
if [ "${START_L2_SNIFFER+x}" != x ] && [ "${START_SNIFFER+x}" = x ]; then
START_L2_SNIFFER="$START_SNIFFER"
fi
: "${ROUTER_HOST:=192.168.88.1}"
: "${ROUTER_USER:=admin}"
: "${ROUTER_PORT:=22}"
: "${ROUTER_IDENTITY_FILE:=}"
: "${ROUTER_SCP_DIR:=/}"
: "${ROUTER_DISK:=disk1}"
: "${CONTAINER_IP:=172.31.255.2/30}"
: "${CONTAINER_GATEWAY:=172.31.255.1}"
: "${CONTAINER_SUBNET:=172.31.255.0/30}"
: "${CONTAINER_BRIDGE:=br-ids}"
: "${CONTAINER_VETH:=veth-ids}"
: "${TZSP_PORT:=37008}"
: "${CONFIGURE_TZSP_CAPTURE:=true}"
: "${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}"
: "${TZSP_RCVBUF_BYTES:=33554432}"
: "${TZSP_BATCH_SIZE:=256}"
: "${TZSP_QUEUE_MB:=64}"
: "${TZSP_DATAGRAM_BYTES:=12288}"
: "${SURICATA_HOME_NET:=[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]}"
: "${MONITORED_NETWORKS:=192.168.0.0/16,10.0.0.0/8,172.16.0.0/12}"
: "${SURICATA_LOG_MAX_MB:=512}"
: "${FORENSIC_PCAP_MODE:=alerts}"
: "${FORENSIC_PCAP_WINDOW_SECONDS:=60}"
: "${FORENSIC_PCAP_MEMORY_MB:=64}"
: "${FORENSIC_PCAP_MAX_FILES:=32}"
: "${FORENSIC_PCAP_MAX_TOTAL_MB:=512}"
: "${AUTO_BLOCK:=false}"
: "${AUTO_BLOCK_MAX_SEVERITY:=1}"
: "${BLOCK_TIMEOUT:=1h}"
: "${UPDATE_RULES_ON_START:=false}"
: "${SURICATA_PERSIST_LIB_DIR:=/data/lib/suricata}"
: "${RULE_UPDATE_INTERVAL_HOURS:=24}"
: "${ALERT_RETENTION_DAYS:=14}"
: "${REDIS_MANAGED:=true}"
: "${REDIS_DATA_DIR:=/data/redis}"
: "${REDIS_PORT:=6379}"
: "${REDIS_MAXMEMORY_MB:=128}"
: "${REDIS_SNAPSHOT_SECONDS:=0}"
: "${REDIS_AOF:=false}"
: "${TRAFFIC_RETENTION_HOURS:=24}"
: "${TRAFFIC_ARCHIVE_INTERVAL_SECONDS:=10}"
: "${TRAFFIC_ARCHIVE_LAG_SECONDS:=10}"
: "${TRAFFIC_ARCHIVE_BATCH_SIZE:=1000}"
: "${TRAFFIC_MAX_EVENTS:=0}"
: "${TRAFFIC_MEMORY_EVENTS:=0}"
: "${WEBSOCKET_QUEUE_SIZE:=512}"
: "${LIVE_FLOW_UPDATE_SECONDS:=2.0}"
: "${ALERT_MAX_SEVERITY:=2}"
: "${ALERT_DEDUP_WINDOW_SECONDS:=300}"
: "${ALERT_IGNORE_SIDS:=1000001}"
: "${ALERT_IGNORE_CATEGORIES:=}"
: "${ADMIN_USERNAME:=admin}"
: "${ADMIN_PASSWORD:=}"
: "${METRICS_ALLOWED_IPS:=127.0.0.1/32,::1/128}"
: "${METRICS_BASIC_AUTH_USERNAME:=}"
: "${METRICS_BASIC_AUTH_PASSWORD:=}"
: "${SESSION_HOURS:=168}"
: "${SESSION_COOKIE_SECURE:=false}"
: "${ANALYTICS_SNAPSHOT_INTERVAL_SECONDS:=120}"
: "${NDR_ENABLED:=true}"
: "${NDR_CORRELATION_WINDOW_SECONDS:=1800}"
: "${BEHAVIOR_MIN_OBSERVATIONS:=50}"
: "${NDR_AUTO_BLOCK:=false}"
: "${NDR_AUTO_BLOCK_RISK:=92}"
: "${ROUTEROS_INVENTORY_INTERVAL_SECONDS:=300}"
: "${NOTIFY_WEBHOOK_URL:=}"
: "${NOTIFY_MIN_RISK:=80}"
: "${NOTIFY_TIMEOUT_SECONDS:=5}"
: "${CREATE_REST_USER:=false}"
: "${ENABLE_WWW_SSL:=false}"
: "${ROUTEROS_REST_USER:=suricata-api}"
: "${ROUTEROS_REST_PASSWORD:=CHANGE_ME}"
: "${ROUTEROS_VERIFY_TLS:=false}"
: "${ROUTEROS_ADDRESS_LIST:=IDS-BLOCK}"
: "${KEEP_REMOTE_RSC:=false}"
usage() {
cat <<USAGE
Usage: $0 <RouterOS image TAR path>
The TAR must already exist on RouterOS. Example:
./scripts/upload-routeros-image.sh build/routeros-suricata-tzsp-arm64.tar
$0 routeros-suricata-tzsp-arm64.tar
Container name: ${CONTAINER_NAME}
Root dir: ${ROOT_DIR}
USAGE
}
IMAGE_TAR_ROS="${1:-}"
if [ -z "$IMAGE_TAR_ROS" ]; then
usage >&2
exit 2
fi
case "$IMAGE_TAR_ROS" in
*.tar) ;;
*) echo "RouterOS image path must point to a .tar file: $IMAGE_TAR_ROS" >&2; exit 2 ;;
esac
need() {
command -v "$1" >/dev/null 2>&1 || {
echo "$1 is required" >&2
exit 2
}
}
need ssh
need scp
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
case "$FORENSIC_PCAP_MODE" in
blocks|alerts|all|off) ;;
*) echo "FORENSIC_PCAP_MODE must be one of: blocks, alerts, all, off" >&2; exit 2 ;;
esac
for bool_pair in \
"CONFIGURE_TZSP_CAPTURE=$CONFIGURE_TZSP_CAPTURE" \
"CONFIGURE_IPV4_MANGLE=$CONFIGURE_IPV4_MANGLE" \
"CONFIGURE_L2_SNIFFER=$CONFIGURE_L2_SNIFFER" \
"START_L2_SNIFFER=$START_L2_SNIFFER"
do
case "${bool_pair#*=}" in
true|false) ;;
*) echo "${bool_pair%%=*} must be true or false" >&2; exit 2 ;;
esac
done
if { [ -n "$METRICS_BASIC_AUTH_USERNAME" ] && [ -z "$METRICS_BASIC_AUTH_PASSWORD" ]; } || \
{ [ -z "$METRICS_BASIC_AUTH_USERNAME" ] && [ -n "$METRICS_BASIC_AUTH_PASSWORD" ]; }; then
echo "METRICS_BASIC_AUTH_USERNAME and METRICS_BASIC_AUTH_PASSWORD must both be set or both be empty" >&2
exit 2
fi
for numeric_pair in \
"REDIS_PORT=$REDIS_PORT" \
"TZSP_RCVBUF_BYTES=$TZSP_RCVBUF_BYTES" \
"TZSP_BATCH_SIZE=$TZSP_BATCH_SIZE" \
"TZSP_QUEUE_MB=$TZSP_QUEUE_MB" \
"TZSP_DATAGRAM_BYTES=$TZSP_DATAGRAM_BYTES" \
"SURICATA_LOG_MAX_MB=$SURICATA_LOG_MAX_MB" \
"FORENSIC_PCAP_WINDOW_SECONDS=$FORENSIC_PCAP_WINDOW_SECONDS" \
"FORENSIC_PCAP_MEMORY_MB=$FORENSIC_PCAP_MEMORY_MB" \
"FORENSIC_PCAP_MAX_FILES=$FORENSIC_PCAP_MAX_FILES" \
"FORENSIC_PCAP_MAX_TOTAL_MB=$FORENSIC_PCAP_MAX_TOTAL_MB" \
"REDIS_MAXMEMORY_MB=$REDIS_MAXMEMORY_MB" \
"REDIS_SNAPSHOT_SECONDS=$REDIS_SNAPSHOT_SECONDS" \
"TRAFFIC_RETENTION_HOURS=$TRAFFIC_RETENTION_HOURS" \
"TRAFFIC_ARCHIVE_INTERVAL_SECONDS=$TRAFFIC_ARCHIVE_INTERVAL_SECONDS" \
"TRAFFIC_ARCHIVE_LAG_SECONDS=$TRAFFIC_ARCHIVE_LAG_SECONDS" \
"TRAFFIC_ARCHIVE_BATCH_SIZE=$TRAFFIC_ARCHIVE_BATCH_SIZE" \
"TRAFFIC_MAX_EVENTS=$TRAFFIC_MAX_EVENTS" \
"TRAFFIC_MEMORY_EVENTS=$TRAFFIC_MEMORY_EVENTS" \
"WEBSOCKET_QUEUE_SIZE=$WEBSOCKET_QUEUE_SIZE" \
"SESSION_HOURS=$SESSION_HOURS" \
"ANALYTICS_SNAPSHOT_INTERVAL_SECONDS=$ANALYTICS_SNAPSHOT_INTERVAL_SECONDS" \
"NDR_CORRELATION_WINDOW_SECONDS=$NDR_CORRELATION_WINDOW_SECONDS" \
"BEHAVIOR_MIN_OBSERVATIONS=$BEHAVIOR_MIN_OBSERVATIONS" \
"NDR_AUTO_BLOCK_RISK=$NDR_AUTO_BLOCK_RISK" \
"ROUTEROS_INVENTORY_INTERVAL_SECONDS=$ROUTEROS_INVENTORY_INTERVAL_SECONDS" \
"NOTIFY_MIN_RISK=$NOTIFY_MIN_RISK" \
"NOTIFY_TIMEOUT_SECONDS=$NOTIFY_TIMEOUT_SECONDS"
do
case "${numeric_pair#*=}" in
*[!0-9]*|'') echo "${numeric_pair%%=*} must be numeric" >&2; exit 2 ;;
esac
done
check_ros_value() {
label="$1"
value="$2"
case "$value" in
*'"'*|*'\\'*|*'$'*|*';'*|*'`'*)
echo "$label contains a character not supported by the deploy script: $value" >&2
exit 3
;;
esac
case "$value" in
*"\n"*|*"\r"*)
echo "$label contains a newline" >&2
exit 3
;;
esac
}
for pair in \
"IMAGE_TAR_ROS=$IMAGE_TAR_ROS" \
"ROUTER_DISK=$ROUTER_DISK" \
"CONTAINER_IP=$CONTAINER_IP" \
"CONTAINER_GATEWAY=$CONTAINER_GATEWAY" \
"CONTAINER_SUBNET=$CONTAINER_SUBNET" \
"CONTAINER_BRIDGE=$CONTAINER_BRIDGE" \
"CONTAINER_VETH=$CONTAINER_VETH" \
"TZSP_L2_INTERFACE=$TZSP_L2_INTERFACE" \
"TZSP_L2_MAC_PROTOCOL=$TZSP_L2_MAC_PROTOCOL" \
"SURICATA_HOME_NET=$SURICATA_HOME_NET" \
"MONITORED_NETWORKS=$MONITORED_NETWORKS" \
"BLOCK_TIMEOUT=$BLOCK_TIMEOUT" \
"ALERT_IGNORE_SIDS=$ALERT_IGNORE_SIDS" \
"ALERT_IGNORE_CATEGORIES=$ALERT_IGNORE_CATEGORIES" \
"ADMIN_USERNAME=$ADMIN_USERNAME" \
"ADMIN_PASSWORD=$ADMIN_PASSWORD" \
"METRICS_ALLOWED_IPS=$METRICS_ALLOWED_IPS" \
"METRICS_BASIC_AUTH_USERNAME=$METRICS_BASIC_AUTH_USERNAME" \
"METRICS_BASIC_AUTH_PASSWORD=$METRICS_BASIC_AUTH_PASSWORD" \
"SESSION_COOKIE_SECURE=$SESSION_COOKIE_SECURE" \
"REDIS_DATA_DIR=$REDIS_DATA_DIR" \
"SURICATA_PERSIST_LIB_DIR=$SURICATA_PERSIST_LIB_DIR" \
"NOTIFY_WEBHOOK_URL=$NOTIFY_WEBHOOK_URL" \
"ROUTEROS_REST_USER=$ROUTEROS_REST_USER" \
"ROUTEROS_REST_PASSWORD=$ROUTEROS_REST_PASSWORD" \
"ROUTEROS_ADDRESS_LIST=$ROUTEROS_ADDRESS_LIST" \
"LIVE_FLOW_UPDATE_SECONDS=$LIVE_FLOW_UPDATE_SECONDS"
do
check_ros_value "${pair%%=*}" "${pair#*=}"
done
SSH_TARGET="${ROUTER_USER}@${ROUTER_HOST}"
ssh_run() {
if [ -n "$ROUTER_IDENTITY_FILE" ]; then
ssh -i "$ROUTER_IDENTITY_FILE" -p "$ROUTER_PORT" "$SSH_TARGET" "$1"
else
ssh -p "$ROUTER_PORT" "$SSH_TARGET" "$1"
fi
}
scp_put() {
src="$1"
dst="$2"
if [ -n "$ROUTER_IDENTITY_FILE" ]; then
scp -i "$ROUTER_IDENTITY_FILE" -P "$ROUTER_PORT" "$src" "${SSH_TARGET}:$dst"
else
scp -P "$ROUTER_PORT" "$src" "${SSH_TARGET}:$dst"
fi
}
printf '[deploy] version: %s\n' "$VERSION"
printf '[deploy] container: %s\n' "$CONTAINER_NAME"
printf '[deploy] image on RouterOS: %s\n' "$IMAGE_TAR_ROS"
printf '[deploy] root-dir: %s\n' "$ROOT_DIR"
echo "[deploy] RouterOS preflight"
if ! ssh_run '/container/print' >/dev/null; then
echo "RouterOS container menu is unavailable." >&2
exit 4
fi
if ! ssh_run "/file/print without-paging where name=\"${IMAGE_TAR_ROS}\"" | grep -F "$IMAGE_TAR_ROS" >/dev/null 2>&1; then
echo "Image TAR not found on RouterOS: $IMAGE_TAR_ROS" >&2
echo "Upload it first with scripts/upload-routeros-image.sh." >&2
exit 5
fi
if [ "$CONFIGURE_TZSP_CAPTURE" = "true" ]; then
if [ "$CONFIGURE_IPV4_MANGLE" = "true" ]; then
if ! ssh_run '/ip/firewall/mangle/print' >/dev/null; then
echo "RouterOS IPv4 mangle is unavailable." >&2
exit 4
fi
fi
if [ "$CONFIGURE_L2_SNIFFER" = "true" ]; then
if ! ssh_run '/tool/sniffer/print' >/dev/null; then
echo "RouterOS sniffer is unavailable." >&2
exit 4
fi
fi
fi
if [ "$CREATE_REST_USER" = "true" ] && [ "$ROUTEROS_REST_PASSWORD" = "CHANGE_ME" ]; then
echo "CREATE_REST_USER=true requires a real ROUTEROS_REST_PASSWORD" >&2
exit 6
fi
DEPLOY_ID="$(date -u +%Y%m%d%H%M%S)"
mkdir -p build
LOCAL_RSC="build/deploy-${CONTAINER_NAME}-${DEPLOY_ID}.rsc"
REMOTE_RSC_NAME="deploy-${CONTAINER_NAME}-${DEPLOY_ID}.rsc"
REMOTE_RSC_SCP="${ROUTER_SCP_DIR%/}/${REMOTE_RSC_NAME}"
[ "$ROUTER_SCP_DIR" = "/" ] && REMOTE_RSC_SCP="/${REMOTE_RSC_NAME}"
# Keep persistent application state stable between versioned containers.
DATA_DIR="${ROUTER_DISK}/containers/suricata-data"
REST_URL="https://${CONTAINER_GATEWAY}"
CONTAINER_IP_ONLY="${CONTAINER_IP%/*}"
cat > "$LOCAL_RSC" <<RSC
# Generated by scripts/deploy-routeros.sh at ${DEPLOY_ID} UTC.
# Version: ${VERSION}
# Container: ${CONTAINER_NAME}
# Image: ${IMAGE_TAR_ROS}
# Root: ${ROOT_DIR}
:if ([:len [/interface/bridge/find where name="${CONTAINER_BRIDGE}"]] = 0) do={
/interface/bridge/add name="${CONTAINER_BRIDGE}" comment="Suricata IDS container bridge"
}
:if ([:len [/ip/address/find where interface="${CONTAINER_BRIDGE}" and address="${CONTAINER_GATEWAY}/30"]] = 0) do={
/ip/address/add address="${CONTAINER_GATEWAY}/30" interface="${CONTAINER_BRIDGE}" comment="Suricata IDS gateway"
}
:if ([:len [/interface/veth/find where name="${CONTAINER_VETH}"]] = 0) do={
/interface/veth/add name="${CONTAINER_VETH}" address="${CONTAINER_IP}" gateway="${CONTAINER_GATEWAY}" comment="Suricata IDS container"
}
:if ([:len [/interface/bridge/port/find where bridge="${CONTAINER_BRIDGE}" and interface="${CONTAINER_VETH}"]] = 0) do={
/interface/bridge/port/add bridge="${CONTAINER_BRIDGE}" interface="${CONTAINER_VETH}"
}
:if ([:len [/ip/firewall/nat/find where comment="Suricata IDS outbound NAT"]] = 0) do={
/ip/firewall/nat/add chain=srcnat src-address="${CONTAINER_SUBNET}" action=masquerade comment="Suricata IDS outbound NAT"
}
/container/envs/remove [find where list="IDS_ENV"]
/container/envs/add list=IDS_ENV key=TZSP_BIND value="0.0.0.0"
/container/envs/add list=IDS_ENV key=TZSP_PORT value="${TZSP_PORT}"
/container/envs/add list=IDS_ENV key=TZSP_RCVBUF_BYTES value="${TZSP_RCVBUF_BYTES}"
/container/envs/add list=IDS_ENV key=TZSP_BATCH_SIZE value="${TZSP_BATCH_SIZE}"
/container/envs/add list=IDS_ENV key=TZSP_QUEUE_MB value="${TZSP_QUEUE_MB}"
/container/envs/add list=IDS_ENV key=TZSP_DATAGRAM_BYTES value="${TZSP_DATAGRAM_BYTES}"
/container/envs/add list=IDS_ENV key=TAP_NAME value="suritap0"
/container/envs/add list=IDS_ENV key=TAP_MTU value="9000"
/container/envs/add list=IDS_ENV key=SURICATA_HOME_NET value="${SURICATA_HOME_NET}"
/container/envs/add list=IDS_ENV key=SURICATA_LOG_MAX_MB value="${SURICATA_LOG_MAX_MB}"
/container/envs/add list=IDS_ENV key=FORENSIC_PCAP_MODE value="${FORENSIC_PCAP_MODE}"
/container/envs/add list=IDS_ENV key=FORENSIC_PCAP_WINDOW_SECONDS value="${FORENSIC_PCAP_WINDOW_SECONDS}"
/container/envs/add list=IDS_ENV key=FORENSIC_PCAP_MEMORY_MB value="${FORENSIC_PCAP_MEMORY_MB}"
/container/envs/add list=IDS_ENV key=FORENSIC_PCAP_MAX_FILES value="${FORENSIC_PCAP_MAX_FILES}"
/container/envs/add list=IDS_ENV key=FORENSIC_PCAP_MAX_TOTAL_MB value="${FORENSIC_PCAP_MAX_TOTAL_MB}"
/container/envs/add list=IDS_ENV key=MONITORED_NETWORKS value="${MONITORED_NETWORKS}"
/container/envs/add list=IDS_ENV key=AUTO_BLOCK value="${AUTO_BLOCK}"
/container/envs/add list=IDS_ENV key=AUTO_BLOCK_MAX_SEVERITY value="${AUTO_BLOCK_MAX_SEVERITY}"
/container/envs/add list=IDS_ENV key=BLOCK_TIMEOUT value="${BLOCK_TIMEOUT}"
/container/envs/add list=IDS_ENV key=ROUTEROS_URL value="${REST_URL}"
/container/envs/add list=IDS_ENV key=ROUTEROS_USER value="${ROUTEROS_REST_USER}"
/container/envs/add list=IDS_ENV key=ROUTEROS_PASSWORD value="${ROUTEROS_REST_PASSWORD}"
/container/envs/add list=IDS_ENV key=ROUTEROS_VERIFY_TLS value="${ROUTEROS_VERIFY_TLS}"
/container/envs/add list=IDS_ENV key=ROUTEROS_ADDRESS_LIST value="${ROUTEROS_ADDRESS_LIST}"
/container/envs/add list=IDS_ENV key=UPDATE_RULES_ON_START value="${UPDATE_RULES_ON_START}"
/container/envs/add list=IDS_ENV key=SURICATA_PERSIST_LIB_DIR value="${SURICATA_PERSIST_LIB_DIR}"
/container/envs/add list=IDS_ENV key=RULE_UPDATE_INTERVAL_HOURS value="${RULE_UPDATE_INTERVAL_HOURS}"
/container/envs/add list=IDS_ENV key=ALERT_RETENTION_DAYS value="${ALERT_RETENTION_DAYS}"
/container/envs/add list=IDS_ENV key=REDIS_URL value="redis://127.0.0.1:${REDIS_PORT}/0"
/container/envs/add list=IDS_ENV key=REDIS_MANAGED value="${REDIS_MANAGED}"
/container/envs/add list=IDS_ENV key=REDIS_DATA_DIR value="${REDIS_DATA_DIR}"
/container/envs/add list=IDS_ENV key=REDIS_PORT value="${REDIS_PORT}"
/container/envs/add list=IDS_ENV key=REDIS_MAXMEMORY_MB value="${REDIS_MAXMEMORY_MB}"
/container/envs/add list=IDS_ENV key=REDIS_SNAPSHOT_SECONDS value="${REDIS_SNAPSHOT_SECONDS}"
/container/envs/add list=IDS_ENV key=REDIS_AOF value="${REDIS_AOF}"
/container/envs/add list=IDS_ENV key=TRAFFIC_RETENTION_HOURS value="${TRAFFIC_RETENTION_HOURS}"
/container/envs/add list=IDS_ENV key=TRAFFIC_ARCHIVE_INTERVAL_SECONDS value="${TRAFFIC_ARCHIVE_INTERVAL_SECONDS}"
/container/envs/add list=IDS_ENV key=TRAFFIC_ARCHIVE_LAG_SECONDS value="${TRAFFIC_ARCHIVE_LAG_SECONDS}"
/container/envs/add list=IDS_ENV key=TRAFFIC_ARCHIVE_BATCH_SIZE value="${TRAFFIC_ARCHIVE_BATCH_SIZE}"
/container/envs/add list=IDS_ENV key=TRAFFIC_MAX_EVENTS value="${TRAFFIC_MAX_EVENTS}"
/container/envs/add list=IDS_ENV key=TRAFFIC_MEMORY_EVENTS value="${TRAFFIC_MEMORY_EVENTS}"
/container/envs/add list=IDS_ENV key=WEBSOCKET_QUEUE_SIZE value="${WEBSOCKET_QUEUE_SIZE}"
/container/envs/add list=IDS_ENV key=LIVE_FLOW_UPDATE_SECONDS value="${LIVE_FLOW_UPDATE_SECONDS}"
/container/envs/add list=IDS_ENV key=ALERT_MAX_SEVERITY value="${ALERT_MAX_SEVERITY}"
/container/envs/add list=IDS_ENV key=ALERT_DEDUP_WINDOW_SECONDS value="${ALERT_DEDUP_WINDOW_SECONDS}"
/container/envs/add list=IDS_ENV key=ALERT_IGNORE_SIDS value="${ALERT_IGNORE_SIDS}"
/container/envs/add list=IDS_ENV key=ALERT_IGNORE_CATEGORIES value="${ALERT_IGNORE_CATEGORIES}"
/container/envs/add list=IDS_ENV key=ADMIN_USERNAME value="${ADMIN_USERNAME}"
/container/envs/add list=IDS_ENV key=ADMIN_PASSWORD value="${ADMIN_PASSWORD}"
/container/envs/add list=IDS_ENV key=METRICS_ALLOWED_IPS value="${METRICS_ALLOWED_IPS}"
/container/envs/add list=IDS_ENV key=METRICS_BASIC_AUTH_USERNAME value="${METRICS_BASIC_AUTH_USERNAME}"
/container/envs/add list=IDS_ENV key=METRICS_BASIC_AUTH_PASSWORD value="${METRICS_BASIC_AUTH_PASSWORD}"
/container/envs/add list=IDS_ENV key=SESSION_HOURS value="${SESSION_HOURS}"
/container/envs/add list=IDS_ENV key=SESSION_COOKIE_SECURE value="${SESSION_COOKIE_SECURE}"
/container/envs/add list=IDS_ENV key=ANALYTICS_SNAPSHOT_INTERVAL_SECONDS value="${ANALYTICS_SNAPSHOT_INTERVAL_SECONDS}"
/container/envs/add list=IDS_ENV key=NDR_ENABLED value="${NDR_ENABLED}"
/container/envs/add list=IDS_ENV key=NDR_CORRELATION_WINDOW_SECONDS value="${NDR_CORRELATION_WINDOW_SECONDS}"
/container/envs/add list=IDS_ENV key=BEHAVIOR_MIN_OBSERVATIONS value="${BEHAVIOR_MIN_OBSERVATIONS}"
/container/envs/add list=IDS_ENV key=NDR_AUTO_BLOCK value="${NDR_AUTO_BLOCK}"
/container/envs/add list=IDS_ENV key=NDR_AUTO_BLOCK_RISK value="${NDR_AUTO_BLOCK_RISK}"
/container/envs/add list=IDS_ENV key=ROUTEROS_INVENTORY_INTERVAL_SECONDS value="${ROUTEROS_INVENTORY_INTERVAL_SECONDS}"
/container/envs/add list=IDS_ENV key=NOTIFY_WEBHOOK_URL value="${NOTIFY_WEBHOOK_URL}"
/container/envs/add list=IDS_ENV key=NOTIFY_MIN_RISK value="${NOTIFY_MIN_RISK}"
/container/envs/add list=IDS_ENV key=NOTIFY_TIMEOUT_SECONDS value="${NOTIFY_TIMEOUT_SECONDS}"
/container/mounts/remove [find where list="IDS_MOUNTS"]
/container/mounts/add list=IDS_MOUNTS src="${DATA_DIR}" dst=/data
RSC
if [ "$CREATE_REST_USER" = "true" ]; then
cat >> "$LOCAL_RSC" <<RSC
:if ([:len [/user/group/find where name="ids-rest"]] = 0) do={
/user/group/add name=ids-rest policy=read,write,rest-api comment="Suricata IDS REST group"
}
:if ([:len [/user/find where name="${ROUTEROS_REST_USER}"]] = 0) do={
/user/add name="${ROUTEROS_REST_USER}" group=ids-rest address="${CONTAINER_IP_ONLY}/32" password="${ROUTEROS_REST_PASSWORD}"
} else={
/user/set [find where name="${ROUTEROS_REST_USER}"] group=ids-rest address="${CONTAINER_IP_ONLY}/32" password="${ROUTEROS_REST_PASSWORD}"
}
RSC
fi
if [ "$ENABLE_WWW_SSL" = "true" ]; then
cat >> "$LOCAL_RSC" <<'RSC'
/ip/service/enable www-ssl
RSC
fi
cat >> "$LOCAL_RSC" <<RSC
:if ([:len [/ip/firewall/filter/find where comment="IDS-BLOCK source"]] = 0) do={
/ip/firewall/filter/add chain=forward action=drop src-address-list="${ROUTEROS_ADDRESS_LIST}" disabled=yes comment="IDS-BLOCK source"
}
:if ([:len [/ip/firewall/filter/find where comment="IDS-BLOCK destination"]] = 0) do={
/ip/firewall/filter/add chain=forward action=drop dst-address-list="${ROUTEROS_ADDRESS_LIST}" disabled=yes comment="IDS-BLOCK destination"
}
:if ([:len [/container/find where name="${CONTAINER_NAME}"]] > 0) do={
:error "Container ${CONTAINER_NAME} already exists"
}
/container/add name="${CONTAINER_NAME}" file="${IMAGE_TAR_ROS}" interface="${CONTAINER_VETH}" root-dir="${ROOT_DIR}" mountlists=IDS_MOUNTS envlist=IDS_ENV start-on-boot=yes logging=yes
# Wait until the newly added image has finished extracting, using find filters only.
:local tries 0
:while (\$tries < 180) do={
:if ([:len [/container/find where name="${CONTAINER_NAME}" and status="stopped"]] > 0) do={
:set tries 999
} else={
:delay 2s
:set tries (\$tries + 1)
}
}
:if ([:len [/container/find where name="${CONTAINER_NAME}" and status="stopped"]] = 0) do={
:error "Container extraction did not reach stopped state"
}
/container/start [find where name="${CONTAINER_NAME}"]
:delay 5s
RSC
if [ "$CONFIGURE_TZSP_CAPTURE" = "true" ]; then
if [ "$CONFIGURE_IPV4_MANGLE" = "true" ]; then
cat >> "$LOCAL_RSC" <<RSC
# High-throughput routed IPv4 capture. sniff-tzsp continues to the next mangle
# rule after cloning the packet, so existing mangle processing remains active.
/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
# Low-volume complement for everything that is not IPv4 EtherType 0x0800.
# No bridge/interface name is assumed. Empty TZSP_L2_INTERFACE maps to RouterOS "all".
/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
# RouterOS current syntax uses streaming-server=IP:PORT. Older releases used a
# separate streaming-port property, so retain a fallback without assuming either.
: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
fi
cat >> "$LOCAL_RSC" <<RSC
:log info "Suricata deployment ${VERSION}: ${CONTAINER_NAME} created from ${IMAGE_TAR_ROS}"
/container/print detail where name="${CONTAINER_NAME}"
RSC
printf '[deploy] uploading RouterOS deployment script: %s\n' "$REMOTE_RSC_SCP"
scp_put "$LOCAL_RSC" "$REMOTE_RSC_SCP"
echo "[deploy] importing configuration and creating ${CONTAINER_NAME}"
ssh_run "/import file-name=\"${REMOTE_RSC_NAME}\""
if [ "$KEEP_REMOTE_RSC" != "true" ]; then
# The generated RSC may contain the REST password.
ssh_run "/file/remove [find where name=\"${REMOTE_RSC_NAME}\"]" || true
fi
echo "[deploy] final status"
ssh_run "/container/print detail where name=\"${CONTAINER_NAME}\""
echo "[deploy] recent container log lines"
ssh_run "/log/print without-paging where message~\"suricata|TZSP|IDS\"" || true
echo "[deploy] done"
echo "Container: ${CONTAINER_NAME}"
echo "Image: ${IMAGE_TAR_ROS}"
echo "Root dir: ${ROOT_DIR}"
echo "Dashboard: http://${CONTAINER_IP_ONLY}:8080/"