add firewallgen package, which creates an nft script
This commit is contained in:
parent
994cca671b
commit
80639a7256
4 changed files with 184 additions and 0 deletions
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
|
58
pkgs/firewallgen/default.nix
Normal file
58
pkgs/firewallgen/default.nix
Normal 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)))}
|
||||||
|
''
|
121
pkgs/firewallgen/test-rules-min.nix
Normal file
121
pkgs/firewallgen/test-rules-min.nix
Normal 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;
|
||||||
|
}
|
4
pkgs/firewallgen/test.nix
Normal file
4
pkgs/firewallgen/test.nix
Normal 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
|
Loading…
Reference in a new issue