From d7f3e050630ce32a48cbee4bbfad223f3648aa23 Mon Sep 17 00:00:00 2001
From: Daniel Barlow <dan@telent.net>
Date: Sun, 16 Jul 2023 16:55:50 +0100
Subject: [PATCH] turn nftables firewall into a service-providing module

---
 examples/rotuer.nix          | 58 ++-------------------------
 modules/base.nix             | 10 ++++-
 modules/firewall/default.nix | 76 ++++++++++++++++++++++++++++++++++++
 modules/firewall/service.nix | 26 ++++++++++++
 4 files changed, 114 insertions(+), 56 deletions(-)
 create mode 100644 modules/firewall/default.nix
 create mode 100644 modules/firewall/service.nix

diff --git a/examples/rotuer.nix b/examples/rotuer.nix
index b9e9e39cc..8644c2b60 100644
--- a/examples/rotuer.nix
+++ b/examples/rotuer.nix
@@ -35,39 +35,13 @@ in rec {
     ../modules/standard.nix
     ../modules/ppp
     ../modules/dnsmasq
+    ../modules/firewall
   ];
   rootfsType = "jffs2";
   hostname = "rotuer";
   kernel = {
     config = {
       BRIDGE = "y";
-
-      NETFILTER_XT_MATCH_CONNTRACK = "y";
-
-      IP6_NF_IPTABLES= "y";     # do we still need these
-      IP_NF_IPTABLES= "y";      # if using nftables directly
-
-      IP_NF_NAT = "y";
-      IP_NF_TARGET_MASQUERADE = "y";
-      NETFILTER = "y";
-      NETFILTER_ADVANCED = "y";
-      NETFILTER_XTABLES = "y";
-
-      NFT_COMPAT = "y";
-      NFT_CT = "y";
-      NFT_LOG = "y";
-      NFT_MASQ = "y";
-      NFT_NAT = "y";
-      NFT_REJECT = "y";
-      NFT_REJECT_INET = "y";
-
-      NF_CONNTRACK = "y";
-      NF_NAT = "y";
-      NF_NAT_MASQUERADE  = "y";
-      NF_TABLES= "y";
-      NF_TABLES_INET = "y";
-      NF_TABLES_IPV4 = "y";
-      NF_TABLES_IPV6 = "y";
     };
   };
 
@@ -221,33 +195,9 @@ in rec {
   };
 
   services.firewall =
-    let
-      script= pkgs.firewallgen "firewall.nft" (import ./rotuer-firewall.nix);
-      kmodules = pkgs.kernel-modules.override {
-        kernelSrc  = config.system.outputs.kernel.src;
-        modulesupport = config.system.outputs.kernel.modulesupport;
-        kconfig = {
-          NFT_FIB_IPV4 = "m";
-          NFT_FIB_IPV6 = "m";
-          NF_TABLES = "m";
-          NF_CT_PROTO_DCCP = "y";
-          NF_CT_PROTO_SCTP = "y";
-          NF_CT_PROTO_UDPLITE = "y";
-          #          NF_CONNTRACK_FTP = "m";
-          NFT_CT = "m";
-        };
-        targets = [
-          "nft_fib_ipv4"
-          "nft_fib_ipv6"
-        ];
-      };
-    in oneshot {
-      name = "firewall";
-      up = ''
-        sh ${kmodules}/load.sh
-        ${script};
-      '';
-      down = "${pkgs.nftables}/bin/nft flush ruleset";
+    let ruleset = import ./rotuer-firewall.nix;
+    in config.system.service.firewall {
+      inherit ruleset;
     };
 
   services.packet_forwarding =
diff --git a/modules/base.nix b/modules/base.nix
index 67eba8a6a..bd1798dab 100644
--- a/modules/base.nix
+++ b/modules/base.nix
@@ -26,6 +26,11 @@ in {
     };
     kernel = {
       src = mkOption { type = types.package; } ;
+      modular = mkOption {
+        type = types.boolean;
+        default = true;
+        description = "support loadable kernel modules";
+      };
       extraPatchPhase = mkOption {
         default = "true";
         type = types.lines;
@@ -67,14 +72,15 @@ in {
     };
 
     kernel = rec {
+      modular = true; # disabling this is not yet supported
       config = {
         IKCONFIG = "y";
         IKCONFIG_PROC = "y";
         PROC_FS = "y";
 
         KEXEC = "y";
-        MODULES = "y";
-        MODULE_SIG = "y";
+        MODULES = if modular then "y" else "n";
+        MODULE_SIG = if modular then "y" else "n";
         DEBUG_FS = "y";
 
         MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
diff --git a/modules/firewall/default.nix b/modules/firewall/default.nix
new file mode 100644
index 000000000..41b3bddea
--- /dev/null
+++ b/modules/firewall/default.nix
@@ -0,0 +1,76 @@
+{ lib, pkgs, config, ...}:
+let
+  inherit (lib) mkOption types;
+  inherit (pkgs.liminix.services) oneshot;
+
+  kconf = isModule :
+    # setting isModule false is utterly untested and mostly
+    # unimplemented: I say this to preempt any "how on earth is this
+    # even supposed to work?" questions
+    let yes = if isModule then "m" else "y";
+    in {
+      NFT_FIB_IPV4 = yes;
+      NFT_FIB_IPV6 = yes;
+      NF_TABLES = yes;
+      NF_CT_PROTO_DCCP = "y";
+      NF_CT_PROTO_SCTP = "y";
+      NF_CT_PROTO_UDPLITE = "y";
+      # NF_CONNTRACK_FTP = yes;
+      NFT_CT = yes;
+    };
+  kmodules = pkgs.kernel-modules.override {
+    kernelSrc = config.system.outputs.kernel.src;
+    modulesupport = config.system.outputs.kernel.modulesupport;
+    targets = [
+      "nft_fib_ipv4"
+      "nft_fib_ipv6"
+    ];
+    kconfig = kconf true;
+  };
+  loadModules = oneshot {
+    name = "firewall-modules";
+    up = "sh ${kmodules}/load.sh";
+    down = "sh ${kmodules}/unload.sh";
+  };
+in
+{
+  options = {
+    system.service.firewall = mkOption {
+      type = types.anything; # types.functionTo pkgs.liminix.lib.types.service;
+    };
+  };
+  config = {
+    system.service.firewall = params :
+      let svc = (pkgs.callPackage ./service.nix {}) params;
+      in svc // { dependencies = svc.dependencies ++ [loadModules]; };
+
+    kernel.config = {
+      NETFILTER_XT_MATCH_CONNTRACK = "y";
+
+      IP6_NF_IPTABLES= "y";     # do we still need these
+      IP_NF_IPTABLES= "y";      # if using nftables directly
+
+      IP_NF_NAT = "y";
+      IP_NF_TARGET_MASQUERADE = "y";
+      NETFILTER = "y";
+      NETFILTER_ADVANCED = "y";
+      NETFILTER_XTABLES = "y";
+
+      NFT_COMPAT = "y";
+      NFT_CT = "y";
+      NFT_LOG = "y";
+      NFT_MASQ = "y";
+      NFT_NAT = "y";
+      NFT_REJECT = "y";
+      NFT_REJECT_INET = "y";
+
+      NF_CONNTRACK = "y";
+      NF_NAT = "y";
+      NF_NAT_MASQUERADE  = "y";
+      NF_TABLES= "y";
+      NF_TABLES_INET = "y";
+      NF_TABLES_IPV4 = "y";
+      NF_TABLES_IPV6 = "y";
+    };
+  };
+}
diff --git a/modules/firewall/service.nix b/modules/firewall/service.nix
new file mode 100644
index 000000000..972081637
--- /dev/null
+++ b/modules/firewall/service.nix
@@ -0,0 +1,26 @@
+{
+  liminix
+, lib
+, firewallgen
+, nftables
+}:
+let
+  inherit (liminix.services) oneshot;
+  inherit (liminix.lib) typeChecked;
+  inherit (lib) mkOption types;
+  t = {
+    ruleset = mkOption {
+      type = types.anything;         # we could usefully define this more tightly
+      description = "firewall ruleset";
+    };
+  };
+in
+params:
+let
+  inherit (typeChecked "firewall" t params) ruleset;
+  script = firewallgen "firewall.nft" ruleset;
+in oneshot {
+  name = "firewall";
+  up = script;
+  down = "${nftables}/bin/nft flush ruleset";
+}