1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.networking.firewall;
8
9 canonicalizePortList =
10 ports: lib.unique (builtins.sort builtins.lessThan ports);
11
12 commonOptions = {
13 allowedTCPPorts = mkOption {
14 type = types.listOf types.port;
15 default = [ ];
16 apply = canonicalizePortList;
17 example = [ 22 80 ];
18 description = lib.mdDoc ''
19 List of TCP ports on which incoming connections are
20 accepted.
21 '';
22 };
23
24 allowedTCPPortRanges = mkOption {
25 type = types.listOf (types.attrsOf types.port);
26 default = [ ];
27 example = [{ from = 8999; to = 9003; }];
28 description = lib.mdDoc ''
29 A range of TCP ports on which incoming connections are
30 accepted.
31 '';
32 };
33
34 allowedUDPPorts = mkOption {
35 type = types.listOf types.port;
36 default = [ ];
37 apply = canonicalizePortList;
38 example = [ 53 ];
39 description = lib.mdDoc ''
40 List of open UDP ports.
41 '';
42 };
43
44 allowedUDPPortRanges = mkOption {
45 type = types.listOf (types.attrsOf types.port);
46 default = [ ];
47 example = [{ from = 60000; to = 61000; }];
48 description = lib.mdDoc ''
49 Range of open UDP ports.
50 '';
51 };
52 };
53
54in
55
56{
57
58 options = {
59
60 networking.firewall = {
61 enable = mkOption {
62 type = types.bool;
63 default = true;
64 description = lib.mdDoc ''
65 Whether to enable the firewall. This is a simple stateful
66 firewall that blocks connection attempts to unauthorised TCP
67 or UDP ports on this machine.
68 '';
69 };
70
71 package = mkOption {
72 type = types.package;
73 default = if config.networking.nftables.enable then pkgs.nftables else pkgs.iptables;
74 defaultText = literalExpression ''if config.networking.nftables.enable then "pkgs.nftables" else "pkgs.iptables"'';
75 example = literalExpression "pkgs.iptables-legacy";
76 description = lib.mdDoc ''
77 The package to use for running the firewall service.
78 '';
79 };
80
81 logRefusedConnections = mkOption {
82 type = types.bool;
83 default = true;
84 description = lib.mdDoc ''
85 Whether to log rejected or dropped incoming connections.
86 Note: The logs are found in the kernel logs, i.e. dmesg
87 or journalctl -k.
88 '';
89 };
90
91 logRefusedPackets = mkOption {
92 type = types.bool;
93 default = false;
94 description = lib.mdDoc ''
95 Whether to log all rejected or dropped incoming packets.
96 This tends to give a lot of log messages, so it's mostly
97 useful for debugging.
98 Note: The logs are found in the kernel logs, i.e. dmesg
99 or journalctl -k.
100 '';
101 };
102
103 logRefusedUnicastsOnly = mkOption {
104 type = types.bool;
105 default = true;
106 description = lib.mdDoc ''
107 If {option}`networking.firewall.logRefusedPackets`
108 and this option are enabled, then only log packets
109 specifically directed at this machine, i.e., not broadcasts
110 or multicasts.
111 '';
112 };
113
114 rejectPackets = mkOption {
115 type = types.bool;
116 default = false;
117 description = lib.mdDoc ''
118 If set, refused packets are rejected rather than dropped
119 (ignored). This means that an ICMP "port unreachable" error
120 message is sent back to the client (or a TCP RST packet in
121 case of an existing connection). Rejecting packets makes
122 port scanning somewhat easier.
123 '';
124 };
125
126 trustedInterfaces = mkOption {
127 type = types.listOf types.str;
128 default = [ ];
129 example = [ "enp0s2" ];
130 description = lib.mdDoc ''
131 Traffic coming in from these interfaces will be accepted
132 unconditionally. Traffic from the loopback (lo) interface
133 will always be accepted.
134 '';
135 };
136
137 allowPing = mkOption {
138 type = types.bool;
139 default = true;
140 description = lib.mdDoc ''
141 Whether to respond to incoming ICMPv4 echo requests
142 ("pings"). ICMPv6 pings are always allowed because the
143 larger address space of IPv6 makes network scanning much
144 less effective.
145 '';
146 };
147
148 pingLimit = mkOption {
149 type = types.nullOr (types.separatedString " ");
150 default = null;
151 example = "--limit 1/minute --limit-burst 5";
152 description = lib.mdDoc ''
153 If pings are allowed, this allows setting rate limits on them.
154
155 For the iptables based firewall, it should be set like
156 "--limit 1/minute --limit-burst 5".
157
158 For the nftables based firewall, it should be set like
159 "2/second" or "1/minute burst 5 packets".
160 '';
161 };
162
163 checkReversePath = mkOption {
164 type = types.either types.bool (types.enum [ "strict" "loose" ]);
165 default = true;
166 defaultText = literalMD "`true` except if the iptables based firewall is in use and the kernel lacks rpfilter support";
167 example = "loose";
168 description = lib.mdDoc ''
169 Performs a reverse path filter test on a packet. If a reply
170 to the packet would not be sent via the same interface that
171 the packet arrived on, it is refused.
172
173 If using asymmetric routing or other complicated routing, set
174 this option to loose mode or disable it and setup your own
175 counter-measures.
176
177 This option can be either true (or "strict"), "loose" (only
178 drop the packet if the source address is not reachable via any
179 interface) or false.
180 '';
181 };
182
183 logReversePathDrops = mkOption {
184 type = types.bool;
185 default = false;
186 description = lib.mdDoc ''
187 Logs dropped packets failing the reverse path filter test if
188 the option networking.firewall.checkReversePath is enabled.
189 '';
190 };
191
192 filterForward = mkOption {
193 type = types.bool;
194 default = false;
195 description = lib.mdDoc ''
196 Enable filtering in IP forwarding.
197
198 This option only works with the nftables based firewall.
199 '';
200 };
201
202 connectionTrackingModules = mkOption {
203 type = types.listOf types.str;
204 default = [ ];
205 example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
206 description = lib.mdDoc ''
207 List of connection-tracking helpers that are auto-loaded.
208 The complete list of possible values is given in the example.
209
210 As helpers can pose as a security risk, it is advised to
211 set this to an empty list and disable the setting
212 networking.firewall.autoLoadConntrackHelpers unless you
213 know what you are doing. Connection tracking is disabled
214 by default.
215
216 Loading of helpers is recommended to be done through the
217 CT target. More info:
218 https://home.regit.org/netfilter-en/secure-use-of-helpers/
219 '';
220 };
221
222 autoLoadConntrackHelpers = mkOption {
223 type = types.bool;
224 default = false;
225 description = lib.mdDoc ''
226 Whether to auto-load connection-tracking helpers.
227 See the description at networking.firewall.connectionTrackingModules
228
229 (needs kernel 3.5+)
230 '';
231 };
232
233 extraPackages = mkOption {
234 type = types.listOf types.package;
235 default = [ ];
236 example = literalExpression "[ pkgs.ipset ]";
237 description = lib.mdDoc ''
238 Additional packages to be included in the environment of the system
239 as well as the path of networking.firewall.extraCommands.
240 '';
241 };
242
243 interfaces = mkOption {
244 default = { };
245 type = with types; attrsOf (submodule [{ options = commonOptions; }]);
246 description = lib.mdDoc ''
247 Interface-specific open ports.
248 '';
249 };
250
251 allInterfaces = mkOption {
252 internal = true;
253 visible = false;
254 default = { default = mapAttrs (name: value: cfg.${name}) commonOptions; } // cfg.interfaces;
255 type = with types; attrsOf (submodule [{ options = commonOptions; }]);
256 description = lib.mdDoc ''
257 All open ports.
258 '';
259 };
260 } // commonOptions;
261
262 };
263
264
265 config = mkIf cfg.enable {
266
267 assertions = [
268 {
269 assertion = cfg.filterForward -> config.networking.nftables.enable;
270 message = "filterForward only works with the nftables based firewall";
271 }
272 {
273 assertion = cfg.autoLoadConntrackHelpers -> lib.versionOlder config.boot.kernelPackages.kernel.version "6";
274 message = "conntrack helper autoloading has been removed from kernel 6.0 and newer";
275 }
276 ];
277
278 networking.firewall.trustedInterfaces = [ "lo" ];
279
280 environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
281
282 boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
283 ++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
284 boot.extraModprobeConfig = optionalString cfg.autoLoadConntrackHelpers ''
285 options nf_conntrack nf_conntrack_helper=1
286 '';
287
288 };
289
290}