at 25.11-pre 14 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 cfg = config.networking.nftables; 9 10 tableSubmodule = 11 { name, ... }: 12 { 13 options = { 14 enable = lib.mkOption { 15 type = lib.types.bool; 16 default = true; 17 description = "Enable this table."; 18 }; 19 20 name = lib.mkOption { 21 type = lib.types.str; 22 description = "Table name."; 23 }; 24 25 content = lib.mkOption { 26 type = lib.types.lines; 27 description = "The table content."; 28 }; 29 30 family = lib.mkOption { 31 description = "Table family."; 32 type = lib.types.enum [ 33 "ip" 34 "ip6" 35 "inet" 36 "arp" 37 "bridge" 38 "netdev" 39 ]; 40 }; 41 }; 42 43 config = { 44 name = lib.mkDefault name; 45 }; 46 }; 47in 48{ 49 ###### interface 50 51 options = { 52 networking.nftables.enable = lib.mkOption { 53 type = lib.types.bool; 54 default = false; 55 description = '' 56 Whether to enable nftables and use nftables based firewall if enabled. 57 nftables is a Linux-based packet filtering framework intended to 58 replace frameworks like iptables. 59 60 Note that if you have Docker enabled you will not be able to use 61 nftables without intervention. Docker uses iptables internally to 62 setup NAT for containers. This module disables the ip_tables kernel 63 module, however Docker automatically loads the module. Please see 64 <https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273> 65 for more information. 66 67 There are other programs that use iptables internally too, such as 68 libvirt. For information on how the two firewalls interact, see 69 <https://wiki.nftables.org/wiki-nftables/index.php/Troubleshooting#Question_4._How_do_nftables_and_iptables_interact_when_used_on_the_same_system.3F>. 70 ''; 71 }; 72 73 networking.nftables.checkRuleset = lib.mkOption { 74 type = lib.types.bool; 75 default = true; 76 description = '' 77 Run `nft check` on the ruleset to spot syntax errors during build. 78 Because this is executed in a sandbox, the check might fail if it requires 79 access to any environmental factors or paths outside the Nix store. 80 To circumvent this, the ruleset file can be edited using the preCheckRuleset 81 option to work in the sandbox environment. 82 ''; 83 }; 84 85 networking.nftables.checkRulesetRedirects = lib.mkOption { 86 type = lib.types.addCheck (lib.types.attrsOf lib.types.path) ( 87 attrs: lib.all lib.types.path.check (lib.attrNames attrs) 88 ); 89 default = { 90 "/etc/hosts" = config.environment.etc.hosts.source; 91 "/etc/protocols" = config.environment.etc.protocols.source; 92 "/etc/services" = config.environment.etc.services.source; 93 }; 94 defaultText = lib.literalExpression '' 95 { 96 "/etc/hosts" = config.environment.etc.hosts.source; 97 "/etc/protocols" = config.environment.etc.protocols.source; 98 "/etc/services" = config.environment.etc.services.source; 99 } 100 ''; 101 description = '' 102 Set of paths that should be intercepted and rewritten while checking the ruleset 103 using `pkgs.buildPackages.libredirect`. 104 ''; 105 }; 106 107 networking.nftables.preCheckRuleset = lib.mkOption { 108 type = lib.types.lines; 109 default = ""; 110 example = lib.literalExpression '' 111 sed 's/skgid meadow/skgid nogroup/g' -i ruleset.conf 112 ''; 113 description = '' 114 This script gets run before the ruleset is checked. It can be used to 115 create additional files needed for the ruleset check to work, or modify 116 the ruleset for cases the build environment cannot cover. 117 ''; 118 }; 119 120 networking.nftables.flushRuleset = lib.mkEnableOption "flushing the entire ruleset on each reload"; 121 122 networking.nftables.extraDeletions = lib.mkOption { 123 type = lib.types.lines; 124 default = ""; 125 example = '' 126 # this makes deleting a non-existing table a no-op instead of an error 127 table inet some-table; 128 129 delete table inet some-table; 130 ''; 131 description = '' 132 Extra deletion commands to be run on every firewall start, reload 133 and after stopping the firewall. 134 ''; 135 }; 136 137 networking.nftables.ruleset = lib.mkOption { 138 type = lib.types.lines; 139 default = ""; 140 example = '' 141 # Check out https://wiki.nftables.org/ for better documentation. 142 # Table for both IPv4 and IPv6. 143 table inet filter { 144 # Block all incoming connections traffic except SSH and "ping". 145 chain input { 146 type filter hook input priority 0; 147 148 # accept any localhost traffic 149 iifname lo accept 150 151 # accept traffic originated from us 152 ct state {established, related} accept 153 154 # ICMP 155 # routers may also want: mld-listener-query, nd-router-solicit 156 ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept 157 ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept 158 159 # allow "ping" 160 ip6 nexthdr icmpv6 icmpv6 type echo-request accept 161 ip protocol icmp icmp type echo-request accept 162 163 # accept SSH connections (required for a server) 164 tcp dport 22 accept 165 166 # count and drop any other traffic 167 counter drop 168 } 169 170 # Allow all outgoing connections. 171 chain output { 172 type filter hook output priority 0; 173 accept 174 } 175 176 chain forward { 177 type filter hook forward priority 0; 178 accept 179 } 180 } 181 ''; 182 description = '' 183 The ruleset to be used with nftables. Should be in a format that 184 can be loaded using "/bin/nft -f". The ruleset is updated atomically. 185 Note that if the tables should be cleaned first, either: 186 - networking.nftables.flushRuleset = true; needs to be set (flushes all tables) 187 - networking.nftables.extraDeletions needs to be set 188 - or networking.nftables.tables can be used, which will clean up the table automatically 189 ''; 190 }; 191 networking.nftables.rulesetFile = lib.mkOption { 192 type = lib.types.nullOr lib.types.path; 193 default = null; 194 description = '' 195 The ruleset file to be used with nftables. Should be in a format that 196 can be loaded using "nft -f". The ruleset is updated atomically. 197 ''; 198 }; 199 200 networking.nftables.flattenRulesetFile = lib.mkOption { 201 type = lib.types.bool; 202 default = false; 203 description = '' 204 Use `builtins.readFile` rather than `include` to handle {option}`networking.nftables.rulesetFile`. It is useful when you want to apply {option}`networking.nftables.preCheckRuleset` to {option}`networking.nftables.rulesetFile`. 205 206 ::: {.note} 207 It is expected that {option}`networking.nftables.rulesetFile` can be accessed from the build sandbox. 208 ::: 209 ''; 210 }; 211 212 networking.nftables.tables = lib.mkOption { 213 type = lib.types.attrsOf (lib.types.submodule tableSubmodule); 214 215 default = { }; 216 217 description = '' 218 Tables to be added to ruleset. 219 Tables will be added together with delete statements to clean up the table before every update. 220 ''; 221 222 example = { 223 filter = { 224 family = "inet"; 225 content = '' 226 # Check out https://wiki.nftables.org/ for better documentation. 227 # Table for both IPv4 and IPv6. 228 # Block all incoming connections traffic except SSH and "ping". 229 chain input { 230 type filter hook input priority 0; 231 232 # accept any localhost traffic 233 iifname lo accept 234 235 # accept traffic originated from us 236 ct state {established, related} accept 237 238 # ICMP 239 # routers may also want: mld-listener-query, nd-router-solicit 240 ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept 241 ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept 242 243 # allow "ping" 244 ip6 nexthdr icmpv6 icmpv6 type echo-request accept 245 ip protocol icmp icmp type echo-request accept 246 247 # accept SSH connections (required for a server) 248 tcp dport 22 accept 249 250 # count and drop any other traffic 251 counter drop 252 } 253 254 # Allow all outgoing connections. 255 chain output { 256 type filter hook output priority 0; 257 accept 258 } 259 260 chain forward { 261 type filter hook forward priority 0; 262 accept 263 } 264 ''; 265 }; 266 }; 267 }; 268 }; 269 270 ###### implementation 271 272 config = lib.mkIf cfg.enable { 273 boot.blacklistedKernelModules = [ "ip_tables" ]; 274 environment.systemPackages = [ pkgs.nftables ]; 275 # versionOlder for backportability, remove afterwards 276 networking.nftables.flushRuleset = lib.mkDefault ( 277 lib.versionOlder config.system.stateVersion "23.11" 278 || (cfg.rulesetFile != null || cfg.ruleset != "") 279 ); 280 systemd.services.nftables = { 281 description = "nftables firewall"; 282 after = [ "sysinit.target" ]; 283 before = [ 284 "network-pre.target" 285 "shutdown.target" 286 ]; 287 conflicts = [ "shutdown.target" ]; 288 wants = [ 289 "network-pre.target" 290 "sysinit.target" 291 ]; 292 wantedBy = [ "multi-user.target" ]; 293 reloadIfChanged = true; 294 serviceConfig = 295 let 296 enabledTables = lib.filterAttrs (_: table: table.enable) cfg.tables; 297 deletionsScript = pkgs.writeScript "nftables-deletions" '' 298 #! ${pkgs.nftables}/bin/nft -f 299 ${ 300 if cfg.flushRuleset then 301 "flush ruleset" 302 else 303 lib.concatStringsSep "\n" ( 304 lib.mapAttrsToList (_: table: '' 305 table ${table.family} ${table.name} 306 delete table ${table.family} ${table.name} 307 '') enabledTables 308 ) 309 } 310 ${cfg.extraDeletions} 311 ''; 312 deletionsScriptVar = "/var/lib/nftables/deletions.nft"; 313 ensureDeletions = pkgs.writeShellScript "nftables-ensure-deletions" '' 314 touch ${deletionsScriptVar} 315 chmod +x ${deletionsScriptVar} 316 ''; 317 saveDeletionsScript = pkgs.writeShellScript "nftables-save-deletions" '' 318 cp ${deletionsScript} ${deletionsScriptVar} 319 ''; 320 cleanupDeletionsScript = pkgs.writeShellScript "nftables-cleanup-deletions" '' 321 rm ${deletionsScriptVar} 322 ''; 323 rulesScript = pkgs.writeTextFile { 324 name = "nftables-rules"; 325 executable = true; 326 text = '' 327 #! ${pkgs.nftables}/bin/nft -f 328 # previous deletions, if any 329 include "${deletionsScriptVar}" 330 # current deletions 331 include "${deletionsScript}" 332 ${lib.concatStringsSep "\n" ( 333 lib.mapAttrsToList (_: table: '' 334 table ${table.family} ${table.name} { 335 ${table.content} 336 } 337 '') enabledTables 338 )} 339 ${cfg.ruleset} 340 ${ 341 if cfg.rulesetFile != null then 342 if cfg.flattenRulesetFile then 343 builtins.readFile cfg.rulesetFile 344 else 345 '' 346 include "${cfg.rulesetFile}" 347 '' 348 else 349 "" 350 } 351 ''; 352 checkPhase = lib.optionalString cfg.checkRuleset '' 353 cp $out ruleset.conf 354 sed 's|include "${deletionsScriptVar}"||' -i ruleset.conf 355 ${cfg.preCheckRuleset} 356 export NIX_REDIRECTS=${ 357 lib.escapeShellArg ( 358 lib.concatStringsSep ":" (lib.mapAttrsToList (n: v: "${n}=${v}") cfg.checkRulesetRedirects) 359 ) 360 } 361 LD_PRELOAD="${pkgs.buildPackages.libredirect}/lib/libredirect.so ${pkgs.buildPackages.lklWithFirewall.lib}/lib/liblkl-hijack.so" \ 362 ${pkgs.buildPackages.nftables}/bin/nft --check --file ruleset.conf 363 ''; 364 }; 365 in 366 { 367 Type = "oneshot"; 368 RemainAfterExit = true; 369 ExecStart = [ 370 ensureDeletions 371 rulesScript 372 ]; 373 ExecStartPost = saveDeletionsScript; 374 ExecReload = [ 375 ensureDeletions 376 rulesScript 377 saveDeletionsScript 378 ]; 379 ExecStop = [ 380 deletionsScriptVar 381 cleanupDeletionsScript 382 ]; 383 StateDirectory = "nftables"; 384 }; 385 unitConfig.DefaultDependencies = false; 386 }; 387 }; 388}