snort3: complete rework
- Add many options to config file. - Move rules and generated snort.lua to /tmp. - Add script for downloading rules. - Add preliminary reporting capabilites. Signed-off-by: Eric Fahlgren <ericfahlgren@gmail.com>
This commit is contained in:
parent
904438be39
commit
f21dffc2a3
10 changed files with 888 additions and 15 deletions
|
@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||
|
||||
PKG_NAME:=snort3
|
||||
PKG_VERSION:=3.1.75.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/snort3/snort3/archive/refs/tags/
|
||||
|
@ -25,7 +25,7 @@ define Package/snort3
|
|||
SUBMENU:=Firewall
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
DEPENDS:=+libstdcpp +libdaq3 +libdnet +libopenssl +libpcap +libpcre +libpthread +libuuid +zlib +libhwloc +libtirpc @HAS_LUAJIT_ARCH +luajit +libatomic
|
||||
DEPENDS:=+libstdcpp +libdaq3 +libdnet +libopenssl +libpcap +libpcre +libpthread +libuuid +zlib +libhwloc +libtirpc @HAS_LUAJIT_ARCH +luajit +libatomic +kmod-nft-queue
|
||||
TITLE:=Lightweight Network Intrusion Detection System
|
||||
URL:=http://www.snort.org/
|
||||
MENU:=1
|
||||
|
@ -76,6 +76,10 @@ define Package/snort3/install
|
|||
$(PKG_INSTALL_DIR)/usr/bin/u2{boat,spewfoo} \
|
||||
$(1)/usr/bin/
|
||||
|
||||
$(INSTALL_BIN) \
|
||||
./files/snort-{mgr,rules} \
|
||||
$(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/lib/snort
|
||||
$(CP) \
|
||||
$(PKG_INSTALL_DIR)/usr/lib/snort/daq/daq_hext.so \
|
||||
|
@ -90,6 +94,19 @@ define Package/snort3/install
|
|||
$(PKG_INSTALL_DIR)/usr/include/snort/lua/snort_plugin.lua \
|
||||
$(1)/usr/share/lua/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/snort
|
||||
$(INSTALL_CONF) \
|
||||
./files/main.uc \
|
||||
$(1)/usr/share/snort/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/snort/templates
|
||||
$(INSTALL_CONF) \
|
||||
./files/nftables.uc \
|
||||
$(1)/usr/share/snort/templates/
|
||||
$(INSTALL_CONF) \
|
||||
./files/snort.uc \
|
||||
$(1)/usr/share/snort/templates/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/snort/{rules,lists,builtin_rules,so_rules}
|
||||
|
||||
$(INSTALL_CONF) \
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
-- 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.1/24 ]]
|
||||
EXTERNAL_NET = "!$HOME_NET"
|
||||
--HOME_NET = [[ 192.168.1.0/24 10.1.0.0/24 ]]
|
||||
--EXTERNAL_NET = "!$HOME_NET"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
-- 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
|
||||
|
|
263
net/snort3/files/main.uc
Normal file
263
net/snort3/files/main.uc
Normal file
|
@ -0,0 +1,263 @@
|
|||
{%
|
||||
//------------------------------------------------------------------------------
|
||||
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// The tables defined using 'config_item' are the source of record for the
|
||||
// configuration file, '/etc/config/snort'. If you wish to add new items,
|
||||
// do that only in the tables and propagate that use into the templates.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
import { cursor } from 'uci';
|
||||
let uci = cursor();
|
||||
|
||||
function wrn(fmt, ...args) {
|
||||
if (getenv("QUIET"))
|
||||
exit(1);
|
||||
|
||||
let msg = "ERROR: " + sprintf(fmt, ...args);
|
||||
|
||||
if (getenv("TTY"))
|
||||
warn(`\033[33m${msg}\033[m\n`);
|
||||
else
|
||||
warn(`[!] ${msg}\n`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function config_item(type, values, def) {
|
||||
// If no default value is provided explicity, then values[0] is used as default.
|
||||
if (! type in [ "enum", "range", "path", "str" ]) {
|
||||
wrn(`Invalid item type '${type}', must be one of "enum", "range", "path" or "str".`);
|
||||
return;
|
||||
}
|
||||
if (type == "range" && (length(values) != 2 || values[0] > values[1])) {
|
||||
wrn(`A 'range' type item must have exactly 2 values in ascending order.`);
|
||||
return;
|
||||
}
|
||||
// Maybe check paths for existence???
|
||||
|
||||
return {
|
||||
type: type,
|
||||
values: values,
|
||||
default: def ?? values[0],
|
||||
|
||||
contains: function(value) {
|
||||
// Check if the value is contained in the listed values,
|
||||
// depending on the item type.
|
||||
switch (this.type) {
|
||||
case "enum":
|
||||
return value in this.values;
|
||||
case "range":
|
||||
return value >= this.values[0] && value <= this.values[1];
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
allowed: function() {
|
||||
// Show a pretty version of the possible values, for error messages.
|
||||
switch (this.type) {
|
||||
case "enum":
|
||||
return "one of [" + join(", ", this.values) + "]";
|
||||
case "range":
|
||||
return `${this.values[0]} <= x <= ${this.values[1]}`;
|
||||
case "path":
|
||||
return "a path string";
|
||||
case "str":
|
||||
return "a string";
|
||||
default:
|
||||
return "???";
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const snort_config = {
|
||||
enabled: config_item("enum", [ 0, 1 ], 0), // Defaults to off, so that user must configure before first start.
|
||||
manual: config_item("enum", [ 0, 1 ], 1), // Allow user to manually configure, legacy behavior when enabled.
|
||||
oinkcode: config_item("str", [ "" ]), // User subscription oinkcode. Much more in 'snort-rules' script.
|
||||
home_net: config_item("str", [ "" ], "192.168.1.0/24"),
|
||||
external_net: config_item("str", [ "" ], "any"),
|
||||
|
||||
config_dir: config_item("path", [ "/etc/snort" ]), // Location of the base snort configuration files.
|
||||
temp_dir: config_item("path", [ "/var/snort.d" ]), // Location of all transient snort config, including downloaded rules.
|
||||
log_dir: config_item("path", [ "/var/log" ]), // Location of the generated logs, and oh-by-the-way the snort PID file (why?).
|
||||
logging: config_item("enum", [ 0, 1 ], 1),
|
||||
openappid: config_item("enum", [ 0, 1 ], 0),
|
||||
|
||||
mode: config_item("enum", [ "ids", "ips" ]),
|
||||
method: config_item("enum", [ "pcap", "afpacket", "nfq" ]),
|
||||
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 }
|
||||
};
|
||||
|
||||
const nfq_config = {
|
||||
queue_count: config_item("range", [ 1, 16 ], 4), // Count of queues to allocate in nft chain when method=nfq, usually 2-8.
|
||||
queue_start: config_item("range", [ 1, 32768], 4), // Start of queue numbers in nftables.
|
||||
queue_maxlen: config_item("range", [ 1024, 65536 ], 1024), // --daq-var queue_maxlen=int
|
||||
fanout_type: config_item("enum", [ "hash", "lb", "cpu", "rollover", "rnd", "qm"], "hash"), // See below.
|
||||
thread_count: config_item("range", [ 0, 32 ], 0), // 0 = use cpu count
|
||||
chain_type: config_item("enum", [ "prerouting", "input", "forward", "output", "postrouting" ], "input"),
|
||||
chain_priority: config_item("enum", [ "raw", "filter", "300"], "filter"),
|
||||
include: config_item("path", [ "" ]), // User-defined rules to include inside queue chain.
|
||||
};
|
||||
|
||||
|
||||
let _snort_config_doc =
|
||||
"
|
||||
This is not an exhaustive list of configuration items, just those that
|
||||
require more explanation than is given in the tables that define them, below.
|
||||
|
||||
https://openwrt.org/docs/guide-user/services/snort
|
||||
|
||||
snort
|
||||
manual - When set to 1, use manual configuration for legacy behavior.
|
||||
When disabled, then use this config.
|
||||
interface - Default should usually be 'uci get network.wan.device',
|
||||
something like 'eth0'
|
||||
home_net - IP range/ranges to protect. May be 'any', but more likely it's
|
||||
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
|
||||
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
|
||||
temp_dir - Location of all transient snort config, including downloaded rules
|
||||
Default /var/snort.d
|
||||
logging - Enable external logging of events thus enabling 'snort-mgr report',
|
||||
otherwise events only go to system log (i.e., 'logread -e snort:')
|
||||
log_dir - Location of the generated logs, and oh-by-the-way the snort
|
||||
PID file (why?). Default /var/log
|
||||
openappid - Enabled inspection using the 'openappid' package
|
||||
See 'opkg info openappid'
|
||||
action - 'alert', 'block', 'reject' or 'drop'
|
||||
method - 'pcap', 'afpacket' or 'nfq'
|
||||
snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
|
||||
|
||||
nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
|
||||
queue_maxlen - nfq's '--daq-var queue_maxlen=int'
|
||||
queue_count - Count of queues to use when method=nfq, usually 2-8
|
||||
fanout_type - Sets kernel load balancing algorithm*, one of hash, lb, cpu,
|
||||
rollover, rnd, qm.
|
||||
thread_count - int snort.-z: <count> maximum number of packet threads
|
||||
(same as --max-packet-threads); 0 gets the number of
|
||||
CPU cores reported by the system; default is 1 { 0:max32 }
|
||||
chain_type - Chain type when generating nft output
|
||||
chain_priority - Chain priority when generating nft output
|
||||
include - Full path to user-defined extra rules to include inside queue chain
|
||||
|
||||
* - for details on fanout_type, see these pages:
|
||||
https://github.com/florincoras/daq/blob/master/README
|
||||
https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
";
|
||||
|
||||
function snort_config_doc(comment) {
|
||||
if (comment == null) comment = "";
|
||||
if (comment != "") comment += " ";
|
||||
for (let line in split(_snort_config_doc, "\n")) {
|
||||
let msg = rtrim(sprintf("%s%s", comment, line));
|
||||
print(msg, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function load(section, config) {
|
||||
let self = {
|
||||
".name": section,
|
||||
".config": config,
|
||||
};
|
||||
|
||||
// Set the defaults from definitions in table.
|
||||
for (let item in config) {
|
||||
self[item] = config[item].default;
|
||||
}
|
||||
|
||||
// Overwrite them with any uci config settings.
|
||||
let cfg = uci.get_all("snort", section);
|
||||
for (let item in cfg) {
|
||||
// If you need to rename, delete or change the meaning of a
|
||||
// config item, just intercept it and do the work here.
|
||||
|
||||
if (exists(config, item)) {
|
||||
let val = cfg[item];
|
||||
if (config[item].contains(val))
|
||||
self[item] = val;
|
||||
else {
|
||||
wrn(`In option ${item}='${val}', must be ${config[item].allowed()}`);
|
||||
// ??? self[item] = config[item][0]; ???
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
let snort = null;
|
||||
let nfq = null;
|
||||
function load_all() {
|
||||
snort = load("snort", snort_config);
|
||||
nfq = load("nfq", nfq_config);
|
||||
}
|
||||
|
||||
function dump_config(settings) {
|
||||
let section = settings[".name"];
|
||||
let config = settings[".config"];
|
||||
printf("config %s '%s'\n", section, section);
|
||||
for (let item in config) {
|
||||
printf("\toption %-15s %-17s# %s\n", item, `'${settings[item]}'`, config[item].allowed());
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
|
||||
function render_snort() {
|
||||
include("templates/snort.uc", { snort, nfq });
|
||||
}
|
||||
|
||||
function render_nftables() {
|
||||
include("templates/nftables.uc", { snort, nfq });
|
||||
}
|
||||
|
||||
function render_config() {
|
||||
snort_config_doc("#");
|
||||
dump_config(snort);
|
||||
dump_config(nfq);
|
||||
}
|
||||
|
||||
function render_help() {
|
||||
snort_config_doc();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
load_all();
|
||||
|
||||
switch (getenv("TYPE")) {
|
||||
case "snort":
|
||||
render_snort();
|
||||
return;
|
||||
|
||||
case "nftables":
|
||||
render_nftables();
|
||||
return;
|
||||
|
||||
case "config":
|
||||
render_config();
|
||||
return;
|
||||
|
||||
case "help":
|
||||
render_help();
|
||||
return;
|
||||
|
||||
default:
|
||||
print("Invalid table type.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
-%}
|
18
net/snort3/files/nftables.uc
Normal file
18
net/snort3/files/nftables.uc
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Do not edit, automatically generated. See /usr/share/snort/templates.
|
||||
{%
|
||||
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
let queues = `${nfq.queue_start}-${int(nfq.queue_start)+int(nfq.queue_count)-1}`;
|
||||
let chain_type = nfq.chain_type;
|
||||
-%}
|
||||
|
||||
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
|
||||
counter queue flags bypass to {{ queues }}
|
||||
}
|
||||
}
|
260
net/snort3/files/snort-mgr
Normal file
260
net/snort3/files/snort-mgr
Normal file
|
@ -0,0 +1,260 @@
|
|||
#!/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
|
||||
|
||||
PROG="/usr/bin/snort"
|
||||
MAIN="/usr/share/snort/main.uc"
|
||||
CONF_DIR="/var/snort.d"
|
||||
CONF="${CONF_DIR}/snort_conf.lua"
|
||||
|
||||
VERBOSE=
|
||||
TESTING=
|
||||
NLINES=0
|
||||
|
||||
[ ! -e "$CONF_DIR" ] && mkdir "$CONF_DIR"
|
||||
[ -e /dev/stdin ] && STDIN=/dev/stdin || STDIN=/proc/self/fd/0
|
||||
[ -e /dev/stdout ] && STDOUT=/dev/stdout || STDOUT=/proc/self/fd/1
|
||||
[ -t 2 ] && export TTY=1
|
||||
|
||||
die() {
|
||||
[ -n "$QUIET" ] || echo "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
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.
|
||||
# What about pcap?
|
||||
|
||||
local filter_method=$(uci -q get snort.snort.method)
|
||||
if [ "$filter_method" = "afpacket" ]; then
|
||||
local wan=$(uci get snort.snort.interface)
|
||||
if [ -n "$wan" ] && ethtool -k "$wan" | grep -q -E '(tcp-segmentation-offload|receive-offload): on' ; then
|
||||
ethtool -K "$wan" gro off lro off tso off 2> /dev/null
|
||||
log "Disabled gro, lro and tso on '$wan' using ethtool."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
nft_rm_table() {
|
||||
for table_type in 'inet' 'netdev'; do
|
||||
nft list tables | grep -q "${table_type} snort" && nft delete table "${table_type}" snort
|
||||
done
|
||||
}
|
||||
|
||||
nft_add_table() {
|
||||
if [ "$(uci -q get snort.snort.method)" = "nfq" ]; then
|
||||
print nftables | nft $VERBOSE -f $STDIN
|
||||
[ -n "$VERBOSE" ] && nft list table inet snort
|
||||
fi
|
||||
}
|
||||
|
||||
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.
|
||||
nft_rm_table
|
||||
print snort > "$CONF"
|
||||
nft_add_table
|
||||
echo "$CONF"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
# Merely cleans up after.
|
||||
nft_rm_table
|
||||
[ -e "$CONF" ] && rm "$CONF"
|
||||
}
|
||||
|
||||
update_rules() {
|
||||
/usr/bin/snort-rules $TESTING
|
||||
}
|
||||
|
||||
print() {
|
||||
# '$1' is file type to generate, one of:
|
||||
# config, snort or nftables
|
||||
TYPE=$1 utpl -S "$MAIN"
|
||||
}
|
||||
|
||||
check() {
|
||||
local manual=$(uci get snort.snort.manual)
|
||||
[ "$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
|
||||
fi
|
||||
die "Errors in snort config tests."
|
||||
}
|
||||
|
||||
report() {
|
||||
# Reported IPs have source port stripped, but destination port (if any)
|
||||
# retained.
|
||||
#
|
||||
# json notes
|
||||
# from alert_fast:
|
||||
# 08/30-11:39:57.639021 [**] [1:382:11] "PROTOCOL-ICMP PING Windows" [**] [Classification: Misc activity] [Priority: 3] {ICMP} 10.1.1.186 -> 10.1.1.20
|
||||
#
|
||||
# same event in alert_json (single line broken for clarity):
|
||||
# { "timestamp" : "08/30-11:39:57.639021", "pkt_num" : 5366, "proto" : "ICMP", "pkt_gen" : "raw",
|
||||
# "pkt_len" : 60, "dir" : "C2S", "src_ap" : "10.1.1.186:0", "dst_ap" : "10.1.1.20:0",
|
||||
# "rule" : "1:382:11", "action" : "allow" }
|
||||
#
|
||||
# Second part of "rule", 382, is "sid" in ruleset, suffixing 11 is "rev".
|
||||
# grep '\bsid:382\b' /etc/snort/rules/snort.rules (again, single line broken for clarity):
|
||||
# alert icmp $EXTERNAL_NET any -> $HOME_NET any ( msg:"PROTOCOL-ICMP PING Windows";
|
||||
# itype:8; content:"abcdefghijklmnop",depth 16; metadata:ruleset community;
|
||||
# classtype:misc-activity; sid:382; rev:11; )
|
||||
#
|
||||
# Not sure where the prefixing 1 comes from.
|
||||
|
||||
local logging=$(uci get snort.snort.logging)
|
||||
local log_dir=$(uci get snort.snort.log_dir)
|
||||
local pattern="$1"
|
||||
|
||||
if [ "$logging" = 0 ]; then
|
||||
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.
|
||||
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"
|
||||
n_incidents="$(wc -l < $tmp)"
|
||||
lines=$(sort "$tmp" | uniq -c | sort -nr \
|
||||
| awk -F'#' '{printf "%-80s %-12s -> %s\n", $1, $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."
|
||||
printf "%7d total incidents\n" "$n_incidents"
|
||||
rm "$tmp"
|
||||
}
|
||||
|
||||
status() {
|
||||
echo 'tbd'
|
||||
}
|
||||
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
case "$1" in
|
||||
-q)
|
||||
export QUIET=1
|
||||
shift
|
||||
;;
|
||||
-v)
|
||||
export VERBOSE=-e
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
TESTING=-t
|
||||
shift
|
||||
;;
|
||||
-n)
|
||||
NLINES="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$1" in
|
||||
setup)
|
||||
setup
|
||||
;;
|
||||
teardown)
|
||||
teardown
|
||||
;;
|
||||
resetup)
|
||||
QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting."
|
||||
teardown
|
||||
setup
|
||||
;;
|
||||
update-rules)
|
||||
update_rules
|
||||
;;
|
||||
check)
|
||||
check
|
||||
;;
|
||||
print)
|
||||
print "$2"
|
||||
;;
|
||||
report)
|
||||
report "$2"
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
*)
|
||||
cat <<USAGE
|
||||
Usage:
|
||||
|
||||
-n = show only NLINES of output
|
||||
-q = quiet
|
||||
-v = verbose
|
||||
-t = testing mode
|
||||
|
||||
$0 [-v] [-q] setup|teardown|resetup
|
||||
|
||||
Normally only used internally by init scripts to manage the generation
|
||||
of configuration files and any needed firewall rules. None of these
|
||||
modify the snort rules in any way (see 'update-rules').
|
||||
setup = generates snort config, sets up firewall.
|
||||
teardown = removes any firewall rules.
|
||||
resetup = shorthand for teardown and then setup.
|
||||
|
||||
|
||||
$0 [-n lines] report [pattern]
|
||||
|
||||
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.
|
||||
|
||||
$0 [-t] update-rules
|
||||
|
||||
Download and install the snort ruleset. Testing mode generates a canned
|
||||
rule that matches IPv4 ping requests. A typical test scenario might look
|
||||
like:
|
||||
|
||||
> snort-mgr -t update-rules
|
||||
> /etc/init.d/snort start
|
||||
> ping -c4 8.8.8.8
|
||||
> logread -e "TEST ALERT"
|
||||
|
||||
|
||||
$0 print config|snort|nftables
|
||||
|
||||
Print the rendered file contents.
|
||||
config = Display contents of /etc/config/snort, but with all values and
|
||||
descriptions. Missing values shown with defaults.
|
||||
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.
|
||||
|
||||
|
||||
$0 [-q] check
|
||||
|
||||
Test the rendered config using snort's check mode without
|
||||
applying it to the running system.
|
||||
|
||||
|
||||
$0 status
|
||||
|
||||
Print the nfq counter values and blah blah blah
|
||||
|
||||
USAGE
|
||||
;;
|
||||
esac
|
92
net/snort3/files/snort-rules
Normal file
92
net/snort3/files/snort-rules
Normal file
|
@ -0,0 +1,92 @@
|
|||
#!/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
|
||||
|
||||
alias log='logger -s -t "snort-rules[$$]" -p "info"'
|
||||
|
||||
[ "$1" = "-t" ] && testing=true || testing=false
|
||||
|
||||
download_rules() {
|
||||
# Further information:
|
||||
# https://www.snort.org/products#rule_subscriptions
|
||||
# https://www.snort.org/oinkcodes
|
||||
#
|
||||
# Also, what to do about "subscription" vs Talos_LightSPD rules when subbed?
|
||||
# Add a "use_rules" list or option or something?
|
||||
oinkcode=$(uci -q get snort.snort.oinkcode)
|
||||
|
||||
|
||||
|
||||
local conf_dir=$(uci -q get snort.snort.config_dir || echo "/etc/snort")
|
||||
local rules_file="$conf_dir/rules/snort.rules"
|
||||
local data_dir=$(uci -q get snort.snort.temp_dir || echo "/var/snort.d")
|
||||
local data_tar="$data_dir/rules.tar.gz"
|
||||
|
||||
# Make sure everything exists.
|
||||
[ -d "$data_dir" ] || mkdir -p "$data_dir"
|
||||
|
||||
|
||||
if $testing ; then
|
||||
log "Generating testing rules..."
|
||||
new_rules="$data_dir/testing.rules"
|
||||
rm -f "$new_rules"
|
||||
echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v4"; icode:0; itype: 8; sid:10000010; rev:001;)' >> "$new_rules"
|
||||
#echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:33; sid:10000011; rev:001;)' >> "$new_rules"
|
||||
#echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:34; sid:10000012; rev:001;)' >> "$new_rules"
|
||||
|
||||
else
|
||||
if [ -z "$oinkcode" ]; then
|
||||
# If you do not have a subscription, then we use the community rules:
|
||||
log "Downloading community rules..."
|
||||
url="https://www.snort.org/downloads/community/snort3-community-rules.tar.gz"
|
||||
|
||||
else
|
||||
# If you have a subscription and its corresponding oinkcode, use this:
|
||||
#
|
||||
# 'snortver' is the version number of the snort executable in use on your
|
||||
# router.
|
||||
#
|
||||
# Ideally, the 'snort --version' output would work, but OpenWrt builds
|
||||
# are often between (or, more likely, newer than) those listed on the
|
||||
# snort.org downloads page.
|
||||
#
|
||||
# So instead, we define it manually to be the value just before the
|
||||
# installed version. Look on https://www.snort.org/advisories/ and
|
||||
# select the most recent date. On that page, find the closest version
|
||||
# number preceding your installed version and modify the hard-coded
|
||||
# value below (for example, installed is 31600 then use 31470):
|
||||
|
||||
#snortver=$(snort --version | awk '/Version/ {print gensub("\\.", "", "", $NF)}')
|
||||
snortver=31470
|
||||
|
||||
log "Downloading subscription rules..."
|
||||
url="https://www.snort.org/rules/snortrules-snapshot-$snortver.tar.gz?oinkcode=$oinkcode"
|
||||
fi
|
||||
|
||||
wget "$url" -O "$data_tar" 2>&1 | log || exit 1
|
||||
|
||||
# ??? Does non-community tar contain just the one "*.rules" file, too???
|
||||
new_rules=$(tar tzf "$data_tar" | grep '\.rules$')
|
||||
new_rules="$data_dir/$new_rules"
|
||||
|
||||
old_rules="$data_dir/old.rules"
|
||||
if [ -e "$new_rules" ]; then
|
||||
# Before we overwrite with the new download.
|
||||
log "Stashing old rules to $old_rules ..."
|
||||
mv -f "$new_rules" "$old_rules"
|
||||
fi
|
||||
|
||||
log "Unpacking $data_tar ..."
|
||||
tar xzvf "$data_tar" -C "$data_dir" | log || exit 1
|
||||
if [ -e "$old_rules" ] && ! cmp -s "$new_rules" "$old_rules" ; then
|
||||
diff "$new_rules" "$old_rules" 2>&1 | log
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$rules_file"
|
||||
ln -s "$new_rules" "$rules_file"
|
||||
|
||||
log "Snort rules loaded, restart snort now."
|
||||
}
|
||||
download_rules
|
|
@ -1,3 +1,74 @@
|
|||
#
|
||||
# This is not an exhaustive list of configuration items, just those that
|
||||
# require more explanation than is given in the tables that define them, below.
|
||||
#
|
||||
# https://openwrt.org/docs/guide-user/services/snort
|
||||
#
|
||||
# snort
|
||||
# manual - When set to 1, use manual configuration for legacy behavior.
|
||||
# When disabled, then use this config.
|
||||
# interface - Default should usually be 'uci get network.wan.device',
|
||||
# something like 'eth0'
|
||||
# home_net - IP range/ranges to protect. May be 'any', but more likely it's
|
||||
# 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
|
||||
# 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
|
||||
# temp_dir - Location of all transient snort config, including downloaded rules
|
||||
# Default /var/snort.d
|
||||
# logging - Enable external logging of events thus enabling 'snort-mgr report',
|
||||
# otherwise events only go to system log (i.e., 'logread -e snort:')
|
||||
# log_dir - Location of the generated logs, and oh-by-the-way the snort
|
||||
# PID file (why?). Default /var/log
|
||||
# openappid - Enabled inspection using the 'openappid' package
|
||||
# See 'opkg info openappid'
|
||||
# action - 'alert', 'block', 'reject' or 'drop'
|
||||
# method - 'pcap', 'afpacket' or 'nfq'
|
||||
# snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
|
||||
#
|
||||
# nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
|
||||
# queue_maxlen - nfq's '--daq-var queue_maxlen=int'
|
||||
# queue_count - Count of queues to use when method=nfq, usually 2-8
|
||||
# fanout_type - Sets kernel load balancing algorithm*, one of hash, lb, cpu,
|
||||
# rollover, rnd, qm.
|
||||
# thread_count - int snort.-z: <count> maximum number of packet threads
|
||||
# (same as --max-packet-threads); 0 gets the number of
|
||||
# CPU cores reported by the system; default is 1 { 0:max32 }
|
||||
# chain_type - Chain type when generating nft output
|
||||
# chain_priority - Chain priority when generating nft output
|
||||
# include - Full path to user-defined extra rules to include inside queue chain
|
||||
#
|
||||
# * - for details on fanout_type, see these pages:
|
||||
# https://github.com/florincoras/daq/blob/master/README
|
||||
# https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
#
|
||||
config snort 'snort'
|
||||
option config_dir '/etc/snort/'
|
||||
option interface 'eth0'
|
||||
option enabled '0' # one of [0, 1]
|
||||
option manual '1' # one of [0, 1]
|
||||
option oinkcode '' # a string
|
||||
option home_net '192.168.1.0/24' # a string
|
||||
option external_net 'any' # a string
|
||||
option config_dir '/etc/snort' # a path string
|
||||
option temp_dir '/var/snort.d' # a path string
|
||||
option log_dir '/var/log' # a path string
|
||||
option logging '1' # one of [0, 1]
|
||||
option openappid '0' # one of [0, 1]
|
||||
option mode 'ids' # one of [ids, ips]
|
||||
option method 'pcap' # one of [pcap, afpacket, nfq]
|
||||
option action 'alert' # one of [alert, block, drop, reject]
|
||||
option interface 'eth0' # a string
|
||||
option snaplen '1518' # 1518 <= x <= 65535
|
||||
|
||||
config nfq 'nfq'
|
||||
option queue_count '4' # 1 <= x <= 16
|
||||
option queue_start '4' # 1 <= x <= 32768
|
||||
option queue_maxlen '1024' # 1024 <= x <= 65536
|
||||
option fanout_type 'hash' # one of [hash, lb, cpu, rollover, rnd, qm]
|
||||
option thread_count '0' # 0 <= x <= 32
|
||||
option chain_type 'input' # one of [prerouting, input, forward, output, postrouting]
|
||||
option chain_priority 'filter' # one of [raw, filter, 300]
|
||||
option include '' # a path string
|
||||
|
||||
|
|
|
@ -1,36 +1,58 @@
|
|||
#!/bin/sh /etc/rc.common
|
||||
# shellcheck disable=SC2039 # "local" not defined in POSIX sh
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/snort
|
||||
MGR=/usr/bin/snort-mgr
|
||||
|
||||
validate_snort_section() {
|
||||
$MGR -q check || return 1
|
||||
uci_validate_section snort snort "${1}" \
|
||||
'enabled:bool:0' \
|
||||
'manual:bool:1' \
|
||||
'config_dir:string' \
|
||||
'interface:string'
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local config_file interface
|
||||
# If you wish to use application-managed PID file:
|
||||
# output.logdir, in the snort lua config, determines the PID file location.
|
||||
# Add '--create-pidfile' to the 'command', below.
|
||||
|
||||
validate_snort_section snort || {
|
||||
echo "validation failed"
|
||||
return 1
|
||||
}
|
||||
local enabled
|
||||
local manual
|
||||
local config_dir
|
||||
local interface
|
||||
|
||||
validate_snort_section snort || {
|
||||
echo "Validation failed, try 'snort-mgr check'."
|
||||
return 1
|
||||
}
|
||||
|
||||
[ "$enabled" = 0 ] && return
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command $PROG -q -i "$interface" -c "${config_dir%/}/snort.lua" --tweaks local
|
||||
procd_set_param env SNORT_LUA_PATH="$config_dir"
|
||||
procd_set_param file $CONFIGFILE
|
||||
if [ "$manual" = 0 ]; then
|
||||
local config_file=$($MGR setup)
|
||||
procd_set_param command "$PROG" -q -c "${config_file}"
|
||||
else
|
||||
procd_set_param command $PROG -q -i "$interface" -c "${config_dir%/}/snort.lua" --tweaks local
|
||||
procd_set_param env SNORT_LUA_PATH="$config_dir"
|
||||
procd_set_param file $CONFIGFILE
|
||||
fi
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
stop_service()
|
||||
{
|
||||
service_stop ${PROG}
|
||||
service_stop "$PROG"
|
||||
$MGR teardown
|
||||
}
|
||||
|
||||
service_triggers()
|
||||
|
|
126
net/snort3/files/snort.uc
Normal file
126
net/snort3/files/snort.uc
Normal file
|
@ -0,0 +1,126 @@
|
|||
{%
|
||||
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
// Create some snort-format-specific items.
|
||||
|
||||
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 inputs = null;
|
||||
let vars = null;
|
||||
switch (snort.method) {
|
||||
case "pcap":
|
||||
case "afpacket":
|
||||
inputs = `{ '${snort.interface}' }`;
|
||||
vars = "{}";
|
||||
break;
|
||||
|
||||
case "nfq":
|
||||
inputs = "{ ";
|
||||
for (let i = int(nfq.queue_start); i < int(nfq.queue_start)+int(nfq.queue_count); i++) {
|
||||
inputs += `'${i}', `
|
||||
}
|
||||
inputs += "}";
|
||||
|
||||
vars = `{ 'device=${snort.interface}', 'queue_maxlen=${nfq.queue_maxlen}', 'fanout_type=${nfq.fanout_type}', 'fail_open', }`;
|
||||
break;
|
||||
}
|
||||
-%}
|
||||
-- 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 }}'
|
||||
|
||||
include('{{ snort.config_dir }}/snort.lua')
|
||||
|
||||
snort = {
|
||||
{% if (snort.mode == 'ips'): %}
|
||||
['-Q'] = true,
|
||||
{% endif %}
|
||||
['--daq'] = {{ snort.method }},
|
||||
--['--daq-dir'] = '/usr/lib/daq/',
|
||||
{% if (snort.method == 'nfq'): %}
|
||||
['--max-packet-threads'] = {{ nfq.thread_count }},
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
ips = {
|
||||
mode = {{ line_mode }},
|
||||
variables = default_variables,
|
||||
action_override = {{ snort.action }},
|
||||
include = "{{ snort.config_dir }}/" .. RULE_PATH .. '/snort.rules',
|
||||
}
|
||||
|
||||
daq = {
|
||||
inputs = {{ inputs }},
|
||||
snaplen = {{ snort.snaplen }},
|
||||
module_dirs = { '/usr/lib/daq/', },
|
||||
modules = {
|
||||
{
|
||||
name = '{{ snort.method }}',
|
||||
mode = {{ line_mode }},
|
||||
variables = {{ vars }},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert_syslog = {
|
||||
level = 'info',
|
||||
}
|
||||
|
||||
{% if (int(snort.logging)): %}
|
||||
-- Note that this is also the location of the PID file, if you use it.
|
||||
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
|
||||
-- int alert_fast.limit = 0: set maximum size in MB before rollover (0 is unlimited) { 0:maxSZ }
|
||||
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
|
||||
file = true,
|
||||
}
|
||||
|
||||
{% endif -%}
|
||||
|
||||
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, 'opkg install openappid' and enable in config.
|
||||
{% if (int(snort.openappid)): %}
|
||||
appid = {
|
||||
log_stats = true,
|
||||
app_detector_dir = '/usr/lib/openappid',
|
||||
app_stats_period = 60,
|
||||
}
|
||||
{% endif %}
|
Loading…
Reference in a new issue