add firewallgen package, which creates an nft script

This commit is contained in:
Daniel Barlow 2023-06-18 17:40:16 +01:00
parent 994cca671b
commit 80639a7256
4 changed files with 184 additions and 0 deletions

View file

@ -53,4 +53,5 @@
min-collect-garbage = callPackage ./min-collect-garbage {}; min-collect-garbage = callPackage ./min-collect-garbage {};
min-copy-closure = callPackage ./min-copy-closure {}; min-copy-closure = callPackage ./min-copy-closure {};
hi = callPackage ./hi {}; hi = callPackage ./hi {};
firewallgen = callPackage ./firewallgen {};
} }

View file

@ -0,0 +1,58 @@
{
nftables
, writeScript
, lib
} :
name : ruleset :
let
inherit (lib.strings) concatStringsSep splitString hasInfix substring;
inherit (lib.lists) groupBy;
inherit (lib.attrsets) mapAttrsToList nameValuePair;
inherit (builtins) map listToAttrs replaceStrings head tail;
indentLines = offset : lines :
if lines == []
then ""
else
let
line = head lines;
isOpen = hasInfix "{" line;
isClose = hasInfix "}" line;
offset' = offset +
(if isOpen then 4 else 0) +
(if isClose then -4 else 0);
padding = offset: substring 0 offset " ";
in
if (isClose && !isOpen)
then
(padding offset') + line + "\n" + indentLines offset' (tail lines)
else
(padding offset) + line + "\n" + indentLines offset' (tail lines);
indent = text : indentLines 0 (splitString "\n" text);
dochain = { name, type, family, rules, policy ? null, hook ? null } : ''
chain ${name} {
${if hook != null
then "type ${type} hook ${hook}; policy ${policy};"
else ""
}
${concatStringsSep "\n" rules}
}
'';
dotable = family : chains : ''
table ${family} ${family} {
${concatStringsSep "\n" (map dochain chains)}
}
'';
categorise = chains :
groupBy
({ family, ... } : family)
(mapAttrsToList (n : v : v // { name = n; }) chains);
in writeScript name ''
#!${nftables}/sbin/nft -cf
flush ruleset
${indent (concatStringsSep "\n" (mapAttrsToList dotable (categorise ruleset)))}
''

View file

@ -0,0 +1,121 @@
let
drop = expr : "${expr} drop";
accept = expr : "${expr} accept";
mcast-scope = 8;
allow-incoming = true;
bogons-ip6 = {
type = "filter";
family = "ip6";
rules = [
(drop "saddr ff00::/8") # multicast saddr is illegal
(drop "saddr ::/128") # unspecified address
(drop "daddr ::/128")
(drop "saddr 2001:db8::/32") # documentation addresses
(drop "daddr 2001:db8::/32")
# I think this means "check FIB for (saddr, iif) to see if we
# could route a packet to that address using that interface",
# and if we can't then it was an inapproppriate source address
# for packets received _from_ said interface
(drop "fib saddr . iif oif eq 0")
(drop "icmpv6 type router-renumbering")
(drop "icmpv6 type 139") # Node Information Query
(drop "icmpv6 type 140") # Node Information Response
(drop "icmpv6 type 100")
(drop "icmpv6 type 101")
(drop "icmpv6 type 200")
(drop "icmpv6 type 201")
(drop "icmpv6 type 127")
(drop "icmpv6 type 255")
(drop "icmpv6 type destination-unreachable ct state invalid,untracked")
];
};
forward-ip6 = {
type = "filter";
family = "ip6";
policy = "drop";
hook = "forward";
rules = [
"jump bogons-ip6"
(drop "saddr ::1/128") # loopback address [RFC4291]
(drop "daddr ::1/128")
(drop "saddr ::FFFF:0:0/96")# IPv4-mapped addresses
(drop "daddr ::FFFF:0:0/96")
(drop "saddr fe80::/10") # link-local unicast
(drop "daddr fe80::/10")
(drop "saddr fc00::/7") # unique-local addresses
(drop "daddr fc00::/7")
(drop "saddr 2001:10::/28") # ORCHID [RFC4843].
(drop "daddr 2001:10::/28")
(drop "saddr fc00::/7") # unique local source
(drop "daddr fc00::/7") # and/or dst addresses [RFC4193]
# multicast with wrong scopes
(drop
# dest addr first byte 0xff, low nibble of second byte <= scope
# https://www.mankier.com/8/nft#Payload_Expressions-Raw_Payload_Expression
"@nh,192,8 eq 0xff @nh,204,4 le ${toString mcast-scope})")
(accept "oifname \"int\" iifname \"ppp0\" meta l4proto udp ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" meta l4proto udp")
(accept "icmpv6")
(accept "ah")
(accept "esp")
(accept "udp port 500") # IKE Protocol [RFC5996]. haha zyxel
(accept "ip6 nexthdr hip")
## FIXME no support yet for recs 27-30 Mobility Header
(accept "oifname \"int\" iifname \"ppp0\" meta l4proto tcp ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" meta l4proto tcp")
(accept "oifname \"int\" iifname \"ppp0\" meta l4proto sctp ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" meta l4proto sctp")
(accept "oifname \"int\" iifname \"ppp0\" meta l4proto dccp ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" meta l4proto dccp")
# we can allow all reasonable inbound, or we can use an explicit
# allowlist to enumerate the endpoints that are allowed to
# accept inbound from the WAN
(if allow-incoming
then accept "oifname \"int\" iifname \"ppp0\""
else { rule = "oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip6"; }
)
# allow all outbound and any inbound that's part of a
# recognised (outbound-initiated) flow
(accept "oifname \"int\" iifname \"ppp0\" ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" ")
];
};
input-ip6 = {
type = "filter";
family = "ip6";
policy = "drop";
hook = "input";
rules = [
"jump bogons-ip6"
(accept "icmpv6")
(if allow-incoming
then accept "oifname \"int\" iifname \"ppp0\""
else { rule = "oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip6"; }
)
(accept "oifname \"int\" iifname \"ppp0\" ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" ")
];
};
incoming-allowed-ip6 = {
type = "filter";
family = "ip6";
rules = [
"oifname \"int\" tcp port 22 daddr loaclhost.lan"
];
};
in {
inherit input-ip6 forward-ip6 bogons-ip6 incoming-allowed-ip6;
}

View file

@ -0,0 +1,4 @@
let
pkgs = import <nixpkgs> { overlays = [( import ../../overlay.nix)]; };
ruleset = import ./test-rules-min.nix;
in pkgs.firewallgen "firewall.nft" ruleset