losslesscut-bin: 3.61.1 -> 3.64.0 (#373227)
[NixPkgs.git] / nixos / modules / services / networking / wireguard-networkd.nix
blob60d86ee43befb72870fcbe2420c295b9cbfae83a
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   inherit (lib) types;
10   inherit (lib.attrsets)
11     filterAttrs
12     mapAttrs
13     mapAttrs'
14     mapAttrsToList
15     nameValuePair
16     ;
17   inherit (lib.lists)
18     concatMap
19     concatLists
20     filter
21     flatten
22     ;
23   inherit (lib.modules) mkIf;
24   inherit (lib.options) literalExpression mkOption;
25   inherit (lib.strings) hasInfix replaceStrings;
26   inherit (lib.trivial) flip pipe;
28   removeNulls = filterAttrs (_: v: v != null);
30   escapeCredentialName = input: replaceStrings [ "\\" ] [ "_" ] input;
32   privateKeyCredential = interfaceName: escapeCredentialName "wireguard-${interfaceName}-private-key";
33   presharedKeyCredential =
34     interfaceName: peer: escapeCredentialName "wireguard-${interfaceName}-${peer.name}-preshared-key";
36   interfaceCredentials =
37     interfaceName: interface:
38     [ "${privateKeyCredential interfaceName}:${interface.privateKeyFile}" ]
39     ++ pipe interface.peers [
40       (filter (peer: peer.presharedKeyFile != null))
41       (map (peer: "${presharedKeyCredential interfaceName peer}:${peer.presharedKeyFile}"))
42     ];
44   generateNetdev =
45     name: interface:
46     nameValuePair "40-${name}" {
47       netdevConfig = removeNulls {
48         Kind = "wireguard";
49         Name = name;
50         MTUBytes = interface.mtu;
51       };
52       wireguardConfig = removeNulls {
53         PrivateKey = "@${privateKeyCredential name}";
54         ListenPort = interface.listenPort;
55         FirewallMark = interface.fwMark;
56         RouteTable = if interface.allowedIPsAsRoutes then interface.table else null;
57         RouteMetric = interface.metric;
58       };
59       wireguardPeers = map (generateWireguardPeer name) interface.peers;
60     };
62   generateWireguardPeer =
63     interfaceName: peer:
64     removeNulls {
65       PublicKey = peer.publicKey;
66       PresharedKey =
67         if peer.presharedKeyFile == null then null else "@${presharedKeyCredential interfaceName peer}";
68       AllowedIPs = peer.allowedIPs;
69       Endpoint = peer.endpoint;
70       PersistentKeepalive = peer.persistentKeepalive;
71     };
73   generateNetwork = name: interface: {
74     matchConfig.Name = name;
75     address = interface.ips;
76   };
78   cfg = config.networking.wireguard;
80   refreshEnabledInterfaces = filterAttrs (
81     name: interface: interface.dynamicEndpointRefreshSeconds != 0
82   ) cfg.interfaces;
84   generateRefreshTimer =
85     name: interface:
86     nameValuePair "wireguard-dynamic-refresh-${name}" {
87       partOf = [ "wireguard-dynamic-refresh-${name}.service" ];
88       wantedBy = [ "timers.target" ];
89       description = "Wireguard dynamic endpoint refresh (${name}) timer";
90       timerConfig.OnBootSec = interface.dynamicEndpointRefreshSeconds;
91       timerConfig.OnUnitInactiveSec = interface.dynamicEndpointRefreshSeconds;
92     };
94   generateRefreshService =
95     name: interface:
96     nameValuePair "wireguard-dynamic-refresh-${name}" {
97       description = "Wireguard dynamic endpoint refresh (${name})";
98       after = [ "network-online.target" ];
99       wants = [ "network-online.target" ];
100       path = with pkgs; [
101         iproute2
102         systemd
103       ];
104       # networkd doesn't provide a mechanism for refreshing endpoints.
105       # See: https://github.com/systemd/systemd/issues/9911
106       # This hack does the job but takes down the whole interface to do it.
107       script = ''
108         ip link delete ${name}
109         networkctl reload
110       '';
111     };
115   meta.maintainers = [ lib.maintainers.majiir ];
117   options.networking.wireguard = {
118     useNetworkd = mkOption {
119       default = config.networking.useNetworkd;
120       defaultText = literalExpression "config.networking.useNetworkd";
121       type = types.bool;
122       description = ''
123         Whether to use networkd as the network configuration backend for
124         Wireguard instead of the legacy script-based system.
126         ::: {.warning}
127         Some options have slightly different behavior with the networkd and
128         script-based backends. Check the documentation for each Wireguard
129         option you use before enabling this option.
130         :::
131       '';
132     };
133   };
135   config = mkIf (cfg.enable && cfg.useNetworkd) {
137     # TODO: Some of these options may be possible to support in networkd.
138     #
139     # privateKey and presharedKey are trivial to support, but we deliberately
140     # don't in order to discourage putting secrets in the /nix store.
141     #
142     # generatePrivateKeyFile can be supported if we can order a service before
143     # networkd configures interfaces. There is also a systemd feature request
144     # for key generation: https://github.com/systemd/systemd/issues/14282
145     #
146     # preSetup, postSetup, preShutdown and postShutdown may be possible, but
147     # networkd is not likely to support script hooks like this directly. See:
148     # https://github.com/systemd/systemd/issues/11629
149     #
150     # socketNamespace and interfaceNamespace can be implemented once networkd
151     # supports setting a netdev's namespace. See:
152     # https://github.com/systemd/systemd/issues/11103
153     # https://github.com/systemd/systemd/pull/14915
155     assertions = concatLists (
156       flip mapAttrsToList cfg.interfaces (
157         name: interface:
158         [
159           # Interface assertions
160           {
161             assertion = interface.privateKey == null;
162             message = "networking.wireguard.interfaces.${name}.privateKey cannot be used with networkd. Use privateKeyFile instead.";
163           }
164           {
165             assertion = !interface.generatePrivateKeyFile;
166             message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile cannot be used with networkd.";
167           }
168           {
169             assertion = interface.preSetup == "";
170             message = "networking.wireguard.interfaces.${name}.preSetup cannot be used with networkd.";
171           }
172           {
173             assertion = interface.postSetup == "";
174             message = "networking.wireguard.interfaces.${name}.postSetup cannot be used with networkd.";
175           }
176           {
177             assertion = interface.preShutdown == "";
178             message = "networking.wireguard.interfaces.${name}.preShutdown cannot be used with networkd.";
179           }
180           {
181             assertion = interface.postShutdown == "";
182             message = "networking.wireguard.interfaces.${name}.postShutdown cannot be used with networkd.";
183           }
184           {
185             assertion = interface.socketNamespace == null;
186             message = "networking.wireguard.interfaces.${name}.socketNamespace cannot be used with networkd.";
187           }
188           {
189             assertion = interface.interfaceNamespace == null;
190             message = "networking.wireguard.interfaces.${name}.interfaceNamespace cannot be used with networkd.";
191           }
192         ]
193         ++ flip concatMap interface.ips (ip: [
194           # IP assertions
195           {
196             assertion = hasInfix "/" ip;
197             message = "networking.wireguard.interfaces.${name}.ips value \"${ip}\" requires a subnet (e.g. 192.0.2.1/32) with networkd.";
198           }
199         ])
200         ++ flip concatMap interface.peers (peer: [
201           # Peer assertions
202           {
203             assertion = peer.presharedKey == null;
204             message = "networking.wireguard.interfaces.${name}.peers[].presharedKey cannot be used with networkd. Use presharedKeyFile instead.";
205           }
206           {
207             assertion = peer.dynamicEndpointRefreshSeconds == null;
208             message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshSeconds cannot be used with networkd. Use networking.wireguard.interfaces.${name}.dynamicEndpointRefreshSeconds instead.";
209           }
210           {
211             assertion = peer.dynamicEndpointRefreshRestartSeconds == null;
212             message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshRestartSeconds cannot be used with networkd.";
213           }
214         ])
215       )
216     );
218     systemd.network = {
219       enable = true;
220       netdevs = mapAttrs' generateNetdev cfg.interfaces;
221       networks = mapAttrs generateNetwork cfg.interfaces;
222     };
224     systemd.timers = mapAttrs' generateRefreshTimer refreshEnabledInterfaces;
225     systemd.services = (mapAttrs' generateRefreshService refreshEnabledInterfaces) // {
226       systemd-networkd.serviceConfig.LoadCredential = flatten (
227         mapAttrsToList interfaceCredentials cfg.interfaces
228       );
229     };
230   };