snort3: add missing config include and general cleanup

- Delete legacy configuration files homenet.lua and local.lua
- Add snort config 'include' to allow user customizations in the lua
- Enhance 'check' to test generated nftables file
- Suppress inclusion of rules file when doing silent config check
- Suppress warnings on configuration check unless '-v'erbose
- Replace text logging with json logging to reduce footprint and make reports easier
- Fix some typos in the snort.uc template
- Fix up some error messages suggesting solutions

Signed-off-by: Eric Fahlgren <ericfahlgren@gmail.com>
This commit is contained in:
Eric Fahlgren 2023-12-06 15:37:32 -08:00 committed by Tianling Shen
parent 880ac1f3d5
commit 0d2dac8792
8 changed files with 120 additions and 113 deletions

View File

@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=snort3
PKG_VERSION:=3.1.76.0
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/snort3/snort3/archive/refs/tags/
@ -125,15 +125,12 @@ define Package/snort3/install
$(INSTALL_CONF) \
./files/snort.config \
$(1)/etc/config/snort
$(INSTALL_CONF) \
./files/local.lua \
$(1)/etc/snort
$(INSTALL_CONF) \
./files/homenet.lua \
$(1)/etc/snort
sed \
-i -e "/^EXTERNAL_NET\\s\\+=/ a include 'homenet.lua'" \
-e "/^HOME_NET\\s\\+=/ i -- we set HOME_NET and EXTERNAL_NET here or via an included file" \
-i \
-e "/^-- HOME_NET and EXTERNAL_NET/ i -- The values for the two variables HOME_NET and EXTERNAL_NET have been" \
-e "/^-- HOME_NET and EXTERNAL_NET/ i -- moved to /etc/config/snort, so do not modify them here without good" \
-e "/^-- HOME_NET and EXTERNAL_NET/ i -- reason.\n" \
-e 's/^\(HOME_NET\s\+=\)/--\1/g' \
-e 's/^\(EXTERNAL_NET\s\+=\)/--\1/g' \
$(1)/etc/snort/snort.lua

View File

@ -1,4 +0,0 @@
-- Unused when using 'snort-mgr', do not modify without deep understanding.
-- setup HOME_NET below with your IP range/ranges to protect
--HOME_NET = [[ 192.168.1.0/24 10.1.0.0/24 ]]
--EXTERNAL_NET = "!$HOME_NET"

View File

@ -1,62 +0,0 @@
-- This file is no longer used if you are using 'snort-mgr' to create the
-- configuration. It is left as a sample.
--
-- use ths file to customize any functions defined in /etc/snort/snort.lua
-- switch tap to inline in ips and uncomment the below to run snort in inline mode
--snort = {}
--snort["-Q"] = true
ips = {
mode = tap,
-- mode = inline,
variables = default_variables,
-- uncomment and change the below to reflect rules or symlinks to rules on your filesystem
-- include = RULE_PATH .. '/snort.rules',
}
daq = {
module_dirs = {
'/usr/lib/daq',
},
modules = {
{
name = 'afpacket',
mode = 'inline',
}
}
}
alert_syslog = {
level = 'info',
}
-- To log to a file, uncomment the below and manually create the dir defined in output.logdir
--output.logdir = '/var/log/snort'
--alert_fast = {
-- file = true,
-- packet = false,
--}
normalizer = {
tcp = {
ips = true,
}
}
file_policy = {
enable_type = true,
enable_signature = true,
rules = {
use = {
verdict = 'log', enable_file_type = true, enable_file_signature = true
}
}
}
-- To use openappid with snort, install the openappid package and uncomment the below
--appid = {
-- app_detector_dir = '/usr/lib/openappid',
-- log_stats = true,
-- app_stats_period = 60,
--}

View File

@ -93,6 +93,8 @@ const snort_config = {
action: config_item("enum", [ "alert", "block", "drop", "reject" ]),
interface: config_item("str", [ uci.get("network", "wan", "device") ]),
snaplen: config_item("range", [ 1518, 65535 ]), // int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
include: config_item("path", [ "" ]), // User-defined snort configuration, applied at end of snort.lua.
};
const nfq_config = {
@ -123,7 +125,7 @@ snort
your lan range, default is '192.168.1.0/24'
external_net - IP range external to home. Usually 'any', but if you only
care about true external hosts (trusting all lan devices),
then '!$HOMENET' or some specific range
then '!$HOME_NET' or some specific range
mode - 'ids' or 'ips', for detection-only or prevention, respectively
oinkcode - https://www.snort.org/oinkcodes
config_dir - Location of the base snort configuration files. Default /etc/snort
@ -138,6 +140,7 @@ snort
action - 'alert', 'block', 'reject' or 'drop'
method - 'pcap', 'afpacket' or 'nfq'
snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
include - User-defined snort configuration, applied at end of generated snort.lua
nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
queue_maxlen - nfq's '--daq-var queue_maxlen=int'
@ -237,7 +240,8 @@ function render_help() {
load_all();
switch (getenv("TYPE")) {
let table_type = getenv("TYPE");
switch (table_type) {
case "snort":
render_snort();
return;
@ -255,7 +259,7 @@ switch (getenv("TYPE")) {
return;
default:
print("Invalid table type.\n");
print(`Invalid table type '${table_type}', should be one of snort, nftables, config, help.\n`);
return;
}

View File

@ -11,8 +11,13 @@ table inet snort {
chain {{ chain_type }}_{{ snort.mode }} {
type filter hook {{ chain_type }} priority {{ nfq.chain_priority }}
policy accept
{% if (nfq.include) { include(nfq.include, { snort, nfq }); } %}
# tcp flags ack ct direction original ct state established counter accept
{% if (nfq.include) {
// We use the ucode include here, so that the included file is also
// part of the template and can use values passed in from the config.
printf("\n\t\t#-- The following content included from '%s'\n", nfq.include);
include(nfq.include, { snort, nfq });
printf("\t\t#-- End of included file.\n\n");
} %}
counter queue flags bypass to {{ queues }}
}
}

View File

@ -1,7 +1,7 @@
#!/bin/sh
# Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2039 # "local" not defined in POSIX sh
# shellcheck disable=SC2039,SC2155 # "local" not defined in POSIX sh
PROG="/usr/bin/snort"
MAIN="/usr/share/snort/main.uc"
@ -26,7 +26,7 @@ disable_offload()
{
# From https://forum.openwrt.org/t/snort-3-nfq-with-ips-mode/161172
# https://blog.snort.org/2016/08/running-snort-on-commodity-hardware.html
# Not needed when running the nft daq as defragmentation is done by the kernel.
# Not needed when running the nfq daq as defragmentation is done by the kernel.
# What about pcap?
local filter_method=$(uci -q get snort.snort.method)
@ -55,6 +55,8 @@ nft_add_table() {
setup() {
# Generates all the configuration, then reports the config file for snort.
# Does NOT generate the rules file, you'll need to do 'update-rules' first.
local log_dir=$(uci get snort.snort.log_dir)
[ ! -e "$log_dir" ] && mkdir -p "$log_dir"
nft_rm_table
print snort > "$CONF"
nft_add_table
@ -82,13 +84,33 @@ check() {
[ "$manual" = 1 ] && return 0
[ -n "$QUIET" ] && OUT=/dev/null || OUT=$STDOUT
local test_conf="${CONF_DIR}/test_conf.lua"
print snort > "${test_conf}" || die "Errors during generation of config."
if $PROG -T -q --warn-all -c "${test_conf}" 2> $OUT ; then
rm "${test_conf}"
return 0
local warn no_rules
if [ -n "$VERBOSE" ]; then
warn='--warn-all'
no_rules=0
else
warn='-q'
no_rules=1
fi
die "Errors in snort config tests."
local test_conf="${CONF_DIR}/test_conf.lua"
_SNORT_WITHOUT_RULES="$no_rules" print snort > "${test_conf}" || die "Errors during generation of snort config."
if $PROG -T $warn -c "${test_conf}" 2> $OUT ; then
rm "${test_conf}"
else
die "Errors in snort config tests. Examine ${test_conf} for issues."
fi
if [ "$(uci -q get snort.snort.method)" = "nfq" ]; then
local test_nft="${CONF_DIR}/test_conf.nft"
print nftables > "${test_nft}" || die "Errors during generation of nftables config."
if nft $VERBOSE --check -f "${test_nft}" ; then
rm "${test_nft}"
else
die "Errors in nftables config tests. Examine ${test_nft} for issues."
fi
fi
}
report() {
@ -120,20 +142,23 @@ report() {
die "Logging is not enabled in snort config."
fi
#if [ -z "$pattern" ]; then
# die "Provide a valid IP and try again."
#fi
[ "$NLINES" = 0 ] && output="cat" || output="head -n $NLINES"
# Fix this to use json file.
local msg src dst dir
tmp="/tmp/snort.report.$$"
echo "Intrusions involving ${pattern:-all IPs}"
grep "\b${pattern}\b" "$log_dir/alert_fast.txt" \
| sed 's/.*"\([^"]*\)".* \([^ :]*\)[: ].*-> \(.*\)/\1#\2#\3/' > "$tmp"
for file in "${log_dir}"/*alert_json.txt; do
while read -r line; do
eval $(jsonfilter -s "$line" -e 'msg=$.msg' -e 'src=$.src_ap' -e 'dst=$.dst_ap' -e 'dir=$.dir')
src=$(echo "$src" | sed 's/:.*$//') # Delete all source ports.
dst=$(echo "$dst" | sed 's/:0$//') # Delete unspecified dest port.
echo "$msg#$src#$dst#$dir"
done < "$file"
done | grep -i "$pattern" > "$tmp"
echo "Events involving ${pattern:-all IPs}"
n_incidents="$(wc -l < $tmp)"
lines=$(sort "$tmp" | uniq -c | sort -nr \
| awk -F'#' '{printf "%-80s %-12s -> %s\n", $1, $2, $3}')
| awk -F'#' '{printf "%-80s %s %-13s -> %s\n", $1, $4, $2, $3}')
echo "$lines" | $output
n_lines=$(echo "$lines" | wc -l)
[ "$NLINES" -gt 0 ] && [ "$NLINES" -lt "$n_lines" ] && echo " ... Only showing $NLINES of $n_lines most frequent incidents."
@ -142,7 +167,8 @@ report() {
}
status() {
echo 'tbd'
echo -n 'snort is ' ; service snort status
ps w | grep -E 'PID|snort' | grep -v grep
}
@ -179,7 +205,7 @@ case "$1" in
teardown
;;
resetup)
QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting."
QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting. Run 'snort-mgr check'"
teardown
setup
;;
@ -221,7 +247,7 @@ Usage:
Report on incidents. Note this is somewhat experimental, so suggested
improvements are quite welcome.
pattern = IP or piece of IP or something in the message to filter.
pattern = A case-insensitive grep pattern used to filter output.
$0 [-t] update-rules
@ -243,6 +269,7 @@ Usage:
snort = The snort configuration file, which is a lua script.
nftables = The nftables script used to define the input queues when using
the 'nfq' DAQ.
help = Display config file help.
$0 [-q] check

View File

@ -13,7 +13,7 @@
# your lan range, default is '192.168.1.0/24'
# external_net - IP range external to home. Usually 'any', but if you only
# care about true external hosts (trusting all lan devices),
# then '!$HOMENET' or some specific range
# then '!$HOME_NET' or some specific range
# mode - 'ids' or 'ips', for detection-only or prevention, respectively
# oinkcode - https://www.snort.org/oinkcodes
# config_dir - Location of the base snort configuration files. Default /etc/snort
@ -28,6 +28,7 @@
# action - 'alert', 'block', 'reject' or 'drop'
# method - 'pcap', 'afpacket' or 'nfq'
# snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
# include - User-defined snort configuration, applied at end of generated snort.lua
#
# nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
# queue_maxlen - nfq's '--daq-var queue_maxlen=int'
@ -61,6 +62,7 @@ config snort 'snort'
option action 'alert' # one of [alert, block, drop, reject]
option interface 'eth0' # a string
option snaplen '1518' # 1518 <= x <= 65535
option include '' # a path string
config nfq 'nfq'
option queue_count '4' # 1 <= x <= 16

View File

@ -7,7 +7,8 @@
let home_net = snort.home_net == 'any' ? "'any'" : snort.home_net;
let external_net = snort.external_net;
let line_mode = snort.mode == "ids" ? "tap" : "inline";
let line_mode = snort.mode == "ids" ? "tap" : "inline";
let mod_mode = snort.mode == "ids" ? "passive" : "inline";
let inputs = null;
let vars = null;
@ -32,9 +33,8 @@ case "nfq":
-- Do not edit, automatically generated. See /usr/share/snort/templates.
-- These must be defined before processing snort.lua
-- The default include '/etc/snort/homenet.lua' must not redefine them.
HOME_NET = [[ {{ home_net }} ]]
EXTERNAL_NET = '{{ external_net }}'
EXTERNAL_NET = [[ {{ external_net }} ]]
include('{{ snort.config_dir }}/snort.lua')
@ -42,7 +42,7 @@ snort = {
{% if (snort.mode == 'ips'): %}
['-Q'] = true,
{% endif %}
['--daq'] = {{ snort.method }},
['--daq'] = '{{ snort.method }}',
--['--daq-dir'] = '/usr/lib/daq/',
{% if (snort.method == 'nfq'): %}
['--max-packet-threads'] = {{ nfq.thread_count }},
@ -50,10 +50,14 @@ snort = {
}
ips = {
mode = {{ line_mode }},
mode = '{{ line_mode }}',
variables = default_variables,
action_override = {{ snort.action }},
include = "{{ snort.config_dir }}/" .. RULE_PATH .. '/snort.rules',
action_override = '{{ snort.action }}',
{% if (getenv("_SNORT_WITHOUT_RULES") == "1"): %}
-- WARNING: THIS IS A TEST-ONLY CONFIGURATION WITHOUT ANY RULES.
{% else %}
include = '{{ snort.config_dir }}/' .. RULE_PATH .. '/snort.rules',
{% endif -%}
}
daq = {
@ -63,7 +67,7 @@ daq = {
modules = {
{
name = '{{ snort.method }}',
mode = {{ line_mode }},
mode = '{{ mod_mode }}',
variables = {{ vars }},
}
}
@ -75,12 +79,11 @@ alert_syslog = {
{% if (int(snort.logging)): %}
-- Note that this is also the location of the PID file, if you use it.
output.logdir = "{{ snort.log_dir }}"
output.logdir = '{{ snort.log_dir }}'
-- Maybe add snort.log_type, 'fast', 'json' and 'full'?
-- Json would be best for reporting, see 'snort-mgr report' code.
-- alert_full = { file = true, }
--[[
alert_fast = {
-- bool alert_fast.file = false: output to alert_fast.txt instead of stdout
-- bool alert_fast.packet = false: output packet dump with alert
@ -88,14 +91,40 @@ alert_fast = {
file = true,
packet = false,
}
--]]
alert_json = {
-- bool alert_json.file = false: output to alert_json.txt instead of stdout
-- multi alert_json.fields = timestamp pkt_num proto pkt_gen pkt_len dir src_ap dst_ap rule action: selected fields will be output
-- int alert_json.limit = 0: set maximum size in MB before rollover (0 is unlimited) { 0:maxSZ }
-- string alert_json.separator = , : separate fields with this character sequence
-- multi alert_json.fields = 'timestamp pkt_num proto pkt_gen pkt_len dir src_ap dst_ap'
-- Rule action: selected fields will be output in given order left to right.
-- { action | class | b64_data | client_bytes | client_pkts | dir
-- | dst_addr | dst_ap | dst_port | eth_dst | eth_len | eth_src
-- | eth_type | flowstart_time | geneve_vni | gid | icmp_code
-- | icmp_id | icmp_seq | icmp_type | iface | ip_id | ip_len
-- | msg | mpls | pkt_gen | pkt_len | pkt_num | priority
-- | proto | rev | rule | seconds | server_bytes | server_pkts
-- | service | sgt | sid | src_addr | src_ap | src_port | target
-- | tcp_ack | tcp_flags | tcp_len | tcp_seq | tcp_win | timestamp
-- | tos | ttl | udp_len | vlan }
-- This is a minimal set of fields that simply supports 'snort-mgr report'
-- and minimizes log size:
fields = 'dir src_ap dst_ap msg',
-- This set also supports the report, but closely matches 'alert_fast' contents.
--fields = 'timestamp pkt_num proto pkt_gen pkt_len dir src_ap dst_ap rule action msg',
file = true,
}
--[[
unified2 = {
limit = 10, -- int unified2.limit = 0: set maximum size in MB before rollover (0 is unlimited) { 0:maxSZ }
}
--]]
{% endif -%}
normalizer = {
@ -124,3 +153,12 @@ appid = {
app_stats_period = 60,
}
{% endif %}
{%
if (snort.include) {
// We use the ucode include here, so that the included file is also
// part of the template and can use values passed in from the config.
printf("-- The following content from included file '%s'\n", snort.include);
include(snort.include, { snort, nfq });
}
%}