lol

networking/nftables: add .tables property and disable ruleset flushing by default

This allows for other unmanaged tables to co-exist peacefully on the os,
by having the nixos-managed tables be re-created atomically and the other
tables will simply be left untouched.

+93 -1
+93 -1
nixos/modules/services/networking/nftables.nix
··· 2 2 with lib; 3 3 let 4 4 cfg = config.networking.nftables; 5 + 6 + tableSubmodule = { name, ... }: { 7 + options = { 8 + enable = mkOption { 9 + type = types.bool; 10 + default = true; 11 + description = lib.mdDoc "Enable this table."; 12 + }; 13 + 14 + name = mkOption { 15 + type = types.str; 16 + description = lib.mdDoc "Table name."; 17 + }; 18 + 19 + content = mkOption { 20 + type = types.lines; 21 + description = lib.mdDoc "The table content."; 22 + }; 23 + 24 + family = mkOption { 25 + description = lib.mdDoc "Table family."; 26 + type = types.enum [ "ip" "ip6" "inet" "arp" "bridge" "netdev" ]; 27 + }; 28 + }; 29 + 30 + config = { 31 + name = mkDefault name; 32 + }; 33 + }; 5 34 in 6 35 { 7 36 ###### interface ··· 116 145 This option conflicts with ruleset and nftables based firewall. 117 146 ''; 118 147 }; 148 + networking.nftables.tables = mkOption { 149 + type = types.attrsOf (types.submodule tableSubmodule); 150 + 151 + default = {}; 152 + 153 + description = lib.mdDoc '' 154 + Tables to be added to ruleset. 155 + Tables will be added together with delete statements to clean up the table before every update. 156 + ''; 157 + 158 + example = { 159 + filter = { 160 + family = "inet"; 161 + content = '' 162 + # Check out https://wiki.nftables.org/ for better documentation. 163 + # Table for both IPv4 and IPv6. 164 + # Block all incoming connections traffic except SSH and "ping". 165 + chain input { 166 + type filter hook input priority 0; 167 + 168 + # accept any localhost traffic 169 + iifname lo accept 170 + 171 + # accept traffic originated from us 172 + ct state {established, related} accept 173 + 174 + # ICMP 175 + # routers may also want: mld-listener-query, nd-router-solicit 176 + ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept 177 + ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept 178 + 179 + # allow "ping" 180 + ip6 nexthdr icmpv6 icmpv6 type echo-request accept 181 + ip protocol icmp icmp type echo-request accept 182 + 183 + # accept SSH connections (required for a server) 184 + tcp dport 22 accept 185 + 186 + # count and drop any other traffic 187 + counter drop 188 + } 189 + 190 + # Allow all outgoing connections. 191 + chain output { 192 + type filter hook output priority 0; 193 + accept 194 + } 195 + 196 + chain forward { 197 + type filter hook forward priority 0; 198 + accept 199 + } 200 + ''; 201 + }; 202 + }; 203 + }; 119 204 }; 120 205 121 206 ###### implementation ··· 131 216 wantedBy = [ "multi-user.target" ]; 132 217 reloadIfChanged = true; 133 218 serviceConfig = let 219 + enabledTables = filterAttrs (_: table: table.enable) cfg.tables; 134 220 rulesScript = pkgs.writeTextFile { 135 221 name = "nftables-rules"; 136 222 executable = true; 137 223 text = '' 138 224 #! ${pkgs.nftables}/bin/nft -f 139 - flush ruleset 225 + ${concatStringsSep "\n" (mapAttrsToList (_: table: '' 226 + table ${table.family} ${table.name} 227 + delete table ${table.family} ${table.name} 228 + table ${table.family} ${table.name} { 229 + ${table.content} 230 + } 231 + '') enabledTables)} 140 232 ${if cfg.rulesetFile != null then '' 141 233 include "${cfg.rulesetFile}" 142 234 '' else cfg.ruleset}