| rule-updater.py installRules.py pkill -USR2 suricata |
#!/bin/sh
set -u
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
export PATH
TAG="suricata-safe-update"
UPDATER="/usr/local/opnsense/scripts/suricata/rule-updater.py"
INSTALLER="/usr/local/opnsense/scripts/suricata/installRules.py"
SURICATA="/usr/local/bin/suricata"
CONFIG="/usr/local/etc/suricata/suricata.yaml"
RC="/usr/local/etc/rc.d/suricata"
ACTIVE_DIR="/usr/local/etc/suricata/opnsense.rules"
ACTIVE_YAML="/usr/local/etc/suricata/installed_rules.yaml"
BACKUP_DIR=""
logmsg()
{
logger -t "$TAG" "$*"
echo "$TAG: $*"
}
active_hash()
{
{
if [ -d "$ACTIVE_DIR" ]; then
find "$ACTIVE_DIR" -type f -name '*.rules' -print 2>/dev/null |
sort |
while IFS= read -r f; do
printf '%s ' "$f"
sha256 -q "$f"
done
fi
if [ -f "$ACTIVE_YAML" ]; then
printf '%s ' "$ACTIVE_YAML"
sha256 -q "$ACTIVE_YAML"
fi
} | sha256 -q
}
divert_count()
{
sockstat 2>/dev/null |
awk '$2 == "suricata" && $5 == "div4" && $6 == "*:8000" { n++ }
END { print n + 0 }'
}
restore_active()
{
logmsg "restoring previous active ruleset"
rm -rf "$ACTIVE_DIR"
if [ -d "$BACKUP_DIR/opnsense.rules" ]; then
cp -a "$BACKUP_DIR/opnsense.rules" "$ACTIVE_DIR"
fi
if [ -f "$BACKUP_DIR/installed_rules.yaml" ]; then
cp -p "$BACKUP_DIR/installed_rules.yaml" "$ACTIVE_YAML"
else
rm -f "$ACTIVE_YAML"
fi
}
cleanup()
{
if [ -n "${BACKUP_DIR:-}" ] && [ -d "$BACKUP_DIR" ]; then
rm -rf "$BACKUP_DIR"
fi
}
trap cleanup EXIT HUP INT TERM
for f in "$UPDATER" "$INSTALLER" "$SURICATA" "$CONFIG" "$RC"; do
if [ ! -e "$f" ]; then
logmsg "ABORT: required file missing: $f"
exit 10
fi
done
if ! "$RC" status >/dev/null 2>&1; then
logmsg "ABORT: Suricata is not running"
exit 11
fi
OLD_DIVERT=$(divert_count)
if [ "$OLD_DIVERT" -lt 1 ]; then
logmsg "ABORT: no active Suricata Divert listeners found"
exit 12
fi
OLD_PID=$(cat /var/run/suricata.pid 2>/dev/null || true)
BEFORE=$(active_hash)
logmsg "starting rule update; current PID=$OLD_PID divert_listeners=$OLD_DIVERT"
"$UPDATER"
rc=$?
if [ "$rc" -ne 0 ]; then
logmsg "FAILED: rule-updater.py rc=$rc"
exit "$rc"
fi
BACKUP_DIR=$(mktemp -d /root/suricata-safe-update.XXXXXX)
if [ -d "$ACTIVE_DIR" ]; then
cp -a "$ACTIVE_DIR" "$BACKUP_DIR/opnsense.rules"
fi
if [ -f "$ACTIVE_YAML" ]; then
cp -p "$ACTIVE_YAML" "$BACKUP_DIR/installed_rules.yaml"
fi
logmsg "installing updated rules"
"$INSTALLER"
rc=$?
if [ "$rc" -ne 0 ]; then
logmsg "FAILED: installRules.py rc=$rc"
restore_active
exit "$rc"
fi
AFTER=$(active_hash)
if [ "$BEFORE" = "$AFTER" ]; then
logmsg "no active rule changes; restart skipped"
exit 0
fi
logmsg "active rules changed; validating new ruleset"
"$SURICATA" -T --init-errors-fatal -c "$CONFIG"
rc=$?
if [ "$rc" -ne 0 ]; then
logmsg "FAILED: Suricata validation rc=$rc; restart NOT performed"
restore_active
exit "$rc"
fi
logmsg "validation passed; performing full Suricata restart"
"$RC" restart
rc=$?
if [ "$rc" -ne 0 ]; then
logmsg "FAILED: restart rc=$rc; restoring previous ruleset"
restore_active
"$RC" restart
exit 30
fi
i=0
while [ "$i" -lt 36 ]; do
sleep 5
NEW_DIVERT=$(divert_count)
if "$RC" status >/dev/null 2>&1 &&
[ "$NEW_DIVERT" -eq "$OLD_DIVERT" ]; then
NEW_PID=$(cat /var/run/suricata.pid 2>/dev/null || true)
logmsg "SUCCESS: PID=$NEW_PID divert_listeners=$NEW_DIVERT"
exit 0
fi
i=$((i + 1))
done
logmsg "FAILED: Divert listeners did not recover within 180 seconds"
logmsg "restoring previous ruleset and restarting"
restore_active
"$RC" restart
rc=$?
if [ "$rc" -ne 0 ]; then
logmsg "CRITICAL: rollback restart failed rc=$rc"
exit 40
fi
logmsg "ROLLBACK completed; manual verification required"
exit 41chown root:wheel /usr/local/sbin/suricata-safe-update
chmod 0700 /usr/local/sbin/suricata-safe-update/bin/sh -n /usr/local/sbin/suricata-safe-updategrep -nEi 'USR2|pkill|kill ' /usr/local/sbin/suricata-safe-update[update]
command:/usr/bin/lockf -k -t 0 /var/run/suricata-safe-update.lock /usr/local/sbin/suricata-safe-update
parameters:
type:script
message:safely update Suricata rules with full restart when needed
description:Suricata safe rule update (full restart, no SIGUSR2)chown root:wheel /usr/local/opnsense/service/conf/actions.d/actions_suricatasafe.conf
chmod 0644 /usr/local/opnsense/service/conf/actions.d/actions_suricatasafe.confservice configd restartconfigctl configd actions | grep -i suricatasafecat /var/run/suricata.pidsockstat | grep '[s]uricata'/usr/local/sbin/configctl -d -- suricatasafe updateps -axww -o pid,ppid,state,etime,%cpu,%mem,rss,vsz,command | grep -E 'suricata-safe-update|suricata -T|/usr/local/bin/suricata' | grep -v grepsockstat | grep '[s]uricata'cat /var/run/suricata.pid/bin/sh -c 'PID=$(cat /var/run/suricata.pid); grep "suricata $PID" /var/log/suricata/suricata_*.log | grep -E "Engine started|<Error>|<Warning>" | tail -30'| Minutes: 0 Hours: 2 Days: * Months: * Weekdays: * |
0 2 * * * /usr/local/sbin/configctl -d -- suricatasafe updategrep -nE 'ids update|suricatasafe update' /var/cron/tabs/nobody/usr/local/sbin/configctl -d -- ids update/usr/local/sbin/configctl -d -- suricatasafe update| error in configd communication TimeoutError: timed out |
/usr/local/sbin/configctl -d -- suricatasafe update| OPNsense: 26.7.2_2 Suricata: 8.0.6 IPS mode: Divert Pattern matcher: Hyperscan |
| div4 *:8000 div4 *:8000 div4 *:8000 div4 *:8000 div4 *:8000 div4 *:8000 div4 *:8000 div4 *:8000 |
| 93d4235d5c79a3ce2aba22bbc80e049601ca0d5ca88a845f24afb67ed928231f |
pkill -USR2 suricata