From 6649ebeccd95848062ca114ec0aee7714eb62c9f Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Fri, 28 Feb 2025 00:43:20 +0000 Subject: [PATCH] firewall: use watch-outputs to track changes in zone->interface map includes a horrible hack to work around (claimed (by me)) deficiencies in the nftables parser --- modules/firewall/service.nix | 38 ++++++++--- modules/secrets/subscriber.nix | 27 +++----- pkgs/firewallgen/default.nix | 2 +- pkgs/output-template/output-template.fnl | 4 +- pkgs/watch-outputs/default.nix | 2 + pkgs/watch-outputs/watch-outputs.fnl | 82 ++++++++++++++++++------ tests/pppoe/getaddress.expect | 1 + 7 files changed, 108 insertions(+), 48 deletions(-) diff --git a/modules/firewall/service.nix b/modules/firewall/service.nix index 431c728..9fe275e 100644 --- a/modules/firewall/service.nix +++ b/modules/firewall/service.nix @@ -5,6 +5,9 @@ nftables, writeFennel, anoia, + svc, + lua, + output-template, lualinux, linotify, }: @@ -18,14 +21,18 @@ let inherit (lib.attrsets) mapAttrs' nameValuePair mapAttrsToList; inherit (lib.strings) concatStringsSep; inherit (lib.lists) flatten; + inherit (builtins) concatLists attrValues; + inherit (liminix) outputRef; mkSet = family: name: nameValuePair "${name}-set-${family}" { kind = "set"; inherit name family; type = "ifname"; + elements = map (s: "{{ output(${builtins.toJSON s}, \"ifname\", \"\") }}") zones.${name}; }; - sets = (mapAttrs' (n: _: mkSet "ip" n) zones) // (mapAttrs' (n: _: mkSet "ip6" n) zones); + 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; ifwatch = writeFennel "ifwatch" { @@ -37,13 +44,26 @@ let mainFunction = "run"; } ./ifwatch.fnl; watchArg = z: intfs: map (i: "${z}:${i}/.outputs") intfs; -in -longrun { name = "firewall"; - run = '' - ${script} - PATH=${nftables}/bin:$PATH - ${ifwatch} ${concatStringsSep " " (flatten (mapAttrsToList watchArg zones))} - ''; - finish = "${nftables}/bin/nft flush ruleset"; + service = longrun { + inherit name; + run = '' + mkdir -p /run/${name}; in_outputs ${name} + # exec > /dev/console 2>&1 + echo RESTARTING FIREWALL >/dev/console + PATH=${nftables}/bin:${lua}/bin:$PATH + ${output-template}/bin/output-template '{{' '}}' < ${script} | lua -e 'for x in io.lines() do if not string.match(x, "elements = {%s+}") then print(x) end; end' > /run/${name}/fw.nft + # cat /run/${name}/fw.nft > /dev/console + nft -f /run/${name}/fw.nft + while sleep 86400 ; do : ; done + ''; + finish = "${nftables}/bin/nft flush ruleset"; + }; +in +svc.secrets.subscriber.build { + watch = + concatLists + (mapAttrsToList (_zone : services : map (s: outputRef s "ifname") services) zones); + + inherit service; } diff --git a/modules/secrets/subscriber.nix b/modules/secrets/subscriber.nix index 578ccf3..1839c33 100644 --- a/modules/secrets/subscriber.nix +++ b/modules/secrets/subscriber.nix @@ -13,12 +13,11 @@ }: let inherit (liminix.services) oneshot longrun; - inherit (builtins) length head toString; - inherit (lib) unique optional optionals; + inherit (builtins) map length head toString; + inherit (lib) unique optional optionals concatStringsSep; inherit (service) name; watched-services = unique (map (f: f "service") watch); - paths = unique (map (f: f "path") watch); restart-flag = { @@ -35,17 +34,11 @@ let } .${action}; - watched-service = - if length watched-services == 0 then - null - else if length watched-services == 1 then - head watched-services - else - throw "cannot subscribe to more than one source service for secrets"; - watcher = let name' = "restart-${name}"; + refs = concatStringsSep " " + (map (s: "${s "service"}:${s "path"}") watch); in longrun { name = name'; @@ -55,16 +48,16 @@ let if test -e $dir/notification-fd; then flag="-U"; else flag="-u"; fi ${s6}/bin/s6-svwait $flag /run/service/${name} || exit PATH=${s6-rc}/bin:${s6}/bin:$PATH - ${watch-outputs}/bin/watch-outputs ${restart-flag} ${name} ${watched-service.name} ${lib.concatStringsSep " " paths} + ${watch-outputs}/bin/watch-outputs ${restart-flag} ${name} ${refs} ''; }; in service.overrideAttrs (o: { - buildInputs = (lim.orEmpty o.buildInputs) ++ optional (watched-service != null) watcher; + buildInputs = (lim.orEmpty o.buildInputs) ++ optional (watch != []) watcher; dependencies = (lim.orEmpty o.dependencies) - ++ optionals (watched-service != null) [ - watcher - watched-service - ]; + # ++ optionals + # (watch != []) + # ([ watcher ] ++ watched-services); + ; }) diff --git a/pkgs/firewallgen/default.nix b/pkgs/firewallgen/default.nix index c9bd96f..af5c372 100644 --- a/pkgs/firewallgen/default.nix +++ b/pkgs/firewallgen/default.nix @@ -61,7 +61,7 @@ let '' set ${name} { type ${type} - ${if elements != [ ] then "elements = { ${concatStringsSep ", " elements} }" else ""} + ${if elements != [ ] then "elements = { ${concatStringsSep ", " (builtins.trace elements elements)} }" else ""} } ''; diff --git a/pkgs/output-template/output-template.fnl b/pkgs/output-template/output-template.fnl index 95be3c6..754b5e4 100644 --- a/pkgs/output-template/output-template.fnl +++ b/pkgs/output-template/output-template.fnl @@ -25,9 +25,9 @@ myenv { : string :output - (fn [service-path path] + (fn [service-path path default] (let [s (assert (svc.open (.. service-path "/.outputs")))] - (s:output path))) + (or (s:output path) default))) :lua_quote #(string.format "%q" %1) :json_quote (fn [x] (.. "\"" (json-escape x) "\"")) }] diff --git a/pkgs/watch-outputs/default.nix b/pkgs/watch-outputs/default.nix index b68c11c..c029a99 100644 --- a/pkgs/watch-outputs/default.nix +++ b/pkgs/watch-outputs/default.nix @@ -21,6 +21,7 @@ stdenv.mkDerivation { nativeBuildInputs = [ fennelrepl ] ; buildPhase = '' + fennelrepl --test ./watch-outputs.fnl cp -p ${ writeFennel name { packages = [ @@ -29,6 +30,7 @@ stdenv.mkDerivation { linotify fennel ]; + macros = [ anoia.dev ]; mainFunction = "run"; } ./watch-outputs.fnl } ${name} diff --git a/pkgs/watch-outputs/watch-outputs.fnl b/pkgs/watch-outputs/watch-outputs.fnl index 8afbede..930ca86 100644 --- a/pkgs/watch-outputs/watch-outputs.fnl +++ b/pkgs/watch-outputs/watch-outputs.fnl @@ -1,10 +1,18 @@ (local { : %% : system : assoc : split : table= : dig } (require :anoia)) (local svc (require :anoia.svc)) -(local { : kill } (require :lualinux)) +(local { : kill &as ll} (require :lualinux)) +(import-macros { : define-tests : expect : expect= } :anoia.assert) -(fn split-paths [paths] - (icollect [_ path (ipairs paths)] - (split "/" path))) +(local { : view } (require :fennel)) + +(fn output-refs [outputs] + (let [result {}] + (each [_ v (ipairs outputs)] + (let [[service path] (split ":" v) + paths (or (. result service) [])] + (table.insert paths (split "/" path )) + (tset result service paths))) + result)) (fn parse-args [args] (match args @@ -17,37 +25,73 @@ ["-s" signal service & rest] (assoc (parse-args rest) :controlled-service service :action [:signal signal]) - [watched-service & paths] { : watched-service - :paths (split-paths paths) - })) + outputs { :output-references (output-refs outputs) } )) + +(define-tests + (expect= (parse-args ["-r" "daemon" + "/nix/store/s1:out1" + "/nix/store/s2:out1" "/nix/store/s2:out2/ifname"]) + {:action "restart" + :controlled-service "daemon" + :output-references + {"/nix/store/s1" [["out1"]] + "/nix/store/s2" [["out1"] ["out2" "ifname"]]}} + )) + (fn changed? [paths old-tree new-tree] (accumulate [changed? false _ path (ipairs paths)] (or changed? (not (table= (dig old-tree path) (dig new-tree path)))))) +(define-tests + (expect (changed? [["ifname"]] {:ifname "true"} {:ifindex 2})) + (expect (changed? [["ifname"]] {:ifname "true"} {:ifname "false"})) + (expect (not (changed? [["ifname"]] {:ifname "true"} {:ifname "true"}))) + (expect (not (changed? [["mtu"]] {:ifname "true"} {:ifname "false"}))) + (expect (not (changed? [["mtu"]] {:ifname "true"} {:ifname "false"}))) + ) + + + (fn do-action [action service] (case action :restart (system (%% "s6-svc -r /run/service/%s" service)) :restart-all (system (%% "s6-rc -b -d %q; s6-rc-up-tree %q" service service)) [:signal n] (system (%% "s6-svc -s %d /run/service/%s" n service)))) +(local POLLIN 0x0001) +(local POLLHUP 0x0010) + +(fn wait-for-change [services] + (let [pollfds (collect [s _p (ipairs services)] + (bor (lshift (s:fileno) 32) + (lshift (bor POLLIN POLLHUP) 16)))] + (ll.poll pollfds))) + +(fn open-services [output-references] + (collect [s p (pairs output-references)] + (values (assert (svc.open (.. s "/.outputs"))) p))) + (fn run [] - (let [{ + (let [trees {} + { + : output-references : controlled-service : action : watched-service - : paths } (parse-args arg) - dir (.. watched-service "/.outputs") - service (assert (svc.open dir))] - (print (%% "watching %q" watched-service)) - (accumulate [tree (service:output ".") - v (service:events)] - (let [new-tree (service:output ".")] - (when (changed? paths tree new-tree) - (print "watched path event:" action controlled-service) - (do-action action controlled-service)) - new-tree)))) + : paths } (parse-args arg)] + (while true + (let [services (open-services output-references) + trees (collect [s _ (pairs services)] + (values s (s:output ".")))] + (wait-for-change services) + (each [service paths (pairs services)] + (let [new-tree (service:output ".")] + (when (changed? paths (. trees service) new-tree) + (print "watched path event:" action controlled-service) + (do-action action controlled-service)))))))) + { : run } diff --git a/tests/pppoe/getaddress.expect b/tests/pppoe/getaddress.expect index cd438c3..7634891 100644 --- a/tests/pppoe/getaddress.expect +++ b/tests/pppoe/getaddress.expect @@ -17,6 +17,7 @@ while { $FINISHED < 10 } { } set FINISHED [ expr $FINISHED + 1 ] } +expect "#READY#" send "nft list set ip table-ip wan || touch /non/existent\n" expect { "ppp0" { puts "ppp0 found " }