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