diff options
Diffstat (limited to 'modules/firewall.nix')
| -rw-r--r-- | modules/firewall.nix | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/modules/firewall.nix b/modules/firewall.nix new file mode 100644 index 0000000..575de7d --- /dev/null +++ b/modules/firewall.nix @@ -0,0 +1,132 @@ +{ + config, + inventory, + self_name, + lib, + ... +}: +let + inherit (lib) + mkOption + types + mkIf + mkEnableOption + mkDefault + ; + inv = inventory.${self_name}; + net = inv.interfaces; + cfg = config.wilkuu.firewall; + + portRangeType = types.addCheck (types.submodule { + options = { + from = mkOption { + type = types.port; + example = 100; + description = "lowest part of the range (inclusive)"; + }; + to = mkOption { + type = types.port; + example = 200; + description = "highest part of the range (inclusive)"; + }; + }; + }) (range: range.from < range.to); + + mkPortsOption = + protocol: + mkOption { + type = types.listOf types.port; + default = [ ]; + example = [ 1234 ]; + description = "List of ${protocol} ports to accept"; + }; + + mkPortRangesOption = + protocol: + mkOption { + type = types.listOf portRangeType; + default = [ ]; + example = [ 1234 ]; + description = "List of ${protocol} port ranges to accept"; + }; + + layerAssertions = lib.mapAttrsToList ( + layerName: layerConfig: + let + missing = builtins.filter (importLayer: !(cfg.layers ? ${importLayer})) layerConfig.import-layer; + in + { + assertion = missing == [ ]; + message = "Layer ${layerName} imports undefined layer(s): ${toString missing}"; + } + ) cfg.layers; +in +{ + options.wilkuu.firewall = { + enable = mkEnableOption "firewall module"; + defaultLayer = lib.mkOption { + type = types.str; + default = "external"; + example = "eth"; + }; + layers = mkOption { + default = { }; + description = "Layers and which ports should be open"; + type = types.attrsOf ( + types.submodule { + options = { + import-layer = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "external" ]; + description = "Layer names which ports will also be opened on this layer"; + }; + allowedTCPPorts = mkPortsOption "TCP"; + allowedUDPPorts = mkPortsOption "UDP"; + allowedTCPPortRanges = mkPortRangesOption "TCP"; + allowedUDPPortRanges = mkPortRangesOption "UDP"; + }; + } + ); + }; + }; + + config = mkIf cfg.enable { + assertions = layerAssertions; + networking.nftables.enable = true; + networking.firewall = + let + # TODO: reconsider doing this as this will only do a flat-import anything more complex would require building an import tree. + resolvedLayers = lib.mapAttrs ( + _: l1: + lib.foldl' (acc: set2: { + allowedTCPPorts = acc.allowedTCPPorts ++ set2.allowedTCPPorts; + allowedUDPPorts = acc.allowedUDPPorts ++ set2.allowedUDPPorts; + allowedTCPPortRanges = acc.allowedTCPPortRanges ++ set2.allowedTCPPortRanges; + allowedUDPPortRanges = acc.allowedUDPPortRanges ++ set2.allowedUDPPortRanges; + }) l1 (map (lk: cfg.layers.${lk}) l1.import-layer) + ) cfg.layers; + + interfaces = lib.mapAttrs (_n: interface: { + inherit (resolvedLayers.${interface.layer}) + allowedTCPPorts + allowedUDPPorts + allowedTCPPortRanges + allowedUDPPortRanges + ; + }) (lib.filterAttrs (_n: interface: !(builtins.elem interface.type [ "roaming" ])) net); + + in + { + enable = true; + checkReversePath = mkDefault false; + inherit interfaces; + inherit (resolvedLayers.${cfg.defaultLayer}) + allowedTCPPorts + allowedUDPPorts + allowedTCPPortRanges + allowedUDPPortRanges + ; + }; + }; +} |
