From c92aacc6fd4267d926e520d8c782b9399102c3d3 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 3 Feb 2025 19:51:08 +0000 Subject: [PATCH 1/7] firewall rules: use @lan and @wan sets instead of ifnames we don't have anything yet to create or populate the sets --- modules/firewall/default-rules.nix | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/modules/firewall/default-rules.nix b/modules/firewall/default-rules.nix index 54c68be..dcb62aa 100644 --- a/modules/firewall/default-rules.nix +++ b/modules/firewall/default-rules.nix @@ -62,8 +62,8 @@ in { # 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 "oifname @lan iifname @wan meta l4proto udp ct state established,related") + (accept "iifname @lan oifname @wan meta l4proto udp") (accept "meta l4proto icmpv6") (accept "meta l4proto ah") @@ -71,31 +71,31 @@ in { # does this ever get used or does the preceding general udp accept # already grab anything that might get here? - (accept "oifname \"ppp0\" udp dport 500") # IKE Protocol [RFC5996]. haha zyxel + (accept "oifname @wan udp dport 500") # IKE Protocol [RFC5996]. haha zyxel (accept "ip6 nexthdr 139") # Host Identity Protocol ## 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 @lan iifname @wan meta l4proto tcp ct state established,related") + (accept "iifname @lan oifname @wan 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 @lan iifname @wan meta l4proto sctp ct state established,related") + (accept "iifname @lan oifname @wan meta l4proto sctp") - (accept "oifname \"int\" iifname \"ppp0\" meta l4proto dccp ct state established,related") - (accept "iifname \"int\" oifname \"ppp0\" meta l4proto dccp") + (accept "oifname @lan iifname @wan meta l4proto dccp ct state established,related") + (accept "iifname @lan oifname @wan 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 "iifname \"ppp0\" jump incoming-allowed-ip6" + then accept "oifname @lan iifname @wan" + else "iifname @wan 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\" ") + (accept "oifname @lan iifname @wan ct state established,related") + (accept "iifname @lan oifname @wan ") "log prefix \"DENIED CHAIN=forward-ip6 \"" ]; @@ -128,15 +128,15 @@ in { hook = "input"; rules = [ (accept "meta l4proto icmpv6") - "iifname int jump input-ip6-lan" - "iifname ppp0 jump input-ip6-wan" + "iifname @lan jump input-ip6-lan" + "iifname @wan jump input-ip6-wan" (if allow-incoming - then accept "iifname \"ppp0\"" - else "iifname \"ppp0\" jump incoming-allowed-ip6" + then accept "iifname @wan" + else "iifname @wan jump incoming-allowed-ip6" ) # how does this even make sense in an input chain? - (accept "iifname \"ppp0\" ct state established,related") - (accept "iifname \"int\" ") + (accept "iifname @wan ct state established,related") + (accept "iifname @lan ") "log prefix \"DENIED CHAIN=input-ip6 \"" ]; }; @@ -146,7 +146,7 @@ in { family = "ip6"; rules = [ # this is where you put permitted incoming connections - # "oifname \"int\" ip6 daddr 2001:8b0:de3a:40de::e9d tcp dport 22" + # "oifname @lan ip6 daddr 2001:8b0:de3a:40de::e9d tcp dport 22" ]; }; @@ -157,7 +157,7 @@ in { policy = "accept"; family = "ip"; rules = [ - "oifname \"ppp0\" masquerade" + "oifname @wan masquerade" ]; }; @@ -208,9 +208,9 @@ in { rules = [ "iifname lo accept" "icmp type { echo-request, echo-reply } accept" - "iifname int jump input-ip4-lan" - "iifname ppp0 jump input-ip4-wan" - "iifname ppp0 jump incoming-allowed-ip4" + "iifname @lan jump input-ip4-lan" + "iifname @wan jump input-ip4-wan" + "iifname @wan jump incoming-allowed-ip4" "ct state established,related accept" "log prefix \"DENIED CHAIN=input-ip4 \"" ]; @@ -222,9 +222,9 @@ in { policy = "drop"; hook = "forward"; rules = [ - "iifname \"int\" accept" + "iifname @lan accept" "ct state established,related accept" - "oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip4" + "oifname @lan iifname @wan jump incoming-allowed-ip4" "log prefix \"DENIED CHAIN=forward-ip4 \"" ]; }; From 8cf602da91cc49acd766f006dbd1029505f83d7c Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 3 Feb 2025 19:52:35 +0000 Subject: [PATCH 2/7] think --- THOUGHTS.txt | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/THOUGHTS.txt b/THOUGHTS.txt index bfd5074..04503b2 100644 --- a/THOUGHTS.txt +++ b/THOUGHTS.txt @@ -6834,3 +6834,189 @@ looking through the openwrt config changes ... Sun Jan 5 12:58:52 GMT 2025 We are running with rt3200 and everything appears to work :-) + +Sun Jan 5 20:34:18 GMT 2025 + +what customization do we want from the firewall? + + - what's allowed wan->lan + - what's allowed lan->wan + - which dropped packets get logged or don't + +plus fix whatever it was RoS found +plus stop hardcoding the interface names + + Q: if pppd _makes_ an interface, how do we know what the name of it + is going to be before it's up and passing packets so that we can + have the firewall active before it starts + + or could we have ppp service install a "drop everything" firewall + before it starts? what if there's more than one upstream interface, + they shouldn't wipe each other out + + so can we have a "default deny" firewall in which every allowed flow + is qualified by the interface name, and any service that brings up an + interface is required to add firewall rules for it according to its + role + + or maybe the firewall service could watch for interfaces being added + (and removed) and update the ruleset as appropriate for the interface + role (lan, wan, dmz, management, guest, ???). but how does it know + the role based on the interface name? + +Tue Jan 14 19:33:28 GMT 2025 + +each interface can add its own chain when it comes up, and then we can +figure out some way to jump to the correct chain (vmaps) based on +interface name + +% nft add map filter mydict { type ipv4_addr : verdict\; } +% nft add element filter mydict { 192.168.0.10 : drop, 192.168.0.11 : accept } +% nft add rule filter input ip saddr vmap @mydict + +_but_ we might be better off declaring static "zones" (lan, world, +dmz, guest, etc etc) with a map for each, and then replace the hardcoded +interface names with a map lookup + +% nft add map filter ifzone { type ifname : string ; } + + +zones { type ifname: ipv4_addr\; } + ... and presumably we also need identical maps for nat and + ... any other chain type where we need to distinguish + ... inside from outside +% nft add element nat porttoip { 80 : 192.168.1.100, 8888 : 192.168.1.101 } + +% nft add rule ip nat postrouting snat to tcp dport map @porttoip + +Mon Jan 20 20:32:58 GMT 2025 + +1) maybe we can add a type="chain" or type="set" attribute to the +firewallgen input, then we cna have sets in the default firewall rules + +2) then we convert the default firewall rules to use sets instead of +hardcoding ifname + +3) then find a nice place to hook "new interface is available" and +add it to the appropriate zone (separately for ip4 and ip6, gah) + +4) and how do we find + +# nft add element ip table-ip lan { int } +# nft add element ip table-ip wan { ppp0 } +# nft add element ip6 table-ip6 lan { int } +# nft add element ip6 table-ip6 wan { ppp0 } + +Tue Jan 28 21:51:46 GMT 2025 + +Going back and forth on the firewall stuff, in respect of where to aim +for with "general" vs "useful". Specifically, if we have hooks +every time interfaces are added/removed that +expect some specific sets to (a) exist and (b) have particular +semantics, there is necessary coupling between those hooks and the +firewall definition. So, practically speaking, the "a new interface +appears" rules need to be bundled with the firewall ruleset + +Which also means that the firewall needs to know which zone the +interface is assigned to, which is a problem if it can't tell from the +name (for example wg0 wg1 wg2 ... could be wan or lan or dmz or +anything) + +So the service that owns the interface needs to communicate "another +one for the lan zone" to the firewall and it can't do that by adding +to sets directly unless it knows what the sets are called. Implying we +need an interface between the interface service that knows "new +interface ppp7 added in wan zone" and the firewall service that knows +how to accommodate this. For extra credit this actually should be more +like pubsub: the interface shouldn't really have to know the firewall +exists. + +outputs? + +- what if each interface wrote a "zone" output and the firewall +subscribed to them? would need the firewall to know which of all the +available services were interfaces + +- a zone service that every interface in the zone depends on. it +doesn't do much in itself but it means the interface updown scripts +know a service directory where they can touch lan-zone/eth0 or +whatever. This could work. The firewall service definition specifies +the zone services and uses inotify watcher thingies to update +interface sets when contents change. + +Wed Jan 29 17:19:24 GMT 2025 + +1) make a zone service defn that can be instantiated for each zone. +it should create $output/interfaces + +2) add a `zone` attribute to interface definitions, causing +- the zone service to be added to the dependencies +- the interface "up" script to include writing to the zone/interfaces output + +2b) any other service that creates an interface (e.g. ppp) needs to also +have `zone` and do the same + +3) firewallgen to be able to make sets + +4) firewall service to watch the zone outputs + +Fri Jan 31 17:11:16 GMT 2025 + +Do we need zone services? I think we could put zones in the outputs of +the firewall service? + +Sun Feb 2 20:59:56 GMT 2025 + +What's the smallest first step? + + - [done] how can we make firewallgen output sets (or could we + make the firewall service tack them on afterwards) + + - make a longrun that watches its own zones output and updates the + appropriate sets + +The sticking point is that if you give the firewall `rules` instead of +`extraRules` then the longrun may or may not work depending on (1) +whether you made the zone sets; (2) whether your rules use +them. Conclusion: if you supply `rules` then you also have to say +whether you want the longrun or not. So add a param +watchForInterfaceUpdates which defaults true + +Mon Feb 3 21:12:55 GMT 2025 + +the thing that updates sets has to know they exist, so the interface watcher +service must live in the firewall module + +the firewall service defn should return the firewall service after +adding the interface watcher as a dependency of it. Or: the watcher +should make the sets and then the firewall service could depend on _it_. +That would mean that the firewall service would fail if it used sets +that the watcher didn't make, is that good or bad or indifferent? + +the interface services have to know about the watcher as well in order +to write into its outputs, so it can't be hidden inside the module + +maybe the watcher service should _be_ the firewall service. + +we could add a "notify" param to an interface which would be an output +reference to (the firewall service / zones / lan ) that the interface would +write its ifname into when the service is up + +Wed Feb 5 00:14:29 GMT 2025 + +another thought: the firewall service could have params to say +which interface services are in which zones + +we'd have to ensure that the interface services did not end up as +dependencies of the firewall + +then the firewall could + +- create the sets +- watch each interface service for the ifname output and add it to the right zone + +Sun Feb 9 21:33:57 GMT 2025 + +nft update set @lan + +echo 'flush set table-ip lan; add element table-ip lan { eth0,lo }' | nft -f - From 1d780de0f1cc8146d7de55b35c04db2390b14b21 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 3 Feb 2025 20:46:22 +0000 Subject: [PATCH 3/7] add (very basic) set support in firewallgen and add sets for lan/wan/dmz/guest interface names to default firewall rules --- modules/firewall/default-rules.nix | 16 ++++++++++++++++ pkgs/firewallgen/default.nix | 22 ++++++++++++++++++++-- pkgs/firewallgen/test-rules-min.nix | 19 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/modules/firewall/default-rules.nix b/modules/firewall/default-rules.nix index dcb62aa..e8ee4d0 100644 --- a/modules/firewall/default-rules.nix +++ b/modules/firewall/default-rules.nix @@ -3,6 +3,13 @@ let accept = expr : "${expr} accept"; mcast-scope = 8; allow-incoming = false; + + ifname-set = family : name : ifnames : { + kind = "set"; + inherit family name; + type = "ifname"; + elements = ifnames; + }; in { bogons-ip6 = { type = "filter"; @@ -241,4 +248,13 @@ in { ]; }; + lan-set-ip = ifname-set "ip" "lan" [ "int" ]; + wan-set-ip = ifname-set "ip" "wan" [ "ppp0" ]; + dmz-set-ip = ifname-set "ip" "dmz" [ ]; + guest-set-ip = ifname-set "ip" "guest" [ ]; + + lan-set-ip6 = ifname-set "ip6" "lan" [ "int" ]; + wan-set-ip6 = ifname-set "ip6" "wan" [ "ppp0" ]; + dmz-set-ip6 = ifname-set "ip6" "dmz" [ ]; + guest-set-ip6 = ifname-set "ip6" "guest" [ ]; } diff --git a/pkgs/firewallgen/default.nix b/pkgs/firewallgen/default.nix index 51ade67..dc7b907 100644 --- a/pkgs/firewallgen/default.nix +++ b/pkgs/firewallgen/default.nix @@ -43,15 +43,33 @@ let ${concatStringsSep "\n" rules} } ''; + + doset = { name, type, elements ? [], ... } : '' + set ${name} { + type ${type} + ${if elements != [] + then "elements = { ${concatStringsSep ", " elements } }" + else "" + } + } + ''; + + dochainorset = + { kind ? "chain", ... } @ params : + { + chain = dochain; + set = doset; + }.${kind} params; + dotable = family : chains : '' table ${family} table-${family} { - ${concatStringsSep "\n" (map dochain chains)} + ${concatStringsSep "\n" (map dochainorset chains)} } ''; categorise = chains : groupBy ({ family, ... } : family) - (mapAttrsToList (n : v : v // { name = n; }) chains); + (mapAttrsToList (n : v : { name = n; } // v ) chains); in writeScript name '' #!${nftables}/sbin/nft -f diff --git a/pkgs/firewallgen/test-rules-min.nix b/pkgs/firewallgen/test-rules-min.nix index d5fe5de..f4cb160 100644 --- a/pkgs/firewallgen/test-rules-min.nix +++ b/pkgs/firewallgen/test-rules-min.nix @@ -121,4 +121,23 @@ let }; in { inherit input-ip6 forward-ip6 bogons-ip6 incoming-allowed-ip6; + lan-set-ip = { + kind = "set"; + family = "ip"; + type = "ifname"; + elements = [ + "eth0" "eth1" + ]; + + }; + # honours timeout flags gc-interval size policy counter auto-merge + lan-set-ip6 = { + kind = "set"; + family = "ip6"; + type = "ifname"; + elements = [ + "eth0" "eth1" + ]; + + }; } From 65878135776e1a1955b7d6b8887448b5d46df674 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Thu, 6 Feb 2025 11:57:06 +0000 Subject: [PATCH 4/7] WIP add zones to firewall module - zones are an attrset of name -> [interface-service] - the firewall will create empty "ifname" sets for each zone name in each address family (ip, ip6) - then watch the interface services, and add the "ifname" outputs to the corresponding sets when they appear This commit only adds the empty sets --- examples/rotuer.nix | 4 ++++ modules/firewall/default-rules.nix | 16 ---------------- modules/firewall/default.nix | 10 ++++++++++ modules/firewall/service.nix | 28 ++++++++++++++++++++++------ modules/profiles/gateway.nix | 4 ++++ 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/examples/rotuer.nix b/examples/rotuer.nix index 1d2a726..78251e2 100644 --- a/examples/rotuer.nix +++ b/examples/rotuer.nix @@ -69,6 +69,10 @@ in rec { firewall = { enable = true; rules = secrets.firewallRules; + zones = { + lan = [ config.services.int ]; + wan = [ config.services.wan ] ; + }; }; wireless.networks = { # EDIT: if you have more or fewer wireless radios, here is where diff --git a/modules/firewall/default-rules.nix b/modules/firewall/default-rules.nix index e8ee4d0..dcb62aa 100644 --- a/modules/firewall/default-rules.nix +++ b/modules/firewall/default-rules.nix @@ -3,13 +3,6 @@ let accept = expr : "${expr} accept"; mcast-scope = 8; allow-incoming = false; - - ifname-set = family : name : ifnames : { - kind = "set"; - inherit family name; - type = "ifname"; - elements = ifnames; - }; in { bogons-ip6 = { type = "filter"; @@ -248,13 +241,4 @@ in { ]; }; - lan-set-ip = ifname-set "ip" "lan" [ "int" ]; - wan-set-ip = ifname-set "ip" "wan" [ "ppp0" ]; - dmz-set-ip = ifname-set "ip" "dmz" [ ]; - guest-set-ip = ifname-set "ip" "guest" [ ]; - - lan-set-ip6 = ifname-set "ip6" "lan" [ "int" ]; - wan-set-ip6 = ifname-set "ip6" "wan" [ "ppp0" ]; - dmz-set-ip6 = ifname-set "ip6" "dmz" [ ]; - guest-set-ip6 = ifname-set "ip6" "guest" [ ]; } diff --git a/modules/firewall/default.nix b/modules/firewall/default.nix index 539eba5..03ff1d9 100644 --- a/modules/firewall/default.nix +++ b/modules/firewall/default.nix @@ -60,6 +60,16 @@ in description = "firewall ruleset"; default = {}; }; + zones = mkOption { + type = types.attrsOf (types.listOf liminix.lib.types.service); + default = {}; + example = lib.literalExpression '' + { + lan = with config.hardware.networkInterfaces; [ int ]; + wan = [ config.services.ppp0 ]; + } + ''; + }; rules = mkOption { type = types.attrsOf types.attrs; # we could usefully tighten this a bit :-) default = import ./default-rules.nix; diff --git a/modules/firewall/service.nix b/modules/firewall/service.nix index 9bb022f..ca8431e 100644 --- a/modules/firewall/service.nix +++ b/modules/firewall/service.nix @@ -4,12 +4,28 @@ , firewallgen , nftables }: -{ rules, extraRules }: +{ rules, extraRules, zones }: let - inherit (liminix.services) oneshot; - script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules); -in oneshot { + inherit (liminix.services) longrun ; # oneshot; + inherit (lib.attrsets) mapAttrs' nameValuePair; + mkSet = family : name : + nameValuePair + "${name}-set-${family}" + { + kind = "set"; + inherit name family; + type = "ifname"; + }; + sets = (mapAttrs' (n : _ : mkSet "ip" n) zones) // + (mapAttrs' (n : _ : mkSet "ip6" n) zones); + allRules = lib.recursiveUpdate extraRules (lib.recursiveUpdate (builtins.trace sets sets) rules); + script = firewallgen "firewall1.nft" allRules; + +in longrun { name = "firewall"; - up = script; - down = "${nftables}/bin/nft flush ruleset"; + run = '' + ${script} + while : ; do sleep 86400 ; done + ''; + finish = "${nftables}/bin/nft flush ruleset"; } diff --git a/modules/profiles/gateway.nix b/modules/profiles/gateway.nix index ef6eca6..1e0ae48 100644 --- a/modules/profiles/gateway.nix +++ b/modules/profiles/gateway.nix @@ -48,6 +48,9 @@ in { firewall = { enable = mkEnableOption "firewall"; rules = mkOption { type = types.attrsOf types.attrs; }; + zones = mkOption { + type = types.attrsOf (types.listOf liminix.lib.types.service); + }; }; wan = { @@ -143,6 +146,7 @@ in { services.firewall = mkIf cfg.firewall.enable (svc.firewall.build { extraRules = cfg.firewall.rules; + inherit (cfg.firewall) zones; }); services.resolvconf = oneshot rec { From 4bb081ffcf355b7560e5e22763ecffb19f033bb0 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 10 Feb 2025 00:41:01 +0000 Subject: [PATCH 5/7] export anoia.svc:fileno so it can be used with event loops --- pkgs/anoia/svc.fnl | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/anoia/svc.fnl b/pkgs/anoia/svc.fnl index 9156860..7bf2c46 100644 --- a/pkgs/anoia/svc.fnl +++ b/pkgs/anoia/svc.fnl @@ -60,6 +60,7 @@ (write-value (.. directory "/" filename) new-value) (read-value (.. directory "/" filename)))) :close #(watcher:close) + :fileno #(watcher:fileno) : events })) From 7f1712503946767b25bb966f8c807f7cbd3b524f Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 10 Feb 2025 00:42:27 +0000 Subject: [PATCH 6/7] firewall: update zones with interface names as they appear --- modules/firewall/ifwatch.fnl | 63 ++++++++++++++++++++++++++++++++++++ modules/firewall/service.nix | 19 ++++++++--- 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 modules/firewall/ifwatch.fnl diff --git a/modules/firewall/ifwatch.fnl b/modules/firewall/ifwatch.fnl new file mode 100644 index 0000000..eaa8615 --- /dev/null +++ b/modules/firewall/ifwatch.fnl @@ -0,0 +1,63 @@ +(local { : system : join } (require :anoia)) +(local svc (require :anoia.svc)) +(local ll (require :lualinux)) + +;; ifwatch.fnl wan:/nix/store/eee/.outputs/ifname wan:/nix/store/ffff/.outputs/ifname lan:/nix/store/abc123/.outputs/ifname + +(fn parse-options [cmdline] + (let [interfaces {}] + (each [_ s (ipairs cmdline)] + (let [(zone service) (string.match s "(.-):(.+)")] + (tset interfaces (svc.open service) zone))) + interfaces)) + +(local POLLIN 1) +(local POLLHUP 16) + +(fn zone-contents [interfaces] + (accumulate [zones {} + intf zone (pairs interfaces)] + (let [ifs (or (. zones zone) [])] + (table.insert ifs (intf:output "ifname")) + (tset zones zone ifs) + zones))) + +(fn wait-for-change [interfaces] + (let [pollfds (icollect [k _ (pairs interfaces)] + (bor (lshift (k:fileno) 32) + (lshift (bor POLLIN POLLHUP) 16)))] + (ll.poll pollfds))) + +(fn fail [msg] + (io.stderr:write (.. "ERROR: " msg "\n"))) + +(macro with-popen [[handle command mode] & body] + `(let [,handle (assert (io.popen ,command ,mode)) + val# (do ,(unpack body))] + (case (: ,handle :close) + ok# val# + (nil :exit code#) (fail (.. ,command " exited " code#)) + (nil :signal sig#) (fail (.. ,command " killed by " sig#))))) + +(fn update-zone-str [zone ifnames] + (if (> (# ifnames) 0) + (.. + "flush set ip table-ip " zone " ; add element ip table-ip " zone " { " (table.concat ifnames ", ") " };\n" + "flush set ip6 table-ip6 " zone " ; add element ip6 table-ip6 " zone " { " (table.concat ifnames ", ") " };\n" + ) + (.. + "flush set ip table-ip " zone "; \n" + "flush set ip6 table-ip6 " zone "; \n" + ))) + +(fn run [] + (while true + (let [interfaces (parse-options arg)] + (with-popen [nft "nft -f -" :w] + (each [zone ifnames (pairs (zone-contents interfaces))] + (nft:write (update-zone-str zone ifnames)))) + (wait-for-change interfaces) + (each [k _ (pairs interfaces)] + (k:close))))) + +{ : run } diff --git a/modules/firewall/service.nix b/modules/firewall/service.nix index ca8431e..9664ec9 100644 --- a/modules/firewall/service.nix +++ b/modules/firewall/service.nix @@ -3,11 +3,17 @@ , lib , firewallgen , nftables +, writeFennel +, anoia +, lualinux +, linotify }: { rules, extraRules, zones }: let - inherit (liminix.services) longrun ; # oneshot; - inherit (lib.attrsets) mapAttrs' nameValuePair; + inherit (liminix.services) longrun; + inherit (lib.attrsets) mapAttrs' nameValuePair mapAttrsToList; + inherit (lib.strings) concatStringsSep; + inherit (lib.lists) flatten; mkSet = family : name : nameValuePair "${name}-set-${family}" @@ -20,12 +26,17 @@ let (mapAttrs' (n : _ : mkSet "ip6" n) zones); allRules = lib.recursiveUpdate extraRules (lib.recursiveUpdate (builtins.trace sets sets) rules); script = firewallgen "firewall1.nft" allRules; - + ifwatch = writeFennel "ifwatch" { + packages = [anoia lualinux linotify]; + mainFunction = "run"; + } ./ifwatch.fnl ; + watchArg = z : intfs : map (i: "${z}:${i}/.outputs") intfs; in longrun { name = "firewall"; run = '' ${script} - while : ; do sleep 86400 ; done + PATH=${nftables}/bin:$PATH + ${ifwatch} ${concatStringsSep " " (flatten (mapAttrsToList watchArg zones))} ''; finish = "${nftables}/bin/nft flush ruleset"; } From 3f889c7119145518e43b2f57bb1b9d7b4fdbd01a Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 10 Feb 2025 21:16:20 +0000 Subject: [PATCH 7/7] default firewall zones in gateway profile --- examples/rotuer.nix | 4 ---- modules/profiles/gateway.nix | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/rotuer.nix b/examples/rotuer.nix index 78251e2..1d2a726 100644 --- a/examples/rotuer.nix +++ b/examples/rotuer.nix @@ -69,10 +69,6 @@ in rec { firewall = { enable = true; rules = secrets.firewallRules; - zones = { - lan = [ config.services.int ]; - wan = [ config.services.wan ] ; - }; }; wireless.networks = { # EDIT: if you have more or fewer wireless radios, here is where diff --git a/modules/profiles/gateway.nix b/modules/profiles/gateway.nix index 1e0ae48..c62c816 100644 --- a/modules/profiles/gateway.nix +++ b/modules/profiles/gateway.nix @@ -50,6 +50,10 @@ in { rules = mkOption { type = types.attrsOf types.attrs; }; zones = mkOption { type = types.attrsOf (types.listOf liminix.lib.types.service); + default = { + lan = [ config.services.int ]; + wan = [ config.services.wan ]; + }; }; };