python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / wg-quick.nix
bloba678d743bb77b8f86d87be578c52f4552fb8cb39
1 { config, lib, pkgs, ... }:
3 with lib;
4 let
5   cfg = config.networking.wg-quick;
7   kernel = config.boot.kernelPackages;
9   # interface options
11   interfaceOpts = { ... }: {
12     options = {
14       configFile = mkOption {
15         example = "/secret/wg0.conf";
16         default = null;
17         type = with types; nullOr str;
18         description = lib.mdDoc ''
19           wg-quick .conf file, describing the interface.
20           This overrides any other configuration interface configuration options.
21           See wg-quick manpage for more details.
22         '';
23       };
25       address = mkOption {
26         example = [ "192.168.2.1/24" ];
27         default = [];
28         type = with types; listOf str;
29         description = lib.mdDoc "The IP addresses of the interface.";
30       };
32       autostart = mkOption {
33         description = lib.mdDoc "Whether to bring up this interface automatically during boot.";
34         default = true;
35         example = false;
36         type = types.bool;
37       };
39       dns = mkOption {
40         example = [ "192.168.2.2" ];
41         default = [];
42         type = with types; listOf str;
43         description = lib.mdDoc "The IP addresses of DNS servers to configure.";
44       };
46       privateKey = mkOption {
47         example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
48         type = with types; nullOr str;
49         default = null;
50         description = lib.mdDoc ''
51           Base64 private key generated by {command}`wg genkey`.
53           Warning: Consider using privateKeyFile instead if you do not
54           want to store the key in the world-readable Nix store.
55         '';
56       };
58       privateKeyFile = mkOption {
59         example = "/private/wireguard_key";
60         type = with types; nullOr str;
61         default = null;
62         description = lib.mdDoc ''
63           Private key file as generated by {command}`wg genkey`.
64         '';
65       };
67       listenPort = mkOption {
68         default = null;
69         type = with types; nullOr int;
70         example = 51820;
71         description = lib.mdDoc ''
72           16-bit port for listening. Optional; if not specified,
73           automatically generated based on interface name.
74         '';
75       };
77       preUp = mkOption {
78         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
79         default = "";
80         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
81         description = lib.mdDoc ''
82           Commands called at the start of the interface setup.
83         '';
84       };
86       preDown = mkOption {
87         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
88         default = "";
89         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
90         description = lib.mdDoc ''
91           Command called before the interface is taken down.
92         '';
93       };
95       postUp = mkOption {
96         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
97         default = "";
98         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
99         description = lib.mdDoc ''
100           Commands called after the interface setup.
101         '';
102       };
104       postDown = mkOption {
105         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
106         default = "";
107         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
108         description = lib.mdDoc ''
109           Command called after the interface is taken down.
110         '';
111       };
113       table = mkOption {
114         example = "main";
115         default = null;
116         type = with types; nullOr str;
117         description = lib.mdDoc ''
118           The kernel routing table to add this interface's
119           associated routes to. Setting this is useful for e.g. policy routing
120           ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
121           numeric table IDs and table names (/etc/rt_tables) can be used.
122           Defaults to "main".
123         '';
124       };
126       mtu = mkOption {
127         example = 1248;
128         default = null;
129         type = with types; nullOr int;
130         description = lib.mdDoc ''
131           If not specified, the MTU is automatically determined
132           from the endpoint addresses or the system default route, which is usually
133           a sane choice. However, to manually specify an MTU to override this
134           automatic discovery, this value may be specified explicitly.
135         '';
136       };
138       peers = mkOption {
139         default = [];
140         description = lib.mdDoc "Peers linked to the interface.";
141         type = with types; listOf (submodule peerOpts);
142       };
143     };
144   };
146   # peer options
148   peerOpts = {
149     options = {
150       publicKey = mkOption {
151         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
152         type = types.str;
153         description = lib.mdDoc "The base64 public key to the peer.";
154       };
156       presharedKey = mkOption {
157         default = null;
158         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
159         type = with types; nullOr str;
160         description = lib.mdDoc ''
161           Base64 preshared key generated by {command}`wg genpsk`.
162           Optional, and may be omitted. This option adds an additional layer of
163           symmetric-key cryptography to be mixed into the already existing
164           public-key cryptography, for post-quantum resistance.
166           Warning: Consider using presharedKeyFile instead if you do not
167           want to store the key in the world-readable Nix store.
168         '';
169       };
171       presharedKeyFile = mkOption {
172         default = null;
173         example = "/private/wireguard_psk";
174         type = with types; nullOr str;
175         description = lib.mdDoc ''
176           File pointing to preshared key as generated by {command}`wg genpsk`.
177           Optional, and may be omitted. This option adds an additional layer of
178           symmetric-key cryptography to be mixed into the already existing
179           public-key cryptography, for post-quantum resistance.
180         '';
181       };
183       allowedIPs = mkOption {
184         example = [ "10.192.122.3/32" "10.192.124.1/24" ];
185         type = with types; listOf str;
186         description = lib.mdDoc ''List of IP (v4 or v6) addresses with CIDR masks from
187         which this peer is allowed to send incoming traffic and to which
188         outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
189         be specified for matching all IPv4 addresses, and ::/0 may be specified
190         for matching all IPv6 addresses.'';
191       };
193       endpoint = mkOption {
194         default = null;
195         example = "demo.wireguard.io:12913";
196         type = with types; nullOr str;
197         description = lib.mdDoc ''Endpoint IP or hostname of the peer, followed by a colon,
198         and then a port number of the peer.'';
199       };
201       persistentKeepalive = mkOption {
202         default = null;
203         type = with types; nullOr int;
204         example = 25;
205         description = lib.mdDoc ''This is optional and is by default off, because most
206         users will not need it. It represents, in seconds, between 1 and 65535
207         inclusive, how often to send an authenticated empty packet to the peer,
208         for the purpose of keeping a stateful firewall or NAT mapping valid
209         persistently. For example, if the interface very rarely sends traffic,
210         but it might at anytime receive traffic from a peer, and it is behind
211         NAT, the interface might benefit from having a persistent keepalive
212         interval of 25 seconds; however, most users will not need this.'';
213       };
214     };
215   };
217   writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
219   generateUnit = name: values:
220     assert assertMsg (values.configFile != null || ((values.privateKey != null) != (values.privateKeyFile != null))) "Only one of privateKey, configFile or privateKeyFile may be set";
221     let
222       preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
223       postUp =
224             optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
225             (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++
226             optional (values.postUp != "") values.postUp;
227       postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null;
228       preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null;
229       postDownFile = if values.postDown != "" then writeScriptFile "postDown.sh" values.postDown else null;
230       configDir = pkgs.writeTextFile {
231         name = "config-${name}";
232         executable = false;
233         destination = "/${name}.conf";
234         text =
235         ''
236         [interface]
237         ${concatMapStringsSep "\n" (address:
238           "Address = ${address}"
239         ) values.address}
240         ${concatMapStringsSep "\n" (dns:
241           "DNS = ${dns}"
242         ) values.dns}
243         '' +
244         optionalString (values.table != null) "Table = ${values.table}\n" +
245         optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
246         optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
247         optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
248         optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
249         optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
250         optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
251         optionalString (postDownFile != null) "PostDown = ${postDownFile}\n" +
252         concatMapStringsSep "\n" (peer:
253           assert assertMsg (!((peer.presharedKeyFile != null) && (peer.presharedKey != null))) "Only one of presharedKey or presharedKeyFile may be set";
254           "[Peer]\n" +
255           "PublicKey = ${peer.publicKey}\n" +
256           optionalString (peer.presharedKey != null) "PresharedKey = ${peer.presharedKey}\n" +
257           optionalString (peer.endpoint != null) "Endpoint = ${peer.endpoint}\n" +
258           optionalString (peer.persistentKeepalive != null) "PersistentKeepalive = ${toString peer.persistentKeepalive}\n" +
259           optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep "," peer.allowedIPs}\n"
260         ) values.peers;
261       };
262       configPath =
263         if values.configFile != null then
264           # This uses bind-mounted private tmp folder (/tmp/systemd-private-***)
265           "/tmp/${name}.conf"
266         else
267           "${configDir}/${name}.conf";
268     in
269     nameValuePair "wg-quick-${name}"
270       {
271         description = "wg-quick WireGuard Tunnel - ${name}";
272         requires = [ "network-online.target" ];
273         after = [ "network.target" "network-online.target" ];
274         wantedBy = optional values.autostart "multi-user.target";
275         environment.DEVICE = name;
276         path = [ pkgs.kmod pkgs.wireguard-tools config.networking.resolvconf.package ];
278         serviceConfig = {
279           Type = "oneshot";
280           RemainAfterExit = true;
281         };
283         script = ''
284           ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
285           ${optionalString (values.configFile != null) ''
286             cp ${values.configFile} ${configPath}
287           ''}
288           wg-quick up ${configPath}
289         '';
291         serviceConfig = {
292           # Used to privately store renamed copies of external config files during activation
293           PrivateTmp = true;
294         };
296         preStop = ''
297           wg-quick down ${configPath}
298         '';
299       };
300 in {
302   ###### interface
304   options = {
305     networking.wg-quick = {
306       interfaces = mkOption {
307         description = lib.mdDoc "Wireguard interfaces.";
308         default = {};
309         example = {
310           wg0 = {
311             address = [ "192.168.20.4/24" ];
312             privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
313             peers = [
314               { allowedIPs = [ "192.168.20.1/32" ];
315                 publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
316                 endpoint   = "demo.wireguard.io:12913"; }
317             ];
318           };
319         };
320         type = with types; attrsOf (submodule interfaceOpts);
321       };
322     };
323   };
326   ###### implementation
328   config = mkIf (cfg.interfaces != {}) {
329     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
330     environment.systemPackages = [ pkgs.wireguard-tools ];
331     systemd.services = mapAttrs' generateUnit cfg.interfaces;
333     # Prevent networkd from clearing the rules set by wg-quick when restarted (e.g. when waking up from suspend).
334     systemd.network.config.networkConfig.ManageForeignRoutingPolicyRules = mkDefault false;
336     # WireGuard interfaces should be ignored in determining whether the network is online.
337     systemd.network.wait-online.ignoredInterfaces = builtins.attrNames cfg.interfaces;
338   };