1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.networking.firewall;
8
9 ifaceSet = concatStringsSep ", " (
10 map (x: ''"${x}"'') cfg.trustedInterfaces
11 );
12
13 portsToNftSet = ports: portRanges: concatStringsSep ", " (
14 map (x: toString x) ports
15 ++ map (x: "${toString x.from}-${toString x.to}") portRanges
16 );
17
18in
19
20{
21
22 options = {
23
24 networking.firewall = {
25 extraInputRules = mkOption {
26 type = types.lines;
27 default = "";
28 example = "ip6 saddr { fc00::/7, fe80::/10 } tcp dport 24800 accept";
29 description = lib.mdDoc ''
30 Additional nftables rules to be appended to the input-allow
31 chain.
32
33 This option only works with the nftables based firewall.
34 '';
35 };
36
37 extraForwardRules = mkOption {
38 type = types.lines;
39 default = "";
40 example = "iifname wg0 accept";
41 description = lib.mdDoc ''
42 Additional nftables rules to be appended to the forward-allow
43 chain.
44
45 This option only works with the nftables based firewall.
46 '';
47 };
48 };
49
50 };
51
52 config = mkIf (cfg.enable && config.networking.nftables.enable) {
53
54 assertions = [
55 {
56 assertion = cfg.extraCommands == "";
57 message = "extraCommands is incompatible with the nftables based firewall: ${cfg.extraCommands}";
58 }
59 {
60 assertion = cfg.extraStopCommands == "";
61 message = "extraStopCommands is incompatible with the nftables based firewall: ${cfg.extraStopCommands}";
62 }
63 {
64 assertion = cfg.pingLimit == null || !(hasPrefix "--" cfg.pingLimit);
65 message = "nftables syntax like \"2/second\" should be used in networking.firewall.pingLimit";
66 }
67 {
68 assertion = config.networking.nftables.rulesetFile == null;
69 message = "networking.nftables.rulesetFile conflicts with the firewall";
70 }
71 ];
72
73 networking.nftables.ruleset = ''
74
75 table inet nixos-fw {
76
77 ${optionalString (cfg.checkReversePath != false) ''
78 chain rpfilter {
79 type filter hook prerouting priority mangle + 10; policy drop;
80
81 meta nfproto ipv4 udp sport . udp dport { 67 . 68, 68 . 67 } accept comment "DHCPv4 client/server"
82 fib saddr . mark ${optionalString (cfg.checkReversePath != "loose") ". iif"} oif exists accept
83
84 ${optionalString cfg.logReversePathDrops ''
85 log level info prefix "rpfilter drop: "
86 ''}
87
88 }
89 ''}
90
91 chain input {
92 type filter hook input priority filter; policy drop;
93
94 ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''}
95
96 # Some ICMPv6 types like NDP is untracked
97 ct state vmap {
98 invalid : drop,
99 established : accept,
100 related : accept,
101 new : jump input-allow,
102 untracked: jump input-allow,
103 }
104
105 ${optionalString cfg.logRefusedConnections ''
106 tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: "
107 ''}
108 ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
109 pkttype broadcast log level info prefix "refused broadcast: "
110 pkttype multicast log level info prefix "refused multicast: "
111 ''}
112 ${optionalString cfg.logRefusedPackets ''
113 pkttype host log level info prefix "refused packet: "
114 ''}
115
116 ${optionalString cfg.rejectPackets ''
117 meta l4proto tcp reject with tcp reset
118 reject
119 ''}
120
121 }
122
123 chain input-allow {
124
125 ${concatStrings (mapAttrsToList (iface: cfg:
126 let
127 ifaceExpr = optionalString (iface != "default") "iifname ${iface}";
128 tcpSet = portsToNftSet cfg.allowedTCPPorts cfg.allowedTCPPortRanges;
129 udpSet = portsToNftSet cfg.allowedUDPPorts cfg.allowedUDPPortRanges;
130 in
131 ''
132 ${optionalString (tcpSet != "") "${ifaceExpr} tcp dport { ${tcpSet} } accept"}
133 ${optionalString (udpSet != "") "${ifaceExpr} udp dport { ${udpSet} } accept"}
134 ''
135 ) cfg.allInterfaces)}
136
137 ${optionalString cfg.allowPing ''
138 icmp type echo-request ${optionalString (cfg.pingLimit != null) "limit rate ${cfg.pingLimit}"} accept comment "allow ping"
139 ''}
140
141 icmpv6 type != { nd-redirect, 139 } accept comment "Accept all ICMPv6 messages except redirects and node information queries (type 139). See RFC 4890, section 4.4."
142 ip6 daddr fe80::/64 udp dport 546 accept comment "DHCPv6 client"
143
144 ${cfg.extraInputRules}
145
146 }
147
148 ${optionalString cfg.filterForward ''
149 chain forward {
150 type filter hook forward priority filter; policy drop;
151
152 ct state vmap {
153 invalid : drop,
154 established : accept,
155 related : accept,
156 new : jump forward-allow,
157 untracked : jump forward-allow,
158 }
159
160 }
161
162 chain forward-allow {
163
164 icmpv6 type != { router-renumbering, 139 } accept comment "Accept all ICMPv6 messages except renumbering and node information queries (type 139). See RFC 4890, section 4.3."
165
166 ct status dnat accept comment "allow port forward"
167
168 ${cfg.extraForwardRules}
169
170 }
171 ''}
172
173 }
174
175 '';
176
177 };
178
179}