python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / wireguard.nix
blobe3c3d3ba3c96248b2e21fc96f90f7c7a367ffa38
1 { config, lib, options, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.networking.wireguard;
8   opt = options.networking.wireguard;
10   kernel = config.boot.kernelPackages;
12   # interface options
14   interfaceOpts = { ... }: {
16     options = {
18       ips = mkOption {
19         example = [ "192.168.2.1/24" ];
20         default = [];
21         type = with types; listOf str;
22         description = lib.mdDoc "The IP addresses of the interface.";
23       };
25       privateKey = mkOption {
26         example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
27         type = with types; nullOr str;
28         default = null;
29         description = lib.mdDoc ''
30           Base64 private key generated by {command}`wg genkey`.
32           Warning: Consider using privateKeyFile instead if you do not
33           want to store the key in the world-readable Nix store.
34         '';
35       };
37       generatePrivateKeyFile = mkOption {
38         default = false;
39         type = types.bool;
40         description = lib.mdDoc ''
41           Automatically generate a private key with
42           {command}`wg genkey`, at the privateKeyFile location.
43         '';
44       };
46       privateKeyFile = mkOption {
47         example = "/private/wireguard_key";
48         type = with types; nullOr str;
49         default = null;
50         description = lib.mdDoc ''
51           Private key file as generated by {command}`wg genkey`.
52         '';
53       };
55       listenPort = mkOption {
56         default = null;
57         type = with types; nullOr int;
58         example = 51820;
59         description = lib.mdDoc ''
60           16-bit port for listening. Optional; if not specified,
61           automatically generated based on interface name.
62         '';
63       };
65       preSetup = mkOption {
66         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
67         default = "";
68         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
69         description = lib.mdDoc ''
70           Commands called at the start of the interface setup.
71         '';
72       };
74       postSetup = mkOption {
75         example = literalExpression ''
76           '''printf "nameserver 10.200.100.1" | ''${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0'''
77         '';
78         default = "";
79         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
80         description = lib.mdDoc "Commands called at the end of the interface setup.";
81       };
83       postShutdown = mkOption {
84         example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
85         default = "";
86         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
87         description = lib.mdDoc "Commands called after shutting down the interface.";
88       };
90       table = mkOption {
91         default = "main";
92         type = types.str;
93         description = lib.mdDoc ''
94           The kernel routing table to add this interface's
95           associated routes to. Setting this is useful for e.g. policy routing
96           ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
97           numeric table IDs and table names (/etc/rt_tables) can be used.
98           Defaults to "main".
99         '';
100       };
102       peers = mkOption {
103         default = [];
104         description = lib.mdDoc "Peers linked to the interface.";
105         type = with types; listOf (submodule peerOpts);
106       };
108       allowedIPsAsRoutes = mkOption {
109         example = false;
110         default = true;
111         type = types.bool;
112         description = lib.mdDoc ''
113           Determines whether to add allowed IPs as routes or not.
114         '';
115       };
117       socketNamespace = mkOption {
118         default = null;
119         type = with types; nullOr str;
120         example = "container";
121         description = lib.mdDoc ''The pre-existing network namespace in which the
122         WireGuard interface is created, and which retains the socket even if the
123         interface is moved via {option}`interfaceNamespace`. When
124         `null`, the interface is created in the init namespace.
125         See [documentation](https://www.wireguard.com/netns/).
126         '';
127       };
129       interfaceNamespace = mkOption {
130         default = null;
131         type = with types; nullOr str;
132         example = "init";
133         description = lib.mdDoc ''The pre-existing network namespace the WireGuard
134         interface is moved to. The special value `init` means
135         the init namespace. When `null`, the interface is not
136         moved.
137         See [documentation](https://www.wireguard.com/netns/).
138         '';
139       };
141       fwMark = mkOption {
142         default = null;
143         type = with types; nullOr str;
144         example = "0x6e6978";
145         description = lib.mdDoc ''
146           Mark all wireguard packets originating from
147           this interface with the given firewall mark. The firewall mark can be
148           used in firewalls or policy routing to filter the wireguard packets.
149           This can be useful for setup where all traffic goes through the
150           wireguard tunnel, because the wireguard packets need to be routed
151           differently.
152         '';
153       };
155       mtu = mkOption {
156         default = null;
157         type = with types; nullOr int;
158         example = 1280;
159         description = lib.mdDoc ''
160           Set the maximum transmission unit in bytes for the wireguard
161           interface. Beware that the wireguard packets have a header that may
162           add up to 80 bytes to the mtu. By default, the MTU is (1500 - 80) =
163           1420. However, if the MTU of the upstream network is lower, the MTU
164           of the wireguard network has to be adjusted as well.
165         '';
166       };
167     };
169   };
171   # peer options
173   peerOpts = {
175     options = {
177       publicKey = mkOption {
178         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
179         type = types.str;
180         description = lib.mdDoc "The base64 public key of the peer.";
181       };
183       presharedKey = mkOption {
184         default = null;
185         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
186         type = with types; nullOr str;
187         description = lib.mdDoc ''
188           Base64 preshared key generated by {command}`wg genpsk`.
189           Optional, and may be omitted. This option adds an additional layer of
190           symmetric-key cryptography to be mixed into the already existing
191           public-key cryptography, for post-quantum resistance.
193           Warning: Consider using presharedKeyFile instead if you do not
194           want to store the key in the world-readable Nix store.
195         '';
196       };
198       presharedKeyFile = mkOption {
199         default = null;
200         example = "/private/wireguard_psk";
201         type = with types; nullOr str;
202         description = lib.mdDoc ''
203           File pointing to preshared key as generated by {command}`wg genpsk`.
204           Optional, and may be omitted. This option adds an additional layer of
205           symmetric-key cryptography to be mixed into the already existing
206           public-key cryptography, for post-quantum resistance.
207         '';
208       };
210       allowedIPs = mkOption {
211         example = [ "10.192.122.3/32" "10.192.124.1/24" ];
212         type = with types; listOf str;
213         description = lib.mdDoc ''List of IP (v4 or v6) addresses with CIDR masks from
214         which this peer is allowed to send incoming traffic and to which
215         outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
216         be specified for matching all IPv4 addresses, and ::/0 may be specified
217         for matching all IPv6 addresses.'';
218       };
220       endpoint = mkOption {
221         default = null;
222         example = "demo.wireguard.io:12913";
223         type = with types; nullOr str;
224         description = lib.mdDoc ''
225           Endpoint IP or hostname of the peer, followed by a colon,
226           and then a port number of the peer.
228           Warning for endpoints with changing IPs:
229           The WireGuard kernel side cannot perform DNS resolution.
230           Thus DNS resolution is done once by the `wg` userspace
231           utility, when setting up WireGuard. Consequently, if the IP address
232           behind the name changes, WireGuard will not notice.
233           This is especially common for dynamic-DNS setups, but also applies to
234           any other DNS-based setup.
235           If you do not use IP endpoints, you likely want to set
236           {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
237           to refresh the IPs periodically.
238         '';
239       };
241       dynamicEndpointRefreshSeconds = mkOption {
242         default = 0;
243         example = 5;
244         type = with types; int;
245         description = lib.mdDoc ''
246           Periodically re-execute the `wg` utility every
247           this many seconds in order to let WireGuard notice DNS / hostname
248           changes.
250           Setting this to `0` disables periodic reexecution.
251         '';
252       };
254       persistentKeepalive = mkOption {
255         default = null;
256         type = with types; nullOr int;
257         example = 25;
258         description = lib.mdDoc ''This is optional and is by default off, because most
259         users will not need it. It represents, in seconds, between 1 and 65535
260         inclusive, how often to send an authenticated empty packet to the peer,
261         for the purpose of keeping a stateful firewall or NAT mapping valid
262         persistently. For example, if the interface very rarely sends traffic,
263         but it might at anytime receive traffic from a peer, and it is behind
264         NAT, the interface might benefit from having a persistent keepalive
265         interval of 25 seconds; however, most users will not need this.'';
266       };
268     };
270   };
272   generateKeyServiceUnit = name: values:
273     assert values.generatePrivateKeyFile;
274     nameValuePair "wireguard-${name}-key"
275       {
276         description = "WireGuard Tunnel - ${name} - Key Generator";
277         wantedBy = [ "wireguard-${name}.service" ];
278         requiredBy = [ "wireguard-${name}.service" ];
279         before = [ "wireguard-${name}.service" ];
280         path = with pkgs; [ wireguard-tools ];
282         serviceConfig = {
283           Type = "oneshot";
284           RemainAfterExit = true;
285         };
287         script = ''
288           set -e
290           # If the parent dir does not already exist, create it.
291           # Otherwise, does nothing, keeping existing permisions intact.
292           mkdir -p --mode 0755 "${dirOf values.privateKeyFile}"
294           if [ ! -f "${values.privateKeyFile}" ]; then
295             # Write private key file with atomically-correct permissions.
296             (set -e; umask 077; wg genkey > "${values.privateKeyFile}")
297           fi
298         '';
299       };
301   peerUnitServiceName = interfaceName: publicKey: dynamicRefreshEnabled:
302     let
303       keyToUnitName = replaceChars
304         [ "/" "-"    " "     "+"     "="      ]
305         [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
306       unitName = keyToUnitName publicKey;
307       refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
308     in
309       "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}";
311   generatePeerUnit = { interfaceName, interfaceCfg, peer }:
312     let
313       psk =
314         if peer.presharedKey != null
315           then pkgs.writeText "wg-psk" peer.presharedKey
316           else peer.presharedKeyFile;
317       src = interfaceCfg.socketNamespace;
318       dst = interfaceCfg.interfaceNamespace;
319       ip = nsWrap "ip" src dst;
320       wg = nsWrap "wg" src dst;
321       dynamicRefreshEnabled = peer.dynamicEndpointRefreshSeconds != 0;
322       # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
323       # to avoid that the same service switches `Type` (`oneshot` vs `simple`),
324       # with the intent to make scripting more obvious.
325       serviceName = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled;
326     in nameValuePair serviceName
327       {
328         description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
329         requires = [ "wireguard-${interfaceName}.service" ];
330         wants = [ "network-online.target" ];
331         after = [ "wireguard-${interfaceName}.service" "network-online.target" ];
332         wantedBy = [ "wireguard-${interfaceName}.service" ];
333         environment.DEVICE = interfaceName;
334         environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
335         path = with pkgs; [ iproute2 wireguard-tools ];
337         serviceConfig =
338           if !dynamicRefreshEnabled
339             then
340               {
341                 Type = "oneshot";
342                 RemainAfterExit = true;
343               }
344             else
345               {
346                 Type = "simple"; # re-executes 'wg' indefinitely
347                 # Note that `Type = "oneshot"` services with `RemainAfterExit = true`
348                 # cannot be used with systemd timers (see `man systemd.timer`),
349                 # which is why `simple` with a loop is the best choice here.
350                 # It also makes starting and stopping easiest.
351               };
353         script = let
354           wg_setup = concatStringsSep " " (
355             [ ''${wg} set ${interfaceName} peer "${peer.publicKey}"'' ]
356             ++ optional (psk != null) ''preshared-key "${psk}"''
357             ++ optional (peer.endpoint != null) ''endpoint "${peer.endpoint}"''
358             ++ optional (peer.persistentKeepalive != null) ''persistent-keepalive "${toString peer.persistentKeepalive}"''
359             ++ optional (peer.allowedIPs != []) ''allowed-ips "${concatStringsSep "," peer.allowedIPs}"''
360           );
361           route_setup =
362             optionalString interfaceCfg.allowedIPsAsRoutes
363               (concatMapStringsSep "\n"
364                 (allowedIP:
365                   ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
366                 ) peer.allowedIPs);
367         in ''
368           ${wg_setup}
369           ${route_setup}
371           ${optionalString (peer.dynamicEndpointRefreshSeconds != 0) ''
372             # Re-execute 'wg' periodically to notice DNS / hostname changes.
373             # Note this will not time out on transient DNS failures such as DNS names
374             # because we have set 'WG_ENDPOINT_RESOLUTION_RETRIES=infinity'.
375             # Also note that 'wg' limits its maximum retry delay to 20 seconds as of writing.
376             while ${wg_setup}; do
377               sleep "${toString peer.dynamicEndpointRefreshSeconds}";
378             done
379           ''}
380         '';
382         postStop = let
383           route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes
384             (concatMapStringsSep "\n"
385               (allowedIP:
386                 ''${ip} route delete "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
387               ) peer.allowedIPs);
388         in ''
389           ${wg} set "${interfaceName}" peer "${peer.publicKey}" remove
390           ${route_destroy}
391         '';
392       };
394   # the target is required to start new peer units when they are added
395   generateInterfaceTarget = name: values:
396     let
397       mkPeerUnit = peer: (peerUnitServiceName name peer.publicKey (peer.dynamicEndpointRefreshSeconds != 0)) + ".service";
398     in
399     nameValuePair "wireguard-${name}"
400       rec {
401         description = "WireGuard Tunnel - ${name}";
402         wantedBy = [ "multi-user.target" ];
403         wants = [ "wireguard-${name}.service" ] ++ map mkPeerUnit values.peers;
404         after = wants;
405       };
407   generateInterfaceUnit = name: values:
408     # exactly one way to specify the private key must be set
409     #assert (values.privateKey != null) != (values.privateKeyFile != null);
410     let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
411         src = values.socketNamespace;
412         dst = values.interfaceNamespace;
413         ipPreMove  = nsWrap "ip" src null;
414         ipPostMove = nsWrap "ip" src dst;
415         wg = nsWrap "wg" src dst;
416         ns = if dst == "init" then "1" else dst;
418     in
419     nameValuePair "wireguard-${name}"
420       {
421         description = "WireGuard Tunnel - ${name}";
422         after = [ "network-pre.target" ];
423         wants = [ "network.target" ];
424         before = [ "network.target" ];
425         environment.DEVICE = name;
426         path = with pkgs; [ kmod iproute2 wireguard-tools ];
428         serviceConfig = {
429           Type = "oneshot";
430           RemainAfterExit = true;
431         };
433         script = ''
434           ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
436           ${values.preSetup}
438           ${ipPreMove} link add dev "${name}" type wireguard
439           ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''}
440           ${optionalString (values.mtu != null) ''${ipPreMove} link set "${name}" mtu ${toString values.mtu}''}
442           ${concatMapStringsSep "\n" (ip:
443             ''${ipPostMove} address add "${ip}" dev "${name}"''
444           ) values.ips}
446           ${concatStringsSep " " (
447             [ ''${wg} set "${name}" private-key "${privKey}"'' ]
448             ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
449             ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
450           )}
452           ${ipPostMove} link set up dev "${name}"
454           ${values.postSetup}
455         '';
457         postStop = ''
458           ${ipPostMove} link del dev "${name}"
459           ${values.postShutdown}
460         '';
461       };
463   nsWrap = cmd: src: dst:
464     let
465       nsList = filter (ns: ns != null) [ src dst ];
466       ns = last nsList;
467     in
468       if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd;
473   ###### interface
475   options = {
477     networking.wireguard = {
479       enable = mkOption {
480         description = lib.mdDoc ''
481           Whether to enable WireGuard.
483           Please note that {option}`systemd.network.netdevs` has more features
484           and is better maintained. When building new things, it is advised to
485           use that instead.
486         '';
487         type = types.bool;
488         # 2019-05-25: Backwards compatibility.
489         default = cfg.interfaces != {};
490         defaultText = literalExpression "config.${opt.interfaces} != { }";
491         example = true;
492       };
494       interfaces = mkOption {
495         description = lib.mdDoc ''
496           WireGuard interfaces.
498           Please note that {option}`systemd.network.netdevs` has more features
499           and is better maintained. When building new things, it is advised to
500           use that instead.
501         '';
502         default = {};
503         example = {
504           wg0 = {
505             ips = [ "192.168.20.4/24" ];
506             privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
507             peers = [
508               { allowedIPs = [ "192.168.20.1/32" ];
509                 publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
510                 endpoint   = "demo.wireguard.io:12913"; }
511             ];
512           };
513         };
514         type = with types; attrsOf (submodule interfaceOpts);
515       };
517     };
519   };
522   ###### implementation
524   config = mkIf cfg.enable (let
525     all_peers = flatten
526       (mapAttrsToList (interfaceName: interfaceCfg:
527         map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
528       ) cfg.interfaces);
529   in {
531     assertions = (attrValues (
532         mapAttrs (name: value: {
533           assertion = (value.privateKey != null) != (value.privateKeyFile != null);
534           message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
535         }) cfg.interfaces))
536       ++ (attrValues (
537         mapAttrs (name: value: {
538           assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
539           message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
540         }) cfg.interfaces))
541         ++ map ({ interfaceName, peer, ... }: {
542           assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
543           message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
544         }) all_peers;
546     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
547     environment.systemPackages = [ pkgs.wireguard-tools ];
549     systemd.services =
550       (mapAttrs' generateInterfaceUnit cfg.interfaces)
551       // (listToAttrs (map generatePeerUnit all_peers))
552       // (mapAttrs' generateKeyServiceUnit
553       (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
555       systemd.targets = mapAttrs' generateInterfaceTarget cfg.interfaces;
556     }
557   );