snort3: add missing config include and general cleanup
authorEric Fahlgren <ericfahlgren@gmail.com>
Wed, 6 Dec 2023 23:37:32 +0000 (15:37 -0800)
committerTianling Shen <cnsztl@gmail.com>
Sat, 16 Dec 2023 14:08:49 +0000 (22:08 +0800)
- 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>
net/snort3/Makefile
net/snort3/files/homenet.lua [deleted file]
net/snort3/files/local.lua [deleted file]
net/snort3/files/main.uc
net/snort3/files/nftables.uc
net/snort3/files/snort-mgr
net/snort3/files/snort.config
net/snort3/files/snort.uc

index 2cd80567e22ef3db9393665f84532e3b97b96372..b991666c3e0e39c645716f269e864b3fa43f4a25 100644 (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
diff --git a/net/snort3/files/homenet.lua b/net/snort3/files/homenet.lua
deleted file mode 100644 (file)
index 9184561..0000000
+++ /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 (file)
index 8de6941..0000000
+++ /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,
---}
index 7db420f339d04f5193648de9d2c0f0cd8d2eb605..c8e039417b292a0a7a994f20bf8a0a006ff8fdd5 100644 (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;
 }
 
index c87246b4411b2b7d543ccae8e632c8bc1ba969fa..5160334262826bb6f4407a127ae35558e7cd6fb0 100644 (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 }}
        }
 }
index 6a5e85e2282a086fefd5e30826e3e88e2d089f76..cc60abf65461754f387d29954380db2b9ab299ad 100644 (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 warn no_rules
+       if [ -n "$VERBOSE" ]; then
+               warn='--warn-all'
+               no_rules=0
+       else
+               warn='-q'
+               no_rules=1
+       fi
+
        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
+       _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}"
-               return 0
+       else
+               die "Errors in snort config tests.  Examine ${test_conf} for issues."
        fi
-       die "Errors in snort config tests."
+
+       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
index 5567ef46468d9e2a4bacf6ef5832e0a527af8262..b7d37901047ffb881f2c85ec48fa8565c5c088f4 100644 (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
index b58fa01e6d095afceace56feabb0c2e9567724d5..dc36e898d2c9955929b0754fc111a9e0e35575a9 100644 (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 });
+}
+%}