[01/20] suricata: Move the IPS into the mangle table

Message ID 20240910143748.3469271-2-michael.tremer@ipfire.org
State New
Headers
Series [01/20] suricata: Move the IPS into the mangle table |

Commit Message

Michael Tremer Sept. 10, 2024, 2:37 p.m. UTC
  This should make the IPS more efficient, we should have fewer rules and
the IPS will now sit at the edge of the networking stack as it will see
packets immediately when they come and and just before they leave.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
---
 src/initscripts/system/firewall |  23 +------
 src/initscripts/system/suricata | 108 +++++++++++---------------------
 2 files changed, 39 insertions(+), 92 deletions(-)
  

Patch

diff --git a/src/initscripts/system/firewall b/src/initscripts/system/firewall
index 6727e4a20..39d9c0f23 100644
--- a/src/initscripts/system/firewall
+++ b/src/initscripts/system/firewall
@@ -39,11 +39,6 @@  fi
 
 NAT_MASK="0x0f000000"
 
-IPS_REPEAT_MARK="0x80000000"
-IPS_REPEAT_MASK="0x80000000"
-IPS_BYPASS_MARK="0x40000000"
-IPS_BYPASS_MASK="0x40000000"
-
 IPSET_DB_DIR="/var/lib/location/ipset"
 
 SYNPROXY_OPTIONS=(
@@ -84,16 +79,6 @@  iptables_init() {
 	modprobe nf_log_ipv4
 	sysctl -q -w net.netfilter.nf_log.2=nf_log_ipv4
 
-	# IPS Bypass Chain which stores the BYPASS bit in connection tracking
-	iptables -N IPSBYPASS
-	iptables -A IPSBYPASS -j CONNMARK --save-mark --mask "$(( ~IPS_REPEAT_MASK & 0xffffffff ))"
-
-	# Jump into bypass chain when the BYPASS bit is set
-	for chain in INPUT FORWARD OUTPUT; do
-		iptables -A "${chain}" -m mark \
-			--mark "$(( IPS_REPEAT_MARK | IPS_BYPASS_MARK ))/$(( IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j IPSBYPASS
-	done
-
 	# Empty LOG_DROP and LOG_REJECT chains
 	iptables -N LOG_DROP
 	iptables -A LOG_DROP   -m limit --limit 10/second -j LOG
@@ -237,12 +222,10 @@  iptables_init() {
 	iptables -A FORWARD -o tun+ -j OVPNBLOCK
 
 	# IPS (Suricata) chains
-	iptables -N IPS_INPUT
-	iptables -N IPS_FORWARD
-	iptables -N IPS_OUTPUT
+	iptables -t mangle -N IPS
 
-	for chain in INPUT FORWARD OUTPUT; do
-		iptables -A "${chain}" -m mark --mark "0x0/$(( IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j "IPS_${chain}"
+	for chain in PREROUTING POSTROUTING; do
+		iptables -t mangle -A "${chain}" -j IPS
 	done
 
 	# OpenVPN transfer network translation
diff --git a/src/initscripts/system/suricata b/src/initscripts/system/suricata
index 79f9478c3..253ece117 100644
--- a/src/initscripts/system/suricata
+++ b/src/initscripts/system/suricata
@@ -27,13 +27,20 @@  PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin; export PATH
 eval $(/usr/local/bin/readhash /var/ipfire/suricata/settings)
 eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
 
+IPS_REPEAT_MARK="0x80000000"
+IPS_REPEAT_MASK="0x80000000"
+IPS_BYPASS_MARK="0x40000000"
+IPS_BYPASS_MASK="0x40000000"
+
 # Name of the firewall chains.
 IPS_INPUT_CHAIN="IPS_INPUT"
 IPS_FORWARD_CHAIN="IPS_FORWARD"
 IPS_OUTPUT_CHAIN="IPS_OUTPUT"
 
 # Optional options for the Netfilter queue.
-NFQ_OPTS="--queue-bypass "
+NFQ_OPTS=(
+	"--queue-bypass"
+)
 
 # Array containing the 4 possible network zones.
 network_zones=( red green blue orange ovpn )
@@ -64,91 +71,48 @@  function get_cpu_count {
 
 # Function to flush the firewall chains.
 function flush_fw_chain {
-	# Call iptables and flush the chains
-	iptables -w -F "$IPS_INPUT_CHAIN"
-	iptables -w -F "$IPS_FORWARD_CHAIN"
-	iptables -w -F "$IPS_OUTPUT_CHAIN"
+	iptables -w -t mangle -F IPS
 }
 
 # Function to create the firewall rules to pass the traffic to suricata.
 function generate_fw_rules {
-	cpu_count=$(get_cpu_count)
-
-	# Loop through the array of network zones.
-	for zone in "${network_zones[@]}"; do
-		# Convert zone into upper case.
-		zone_upper=${zone^^}
-
-		# Generate variable name for checking if the IDS is
-		# enabled on the zone.
-		enable_ids_zone="ENABLE_IDS_$zone_upper"
-
-		# Check if the IDS is enabled for this network zone.
-		if [ "${!enable_ids_zone}" == "on" ]; then
-			# Check if the current processed zone is "red" and the configured type is PPPoE dialin.
-			if [ "$zone" == "red" ] && [ "$RED_TYPE" == "PPPOE" ] && [ "$RED_DRIVER" != "qmi_wwan" ]; then
-				# Set device name to ppp0.
-				network_device="ppp0"
-			elif [ "$zone" == "ovpn" ]; then
-				# Get all virtual net devices because the RW server and each
-				# N2N connection creates it's own tun device.
-				for virt_dev in /sys/devices/virtual/net/*; do
-					# Cut-off the directory.
-					dev="${virt_dev##*/}"
-
-					# Only process tun devices.
-					if [[ $dev =~ "tun" ]]; then
-						# Add the network device to the array of enabled zones.
-						enabled_ips_zones+=( "$dev" )
-					fi
-				done
-
-				# Process next zone.
-				continue
-			else
-				# Generate variable name which contains the device name.
-				zone_name="$zone_upper"
-				zone_name+="_DEV"
-
-				# Grab device name.
-				network_device=${!zone_name}
-			fi
-
-			# Add the network device to the array of enabled zones.
-			enabled_ips_zones+=( "$network_device" )
-		fi
-	done
-
 	# Assign NFQ_OPTS
-	NFQ_OPTIONS=$NFQ_OPTS
+	local NFQ_OPTIONS=( "${NFQ_OPTS[@]}" )
+
+	local cpu_count="$(get_cpu_count)"
 
 	# Check if there are multiple cpu cores available.
 	if [ "$cpu_count" -gt "1" ]; then
-		# Balance beetween all queues.
-		NFQ_OPTIONS+="--queue-balance 0:$(($cpu_count-1))"
-		NFQ_OPTIONS+=" --queue-cpu-fanout"
+		# Balance beetween all queues
+		NFQ_OPTIONS+=(
+			"--queue-balance" "0:$(($cpu_count-1))"
+			"--queue-cpu-fanout"
+		)
 	else
-		# Send all packets to queue 0.
-		NFQ_OPTIONS+="--queue-num 0"
+		# Send all packets to queue 0
+		NFQ_OPTIONS+=(
+			"--queue-num" "0"
+		)
 	fi
 
 	# Flush the firewall chains.
 	flush_fw_chain
 
-	# Check if the array of enabled_ips_zones contains any elements.
-	if [[ ${enabled_ips_zones[@]} ]]; then
-		# Loop through the array and create firewall rules.
-		for enabled_ips_zone in "${enabled_ips_zones[@]}"; do
-			# Create rules queue input and output related traffic and pass it to the IPS.
-			iptables -w -A "$IPS_INPUT_CHAIN" -i "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
-			iptables -w -A "$IPS_OUTPUT_CHAIN" -o "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
-
-			# Create rules which are required to handle forwarded traffic.
-			for enabled_ips_zone_forward in "${enabled_ips_zones[@]}"; do
-				iptables -w -A "$IPS_FORWARD_CHAIN" -i "$enabled_ips_zone" -o "$enabled_ips_zone_forward" -j NFQUEUE $NFQ_OPTIONS
-			done
-		done
-	fi
+	# Don't process packets where the IPS has requested to bypass the stream
+	iptables -w -t mangle -A IPS -m mark --mark "$(( IPS_BYPASS_MARK ))/$(( IPS_BYPASS_MASK ))" -j RETURN
+
+	# Don't process packets that have already been seen by the IPS
+	iptables -w -t mangle -A IPS -m mark --mark "$(( IPS_REPEAT_MARK ))/$(( IPS_REPEAT_MASK ))" -j RETURN
+
+	# Send packets to suricata
+	iptables -w -t mangle -A IPS -j NFQUEUE "${NFQ_OPTIONS[@]}"
+
+	# If suricata decided to bypass a stream, we will store the mark in the connection tracking table
+	iptables -w -t mangle -A IPS \
+		-m mark --mark "$(( IPS_BYPASS_MARK ))/$(( IPS_BYPASS_MASK ))" \
+		-j CONNMARK --save-mark --mask "$(( IPS_BYPASS_MASK ))"
+
+	return 0
 }
 
 case "$1" in