diff --git a/net/snort3/Makefile b/net/snort3/Makefile index 2cd80567e2..b991666c3e 100644 --- a/net/snort3/Makefile +++ b/net/snort3/Makefile @@ -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 diff --git a/net/snort3/files/homenet.lua b/net/snort3/files/homenet.lua deleted file mode 100644 index 91845611d3..0000000000 --- a/net/snort3/files/homenet.lua +++ /dev/null @@ -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" diff --git a/net/snort3/files/local.lua b/net/snort3/files/local.lua deleted file mode 100644 index 8de694131d..0000000000 --- a/net/snort3/files/local.lua +++ /dev/null @@ -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, ---} diff --git a/net/snort3/files/main.uc b/net/snort3/files/main.uc index 7db420f339..c8e039417b 100644 --- a/net/snort3/files/main.uc +++ b/net/snort3/files/main.uc @@ -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; } diff --git a/net/snort3/files/nftables.uc b/net/snort3/files/nftables.uc index c87246b441..5160334262 100644 --- a/net/snort3/files/nftables.uc +++ b/net/snort3/files/nftables.uc @@ -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 }} } } diff --git a/net/snort3/files/snort-mgr b/net/snort3/files/snort-mgr index 6a5e85e228..cc60abf654 100644 --- a/net/snort3/files/snort-mgr +++ b/net/snort3/files/snort-mgr @@ -1,7 +1,7 @@ #!/bin/sh # Copyright (c) 2023 Eric Fahlgren # 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 diff --git a/net/snort3/files/snort.config b/net/snort3/files/snort.config index 5567ef4646..b7d3790104 100644 --- a/net/snort3/files/snort.config +++ b/net/snort3/files/snort.config @@ -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 diff --git a/net/snort3/files/snort.uc b/net/snort3/files/snort.uc index b58fa01e6d..dc36e898d2 100644 --- a/net/snort3/files/snort.uc +++ b/net/snort3/files/snort.uc @@ -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 }); +} +%}