/ module.nix
module.nix
1 { lib, ... }: 2 3 let 4 indentation = n: lib.strings.replicate n " "; 5 indent = 6 n: 7 lib.flip lib.pipe [ 8 (lib.splitString "\n") 9 (map (s: lib.optionalString (lib.match " *" s == null) (indentation n + s))) 10 (lib.concatStringsSep "\n") 11 ]; 12 13 nestedListOf = 14 t: 15 lib.types.listOf (lib.types.either t (nestedListOf t)) 16 // { 17 description = "nested ${(lib.types.listOf t).description}"; 18 }; 19 20 chainSubmodule = 21 { name, config, ... }: 22 { 23 options = { 24 enable = lib.mkOption { 25 type = lib.types.bool; 26 description = "Enable this chain."; 27 default = true; 28 }; 29 30 name = lib.mkOption { 31 type = lib.types.str; 32 description = "Chain name. Defaults to the attribute name."; 33 }; 34 35 type = lib.mkOption { 36 type = lib.types.nullOr ( 37 lib.types.enum [ 38 "filter" 39 "nat" 40 "route" 41 ] 42 ); 43 description = "Chain type."; 44 default = null; 45 }; 46 47 hook = lib.mkOption { 48 type = lib.types.nullOr ( 49 lib.types.enum [ 50 "ingress" 51 "prerouting" 52 "forward" 53 "input" 54 "output" 55 "postrouting" 56 "egress" 57 ] 58 ); 59 description = "Chain hook."; 60 default = null; 61 }; 62 63 priority = lib.mkOption { 64 type = lib.types.nullOr lib.types.int; 65 description = "Chain priority."; 66 default = null; 67 }; 68 69 policy = lib.mkOption { 70 type = lib.types.nullOr ( 71 lib.types.enum [ 72 "accept" 73 "drop" 74 ] 75 ); 76 description = "Chain policy."; 77 default = null; 78 }; 79 80 rules = lib.mkOption { 81 type = nestedListOf lib.types.str; 82 apply = lib.flatten; 83 description = "Chain rules."; 84 default = [ ]; 85 }; 86 87 header = lib.mkOption { 88 type = lib.types.nullOr lib.types.str; 89 description = "Chain header. Constructed by other options if not overridden."; 90 default = null; 91 example = "type filter hook input priority 0; policy drop;"; 92 }; 93 94 content = lib.mkOption { 95 type = lib.types.lines; 96 description = "Chain content. Constructed by other options if not overridden."; 97 }; 98 99 defaultRules = { 100 enable = lib.mkEnableOption "default rules depending on the chain type and hook"; 101 102 ct_state = lib.mkOption { 103 type = lib.types.bool; 104 description = "Accept related/established, drop invalid. Applies if type = filter."; 105 }; 106 107 icmpv6 = lib.mkOption { 108 type = lib.types.bool; 109 description = "Accept basic ICMPv6 request types. Applies if type = filter && hook = input."; 110 }; 111 112 icmp_pings = lib.mkOption { 113 type = lib.types.bool; 114 description = "Accept ICMP echo requests (\"pings\")."; 115 default = false; 116 }; 117 118 lo = lib.mkOption { 119 type = lib.types.bool; 120 description = "Accept traffic from `lo` (loopback). Applies if type = filter && hook = input."; 121 }; 122 }; 123 }; 124 125 config = { 126 name = lib.mkDefault name; 127 128 header = lib.mkIf (config.type != null || config.hook != null) ( 129 "type ${config.type} hook ${config.hook} priority ${toString (lib.defaultTo 0 config.priority)};" 130 + lib.optionalString (config.policy != null) " policy ${config.policy};" 131 ); 132 133 content = lib.concatStringsSep "\n" ( 134 lib.optional (config.header != null) config.header 135 ++ config.rules 136 ++ lib.optional (config.policy != null && config.header == null) config.policy 137 ); 138 139 defaultRules = lib.mapAttrs (_: x: lib.mkDefault (config.defaultRules.enable && x)) { 140 ct_state = 141 config.type == "filter" 142 && lib.elem config.hook [ 143 "input" 144 "forward" 145 "output" 146 ]; 147 148 icmpv6 = config.type == "filter" && config.hook == "input"; 149 150 lo = config.type == "filter" && config.hook == "input"; 151 }; 152 153 rules = lib.mkBefore ( 154 lib.optionals config.defaultRules.ct_state [ 155 "ct state vmap { related : accept, established : accept, invalid : drop }" 156 ] 157 ++ lib.optionals config.defaultRules.icmpv6 [ 158 "icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept" 159 ] 160 ++ lib.optionals config.defaultRules.icmp_pings [ 161 "icmp type echo-request accept" 162 "icmpv6 type echo-request accept" 163 ] 164 ++ lib.optionals config.defaultRules.lo [ "iif lo accept" ] 165 ); 166 }; 167 }; 168 169 tableSubmodule = 170 { config, ... }: 171 { 172 options = { 173 chains = lib.mkOption { 174 type = lib.types.attrsOf (lib.types.submodule chainSubmodule); 175 description = "Chains to be added to the table."; 176 default = { }; 177 }; 178 }; 179 180 config.content = lib.mkIf (config.chains != { }) ( 181 lib.pipe config.chains [ 182 lib.attrValues 183 (lib.filter (chain: chain.enable)) 184 (lib.concatMapStringsSep "\n" (chain: '' 185 chain ${chain.name} { 186 ${indent 1 chain.content} 187 } 188 '')) 189 (indent 1) 190 (lib.removePrefix (indentation 1)) 191 (lib.removeSuffix "\n") 192 ] 193 ); 194 }; 195 in 196 197 { 198 options.networking.nftables.tables = lib.mkOption { 199 type = lib.types.attrsOf (lib.types.submodule tableSubmodule); 200 }; 201 }