openwrt-packages/net/snort3/files/main.uc

282 lines
10 KiB
Ucode

{%
//------------------------------------------------------------------------------
// Copyright (c) 2023-2024 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.
//
//------------------------------------------------------------------------------
QUIET; // Reference globals passed from CLI, so we get errors when missing.
TYPE;
import { cursor } from 'uci';
let uci = cursor();
function wrn(fmt, ...args) {
if (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 rpad(str, fill, len)
{
str = rtrim(str) + ' ';
while (length(str) < len) {
str += fill;
}
return str;
}
//------------------------------------------------------------------------------
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", [ "default", "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 = {
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 '!$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
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 - Override the specified action of your rules. One of 'default',
'alert', 'block', 'reject' or 'drop', where 'default' means use
the rule as defined and don't override.
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'
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, rpad });
}
function render_nftables() {
include("templates/nftables.uc", { snort, nfq, rpad });
}
function render_config() {
snort_config_doc("#");
dump_config(snort);
dump_config(nfq);
}
function render_help() {
snort_config_doc();
}
//------------------------------------------------------------------------------
load_all();
let table_type = TYPE; // Supply on cli with '-D TYPE=snort'...
switch (table_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 '${table_type}', should be one of snort, nftables, config, help.\n`);
return;
}
//------------------------------------------------------------------------------
-%}