Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149{ pkgs, lib, config, ...}:with lib;let cfg = config.modules.firewall;
ruleModule = types.submodule ( { name, ... }: { options = { enable = mkOption { type = types.bool; default = true; description = "Whether this rule is active. Set to false from another module to disable it."; };
proto = mkOption { type = types.enum [ "tcp" "udp" "tcp-udp" ]; default = "tcp"; description = "Protocol this rule accepts."; };
ports = mkOption { type = types.listOf types.port; description = "Ports to accept."; };
interfaces = mkOption { type = types.listOf types.str; default = [ ]; example = [ "eth0" ]; description = "Restrict this rule to specific inbound interfaces. Empty = all interfaces."; };
comment = mkOption { type = types.str; default = name; description = "Comment attached to the generated nftables rule."; }; }; } );
enabledRules = filterAttrs (_: r: r.enable) cfg.rules;
protosOf = r: if r.proto == "tcp-udp" then [ "tcp" "udp" ] else [ r.proto ]; portSet = r: "{ ${concatMapStringsSep ", " toString r.ports} }";
renderProto = r: proto: if r.interfaces == [ ] then ''${proto} dport ${portSet r} accept comment "${r.comment}"'' else concatMapStringsSep "\n " ( iface: ''iifname "${iface}" ${proto} dport ${portSet r} accept comment "${r.comment}"'' ) r.interfaces;
renderRule = _: r: concatMapStringsSep "\n " (renderProto r) (protosOf r);in{ options.modules.firewall = { enable = mkEnableOption "nftables firewall (default deny inbound) with named, composable rules";
trustedInterfaces = mkOption { type = types.listOf types.str; default = [ ]; example = [ "tailscale0" ]; description = "Interfaces whose inbound traffic is fully trusted, bypassing all filtering."; };
rules = mkOption { type = types.attrsOf ruleModule; default = { }; description = '' Named firewall rules, meant to be defined from per-service files
Add a rule: modules.firewall.rules.my-service.ports = [ 8080 ]; Edit a rule: set any field again for the same name from another module, it merges. Remove a rule: modules.firewall.rules.my-service.enable = false; ''; example = literalExpression '' { adguardhome-web = { proto = "tcp"; ports = [ 3000 ]; }; adguardhome-dns = { proto = "tcp-udp"; ports = [ 53 ]; }; } ''; }; };
config = mkIf cfg.enable { environment.etc."nftables.conf".text = '' flush ruleset
table inet filter { chain input { type filter hook input priority 0; policy drop;
iif "lo" accept ${concatMapStringsSep "\n " (iface: ''iif "${iface}" accept'') cfg.trustedInterfaces} ct state established,related accept ct state invalid drop
icmp type echo-request limit rate 5/second accept icmpv6 type echo-request limit rate 5/second accept
${concatStringsSep "\n " (mapAttrsToList renderRule enabledRules)}
limit rate 5/minute log prefix "nft-drop-in: " }
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; } } '';
finit.tasks.nftables-setup = { description = "Load nftables firewall ruleset"; runlevels = "S"; command = "${pkgs.nftables}/bin/nft -f /etc/nftables.conf"; };
environment.systemPackages = with pkgs; [ nftables sbctl efibootmgr ]; };}