waylyrics: 0.3.16 -> 0.3.20 (#364626)
[NixPkgs.git] / nixos / modules / services / networking / wireguard.nix
bloba3308a1cd396449fb674f3511a72be627a262999
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 = "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 = ''
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 = ''
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 = ''
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 = ''
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 = ''
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 = "Commands called at the end of the interface setup.";
81       };
83       preShutdown = mkOption {
84         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
85         default = "";
86         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
87         description = ''
88           Commands called before shutting down the interface.
89         '';
90       };
92       postShutdown = mkOption {
93         example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
94         default = "";
95         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
96         description = "Commands called after shutting down the interface.";
97       };
99       table = mkOption {
100         default = "main";
101         type = types.str;
102         description = ''
103           The kernel routing table to add this interface's
104           associated routes to. Setting this is useful for e.g. policy routing
105           ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
106           numeric table IDs and table names (/etc/rt_tables) can be used.
107           Defaults to "main".
108         '';
109       };
111       peers = mkOption {
112         default = [];
113         description = "Peers linked to the interface.";
114         type = with types; listOf (submodule peerOpts);
115       };
117       allowedIPsAsRoutes = mkOption {
118         example = false;
119         default = true;
120         type = types.bool;
121         description = ''
122           Determines whether to add allowed IPs as routes or not.
123         '';
124       };
126       socketNamespace = mkOption {
127         default = null;
128         type = with types; nullOr str;
129         example = "container";
130         description = ''The pre-existing network namespace in which the
131         WireGuard interface is created, and which retains the socket even if the
132         interface is moved via {option}`interfaceNamespace`. When
133         `null`, the interface is created in the init namespace.
134         See [documentation](https://www.wireguard.com/netns/).
135         '';
136       };
138       interfaceNamespace = mkOption {
139         default = null;
140         type = with types; nullOr str;
141         example = "init";
142         description = ''The pre-existing network namespace the WireGuard
143         interface is moved to. The special value `init` means
144         the init namespace. When `null`, the interface is not
145         moved.
146         See [documentation](https://www.wireguard.com/netns/).
147         '';
148       };
150       fwMark = mkOption {
151         default = null;
152         type = with types; nullOr str;
153         example = "0x6e6978";
154         description = ''
155           Mark all wireguard packets originating from
156           this interface with the given firewall mark. The firewall mark can be
157           used in firewalls or policy routing to filter the wireguard packets.
158           This can be useful for setup where all traffic goes through the
159           wireguard tunnel, because the wireguard packets need to be routed
160           differently.
161         '';
162       };
164       mtu = mkOption {
165         default = null;
166         type = with types; nullOr int;
167         example = 1280;
168         description = ''
169           Set the maximum transmission unit in bytes for the wireguard
170           interface. Beware that the wireguard packets have a header that may
171           add up to 80 bytes to the mtu. By default, the MTU is (1500 - 80) =
172           1420. However, if the MTU of the upstream network is lower, the MTU
173           of the wireguard network has to be adjusted as well.
174         '';
175       };
177       metric = mkOption {
178         default = null;
179         type = with types; nullOr int;
180         example = 700;
181         description = ''
182           Set the metric of routes related to this Wireguard interface.
183         '';
184       };
186       dynamicEndpointRefreshSeconds = mkOption {
187         default = 0;
188         example = 300;
189         type = with types; int;
190         description = ''
191           Periodically refresh the endpoint hostname or address for all peers.
192           Allows WireGuard to notice DNS and IPv4/IPv6 connectivity changes.
193           This option can be set or overridden for individual peers.
195           Setting this to `0` disables periodic refresh.
197           ::: {.warning}
198           When {option}`networking.wireguard.useNetworkd` is enabled, this
199           option deletes the Wireguard interface and brings it back up by
200           reconfiguring the network with `networkctl reload` on every refresh.
201           This could have adverse effects on your network and cause brief
202           connectivity blips. See [systemd/systemd#9911](https://github.com/systemd/systemd/issues/9911)
203           for an upstream feature request that can make this less hacky.
204           :::
205         '';
206       };
207     };
209   };
211   # peer options
213   peerOpts = self: {
215     options = {
217       name = mkOption {
218         default =
219           replaceStrings
220             [ "/" "-"     " "     "+"     "="     ]
221             [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ]
222             self.config.publicKey;
223         defaultText = literalExpression "publicKey";
224         example = "bernd";
225         type = types.str;
226         description = "Name used to derive peer unit name.";
227       };
229       publicKey = mkOption {
230         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
231         type = types.singleLineStr;
232         description = "The base64 public key of the peer.";
233       };
235       presharedKey = mkOption {
236         default = null;
237         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
238         type = with types; nullOr str;
239         description = ''
240           Base64 preshared key generated by {command}`wg genpsk`.
241           Optional, and may be omitted. This option adds an additional layer of
242           symmetric-key cryptography to be mixed into the already existing
243           public-key cryptography, for post-quantum resistance.
245           Warning: Consider using presharedKeyFile instead if you do not
246           want to store the key in the world-readable Nix store.
247         '';
248       };
250       presharedKeyFile = mkOption {
251         default = null;
252         example = "/private/wireguard_psk";
253         type = with types; nullOr str;
254         description = ''
255           File pointing to preshared key as generated by {command}`wg genpsk`.
256           Optional, and may be omitted. This option adds an additional layer of
257           symmetric-key cryptography to be mixed into the already existing
258           public-key cryptography, for post-quantum resistance.
259         '';
260       };
262       allowedIPs = mkOption {
263         example = [ "10.192.122.3/32" "10.192.124.1/24" ];
264         type = with types; listOf str;
265         description = ''List of IP (v4 or v6) addresses with CIDR masks from
266         which this peer is allowed to send incoming traffic and to which
267         outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
268         be specified for matching all IPv4 addresses, and ::/0 may be specified
269         for matching all IPv6 addresses.'';
270       };
272       endpoint = mkOption {
273         default = null;
274         example = "demo.wireguard.io:12913";
275         type = with types; nullOr str;
276         description = ''
277           Endpoint IP or hostname of the peer, followed by a colon,
278           and then a port number of the peer.
280           Warning for endpoints with changing IPs:
281           The WireGuard kernel side cannot perform DNS resolution.
282           Thus DNS resolution is done once by the `wg` userspace
283           utility, when setting up WireGuard. Consequently, if the IP address
284           behind the name changes, WireGuard will not notice.
285           This is especially common for dynamic-DNS setups, but also applies to
286           any other DNS-based setup.
287           If you do not use IP endpoints, you likely want to set
288           {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
289           to refresh the IPs periodically.
290         '';
291       };
293       dynamicEndpointRefreshSeconds = mkOption {
294         default = null;
295         defaultText = literalExpression "config.networking.wireguard.interfaces.<name>.dynamicEndpointRefreshSeconds";
296         example = 5;
297         type = with types; nullOr int;
298         description = ''
299           Periodically re-execute the `wg` utility every
300           this many seconds in order to let WireGuard notice DNS / hostname
301           changes.
303           Setting this to `0` disables periodic reexecution.
305           ::: {.note}
306           This peer-level setting is not available when {option}`networking.wireguard.useNetworkd`
307           is enabled. The interface-level setting may be used instead.
308           :::
309         '';
310       };
312       dynamicEndpointRefreshRestartSeconds = mkOption {
313         default = null;
314         example = 5;
315         type = with types; nullOr ints.unsigned;
316         description = ''
317           When the dynamic endpoint refresh that is configured via
318           dynamicEndpointRefreshSeconds exits (likely due to a failure),
319           restart that service after this many seconds.
321           If set to `null` the value of
322           {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
323           will be used as the default.
324         '';
325       };
327       persistentKeepalive = mkOption {
328         default = null;
329         type = with types; nullOr int;
330         example = 25;
331         description = ''This is optional and is by default off, because most
332         users will not need it. It represents, in seconds, between 1 and 65535
333         inclusive, how often to send an authenticated empty packet to the peer,
334         for the purpose of keeping a stateful firewall or NAT mapping valid
335         persistently. For example, if the interface very rarely sends traffic,
336         but it might at anytime receive traffic from a peer, and it is behind
337         NAT, the interface might benefit from having a persistent keepalive
338         interval of 25 seconds; however, most users will not need this.'';
339       };
341     };
343   };
345   generateKeyServiceUnit = name: values:
346     assert values.generatePrivateKeyFile;
347     nameValuePair "wireguard-${name}-key"
348       {
349         description = "WireGuard Tunnel - ${name} - Key Generator";
350         wantedBy = [ "wireguard-${name}.service" ];
351         requiredBy = [ "wireguard-${name}.service" ];
352         before = [ "wireguard-${name}.service" ];
353         path = with pkgs; [ wireguard-tools ];
355         serviceConfig = {
356           Type = "oneshot";
357           RemainAfterExit = true;
358         };
360         script = ''
361           set -e
363           # If the parent dir does not already exist, create it.
364           # Otherwise, does nothing, keeping existing permissions intact.
365           mkdir -p --mode 0755 "${dirOf values.privateKeyFile}"
367           if [ ! -f "${values.privateKeyFile}" ]; then
368             # Write private key file with atomically-correct permissions.
369             (set -e; umask 077; wg genkey > "${values.privateKeyFile}")
370           fi
371         '';
372       };
374   peerUnitServiceName = interfaceName: peerName: dynamicRefreshEnabled:
375     let
376       refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
377     in
378       "wireguard-${interfaceName}-peer-${peerName}${refreshSuffix}";
380   dynamicRefreshSeconds = interfaceCfg: peer:
381     if peer.dynamicEndpointRefreshSeconds != null
382     then peer.dynamicEndpointRefreshSeconds
383     else interfaceCfg.dynamicEndpointRefreshSeconds;
385   generatePeerUnit = { interfaceName, interfaceCfg, peer }:
386     let
387       psk =
388         if peer.presharedKey != null
389           then pkgs.writeText "wg-psk" peer.presharedKey
390           else peer.presharedKeyFile;
391       src = interfaceCfg.socketNamespace;
392       dst = interfaceCfg.interfaceNamespace;
393       ip = nsWrap "ip" src dst;
394       wg = nsWrap "wg" src dst;
395       dynamicEndpointRefreshSeconds = dynamicRefreshSeconds interfaceCfg peer;
396       dynamicRefreshEnabled = dynamicEndpointRefreshSeconds != 0;
397       # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
398       # to avoid that the same service switches `Type` (`oneshot` vs `simple`),
399       # with the intent to make scripting more obvious.
400       serviceName = peerUnitServiceName interfaceName peer.name dynamicRefreshEnabled;
401     in nameValuePair serviceName
402       {
403         description = "WireGuard Peer - ${interfaceName} - ${peer.name}"
404           + optionalString (peer.name != peer.publicKey) " (${peer.publicKey})";
405         requires = [ "wireguard-${interfaceName}.service" ];
406         wants = [ "network-online.target" ];
407         after = [ "wireguard-${interfaceName}.service" "network-online.target" ];
408         wantedBy = [ "wireguard-${interfaceName}.service" ];
409         environment.DEVICE = interfaceName;
410         environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
411         path = with pkgs; [ iproute2 wireguard-tools ];
413         serviceConfig =
414           if !dynamicRefreshEnabled
415             then
416               {
417                 Type = "oneshot";
418                 RemainAfterExit = true;
419               }
420             else
421               {
422                 Type = "simple"; # re-executes 'wg' indefinitely
423                 # Note that `Type = "oneshot"` services with `RemainAfterExit = true`
424                 # cannot be used with systemd timers (see `man systemd.timer`),
425                 # which is why `simple` with a loop is the best choice here.
426                 # It also makes starting and stopping easiest.
427                 #
428                 # Restart if the service exits (e.g. when wireguard gives up after "Name or service not known" dns failures):
429                 Restart = "always";
430                 RestartSec = if null != peer.dynamicEndpointRefreshRestartSeconds
431                              then peer.dynamicEndpointRefreshRestartSeconds
432                              else dynamicEndpointRefreshSeconds;
433               };
434         unitConfig = lib.optionalAttrs dynamicRefreshEnabled {
435           StartLimitIntervalSec = 0;
436         };
438         script = let
439           wg_setup = concatStringsSep " " (
440             [ ''${wg} set ${interfaceName} peer "${peer.publicKey}"'' ]
441             ++ optional (psk != null) ''preshared-key "${psk}"''
442             ++ optional (peer.endpoint != null) ''endpoint "${peer.endpoint}"''
443             ++ optional (peer.persistentKeepalive != null) ''persistent-keepalive "${toString peer.persistentKeepalive}"''
444             ++ optional (peer.allowedIPs != []) ''allowed-ips "${concatStringsSep "," peer.allowedIPs}"''
445           );
446           route_setup =
447             optionalString interfaceCfg.allowedIPsAsRoutes
448               (concatMapStringsSep "\n"
449                 (allowedIP:
450                   ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}" ${optionalString (interfaceCfg.metric != null) "metric ${toString interfaceCfg.metric}"}''
451                 ) peer.allowedIPs);
452         in ''
453           ${wg_setup}
454           ${route_setup}
456           ${optionalString (dynamicEndpointRefreshSeconds != 0) ''
457             # Re-execute 'wg' periodically to notice DNS / hostname changes.
458             # Note this will not time out on transient DNS failures such as DNS names
459             # because we have set 'WG_ENDPOINT_RESOLUTION_RETRIES=infinity'.
460             # Also note that 'wg' limits its maximum retry delay to 20 seconds as of writing.
461             while ${wg_setup}; do
462               sleep "${toString dynamicEndpointRefreshSeconds}";
463             done
464           ''}
465         '';
467         postStop = let
468           route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes
469             (concatMapStringsSep "\n"
470               (allowedIP:
471                 ''${ip} route delete "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
472               ) peer.allowedIPs);
473         in ''
474           ${wg} set "${interfaceName}" peer "${peer.publicKey}" remove
475           ${route_destroy}
476         '';
477       };
479   # the target is required to start new peer units when they are added
480   generateInterfaceTarget = name: values:
481     let
482       mkPeerUnit = peer: (peerUnitServiceName name peer.name (dynamicRefreshSeconds values peer != 0)) + ".service";
483     in
484     nameValuePair "wireguard-${name}"
485       rec {
486         description = "WireGuard Tunnel - ${name}";
487         wantedBy = [ "multi-user.target" ];
488         wants = [ "wireguard-${name}.service" ] ++ map mkPeerUnit values.peers;
489         after = wants;
490       };
492   generateInterfaceUnit = name: values:
493     # exactly one way to specify the private key must be set
494     #assert (values.privateKey != null) != (values.privateKeyFile != null);
495     let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
496         src = values.socketNamespace;
497         dst = values.interfaceNamespace;
498         ipPreMove  = nsWrap "ip" src null;
499         ipPostMove = nsWrap "ip" src dst;
500         wg = nsWrap "wg" src dst;
501         ns = if dst == "init" then "1" else dst;
503     in
504     nameValuePair "wireguard-${name}"
505       {
506         description = "WireGuard Tunnel - ${name}";
507         after = [ "network-pre.target" ];
508         wants = [ "network.target" ];
509         before = [ "network.target" ];
510         environment.DEVICE = name;
511         path = with pkgs; [ kmod iproute2 wireguard-tools ];
513         serviceConfig = {
514           Type = "oneshot";
515           RemainAfterExit = true;
516         };
518         script = concatStringsSep "\n" (
519           optional (!config.boot.isContainer) "modprobe wireguard || true"
520           ++ [
521             values.preSetup
522             ''${ipPreMove} link add dev "${name}" type wireguard''
523           ]
524           ++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''
525           ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
526           ++ (map (ip:
527             ''${ipPostMove} address add "${ip}" dev "${name}"''
528           ) values.ips)
529           ++ [
530             (concatStringsSep " " (
531             [ ''${wg} set "${name}" private-key "${privKey}"'' ]
532             ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
533             ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
534             ))
535             ''${ipPostMove} link set up dev "${name}"''
536             values.postSetup
537           ]
538           );
540         postStop = ''
541           ${values.preShutdown}
542           ${ipPostMove} link del dev "${name}"
543           ${values.postShutdown}
544         '';
545       };
547   nsWrap = cmd: src: dst:
548     let
549       nsList = filter (ns: ns != null) [ src dst ];
550       ns = last nsList;
551     in
552       if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd;
557   ###### interface
559   options = {
561     networking.wireguard = {
563       enable = mkOption {
564         description = ''
565           Whether to enable WireGuard.
567           ::: {.note}
568           By default, this module is powered by a script-based backend. You can
569           enable the networkd backend with {option}`networking.wireguard.useNetworkd`.
570           :::
571         '';
572         type = types.bool;
573         # 2019-05-25: Backwards compatibility.
574         default = cfg.interfaces != {};
575         defaultText = literalExpression "config.${opt.interfaces} != { }";
576         example = true;
577       };
579       interfaces = mkOption {
580         description = ''
581           WireGuard interfaces.
582         '';
583         default = {};
584         example = {
585           wg0 = {
586             ips = [ "192.168.20.4/24" ];
587             privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
588             peers = [
589               { allowedIPs = [ "192.168.20.1/32" ];
590                 publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
591                 endpoint   = "demo.wireguard.io:12913"; }
592             ];
593           };
594         };
595         type = with types; attrsOf (submodule interfaceOpts);
596       };
598     };
600   };
603   ###### implementation
605   config = mkIf cfg.enable (let
606     all_peers = flatten
607       (mapAttrsToList (interfaceName: interfaceCfg:
608         map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
609       ) cfg.interfaces);
610   in {
612     assertions = (attrValues (
613         mapAttrs (name: value: {
614           assertion = (value.privateKey != null) != (value.privateKeyFile != null);
615           message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
616         }) cfg.interfaces))
617       ++ (attrValues (
618         mapAttrs (name: value: {
619           assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
620           message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
621         }) cfg.interfaces))
622         ++ map ({ interfaceName, peer, ... }: {
623           assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
624           message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
625         }) all_peers;
627     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
628     boot.kernelModules = [ "wireguard" ];
629     environment.systemPackages = [ pkgs.wireguard-tools ];
631     systemd.services = mkIf (!cfg.useNetworkd) (
632       (mapAttrs' generateInterfaceUnit cfg.interfaces)
633       // (listToAttrs (map generatePeerUnit all_peers))
634       // (mapAttrs' generateKeyServiceUnit
635       (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces)));
637       systemd.targets = mkIf (!cfg.useNetworkd) (mapAttrs' generateInterfaceTarget cfg.interfaces);
638     }
639   );