]> Git — Sourcephile - julm/julm-nix.git/blob - nixos/modules/services/networking/wireguard.nix
nix: format with nixfmt-rfc-style
[julm/julm-nix.git] / nixos / modules / services / networking / wireguard.nix
1 {
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7 }:
8 with lib;
9 let
10 cfg = config.networking.wireguard;
11 opt = options.networking.wireguard;
12 interfaceOpts =
13 { config, ... }:
14 {
15 options = {
16 peers = mkOption {
17 type = with types; listOf (submodule peerOpts);
18 };
19 peersAnnouncing = {
20 enable = mkEnableOption ''
21 announcing of peers' endpoints.
22 '';
23 listenPort = mkOption {
24 type = types.port;
25 defaultText = "<option>networking.wireguard.listenPort</option> or 51820 if null";
26 description = ''
27 TCP port on which to listen for peers queries for other peers' endpoints.
28
29 <warning><para>
30 This exposes the public key and current (or last known) endpoint address of all the peers
31 configured in the WireGuard interface, to all the peers,
32 and to any host connected to the peers or announcing peer
33 able to query the WireGuard interface
34 by spoofing the allowedIPs of any peer.
35 </para></warning>
36 '';
37 };
38 };
39 };
40 config = {
41 peersAnnouncing = {
42 listenPort = mkDefault (if config.listenPort == null then 51820 else config.listenPort);
43 };
44 };
45 };
46 peerOpts =
47 { config, ... }:
48 {
49 options = {
50 endpointsUpdater = {
51 enable = mkEnableOption ''
52 receiving the enpoint of other peers from a periodic TCP connection.
53 That connection is meant to be to a peer using <option>networking.wireguard.peersAnnouncing.enable</option>.
54
55 This is especially useful to punch-through non-symmetric NATs
56 effectively establishing peer-to-peer connectivity:
57 the peers initiate a Wireguard connection to the announcing peer,
58 then use that Wireguard tunnel to query the endpoints of the other peers
59 to establish Wireguard tunnels to them,
60 using UDP hole punching when both peers are behind non-symmetric NATs.
61
62 Note that it can also work for two peers behind the same NAT
63 if that NAT can reflect (to its internal network) connections
64 from its internal network to its external IP address,
65 which may require to set persistentKeepalive as low as 1
66 and/or redirecting at least one host's WireGuard port, eg. with UPnP.
67 '';
68 addr = mkOption {
69 type = types.str;
70 defaultText = "address of the first item of the peer's <option>allowedIPs</option>";
71 description = ''
72 Address at which to contact the announcing peer,
73 configured on the announcing peer's <option>networking.wireguard.ips</option>.
74 '';
75 };
76 port = mkOption {
77 type = types.port;
78 defaultText = "port of the peer's endpoint";
79 description = ''
80 TCP port on which to contact the peer announcing other peers.
81 This is configured on the announcing peer's <option>networking.wireguard.peersAnnouncing.listenPort</option>.
82 '';
83 };
84 refreshSeconds = mkOption {
85 type = with types; int;
86 example = 120;
87 default = 60;
88 description = ''
89 Time to wait between two successive queries of the endpoints known by the peer.
90 '';
91 };
92 };
93 };
94 config = {
95 endpointsUpdater = {
96 addr = mkDefault (head (builtins.match "^\([^/]*\).*$" (head (config.allowedIPs))));
97 port = mkDefault (toInt (head (builtins.match "^.*:\([0-9]*\)$" config.endpoint)));
98 };
99 };
100 };
101
102 keyToUnitName = replaceStrings [ "/" "-" " " "+" "=" ] [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
103
104 peerUnitServiceName =
105 interfaceName: publicKey: dynamicRefreshEnabled:
106 let
107 unitName = keyToUnitName publicKey;
108 refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
109 in
110 "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}";
111
112 # See:
113 # - systemd-analyze security wireguard-${iface}-peers-announcing@
114 # - systemd-analyze security wireguard-${iface}-endpoints-updater-${public_key}.service
115 # Note that PrivateUsers=true would be too restrictive wrt. capabilities.
116 peerUpdateSecurity = {
117 IPAddressDeny = "any";
118 # For running wg(1)
119 AmbientCapabilities = [ "CAP_NET_ADMIN" ];
120 CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
121 RestrictNamespaces = true;
122 DynamicUser = true;
123 NoNewPrivileges = true;
124 PrivateDevices = true;
125 ProtectClock = true;
126 ProtectControlGroups = true;
127 ProtectHome = true;
128 ProtectKernelLogs = true;
129 ProtectKernelModules = true;
130 ProtectKernelTunables = true;
131 ProtectProc = "invisible";
132 SystemCallArchitectures = "native";
133 # Remove (likely) unused groups from the basic @system-service group
134 SystemCallFilter = [
135 "@system-service"
136 "~@aio"
137 "~@chown"
138 "~@keyring"
139 "~@privileged"
140 "~@memlock"
141 "~@resources"
142 "~@setuid"
143 ];
144 RestrictRealtime = true;
145 LockPersonality = true;
146 MemoryDenyWriteExecute = true;
147 UMask = 77;
148 ProtectHostname = true;
149 ProcSubset = "pid";
150 };
151
152 generatePeersAnnouncingSocket =
153 name: values:
154 nameValuePair "wireguard-${name}-peers-announcing" {
155 enable = values.peersAnnouncing.enable;
156 listenStreams = [ (toString values.peersAnnouncing.listenPort) ];
157 socketConfig.Accept = true;
158 # Basic firewalling restricting answers to peers
159 # querying an internal IP address of the announcing peer.
160 # Note that IPv4 addresses can be spoofed using other interfaces unless
161 # sysctl net.ipv4.conf.${name}.rp_filter=1
162 socketConfig.BindToDevice = name;
163 socketConfig.IPAddressAllow = map (peer: peer.allowedIPs) values.peers;
164 socketConfig.IPAddressDeny = "any";
165 socketConfig.MaxConnectionsPerSource = 1;
166 socketConfig.ReusePort = true;
167 wantedBy = [ "sockets.target" ];
168 };
169
170 generatePeersAnnouncingUnit =
171 name: values:
172 nameValuePair "wireguard-${name}-peers-announcing@" {
173 description = "WireGuard Peers Announcing - ${name}";
174 requires = [ "wireguard-${name}.service" ];
175 after = [ "wireguard-${name}.service" ];
176
177 serviceConfig = mkMerge [
178 peerUpdateSecurity
179 {
180 Type = "simple";
181 ExecStart = "${pkgs.wireguard-tools}/bin/wg show '${name}' endpoints";
182 StandardInput = "null";
183 StandardOutput = "socket";
184 RestrictAddressFamilies = "";
185 }
186 (mkIf (values.interfaceNamespace != null) {
187 NetworkNamespacePath = "/var/run/netns/${values.interfaceNamespace}";
188 })
189 ];
190 };
191
192 generateEndpointsUpdaterUnit =
193 {
194 interfaceName,
195 interfaceCfg,
196 peer,
197 }:
198 let
199 dynamicRefreshEnabled = peer.dynamicEndpointRefreshSeconds != 0;
200 peerService = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled;
201 in
202 nameValuePair "wireguard-${interfaceName}-endpoints-updater-${keyToUnitName peer.publicKey}" {
203 description = "WireGuard ${interfaceName} Endpoints Updater - ${peer.publicKey}";
204 requires = [ "${peerService}.service" ];
205 after = [ "${peerService}.service" ];
206 wantedBy = [ "${peerService}.service" ];
207 path = with pkgs; [ wireguard-tools ];
208
209 unitConfig = {
210 StartLimitIntervalSec = 0;
211 };
212 serviceConfig = mkMerge [
213 peerUpdateSecurity
214 {
215 Type = "simple";
216 IPAddressAllow = [ peer.endpointsUpdater.addr ];
217 RestrictAddressFamilies = [
218 "AF_INET"
219 "AF_INET6"
220 "AF_NETLINK"
221 ];
222 Restart = "on-failure";
223 }
224 (mkIf (interfaceCfg.interfaceNamespace != null) {
225 NetworkNamespacePath = "/var/run/netns/${interfaceCfg.interfaceNamespace}";
226 })
227 ];
228
229 # Query the peer announcing other peers
230 # for setting the current endpoint of all configured peers
231 # (but the announcing peer).
232 # Note that socat is used instead of libressl's netcat
233 # (which would require MemoryDenyWriteExecute=true to load libtls.so)
234 # or netcat-gnu (which does not work on ARM and has last been released in 2004).
235 script = ''
236 wait="${toString peer.endpointsUpdater.refreshSeconds}"
237 declare -A configured_keys
238 configured_keys=(${
239 concatMapStringsSep " " (
240 p: optionalString (p.publicKey != peer.publicKey) "[${p.publicKey}]=set"
241 ) interfaceCfg.peers
242 })
243
244 while true; do
245 # Set stdin to socat's stdout
246 exec < <(exec ${pkgs.socat}/bin/socat STDOUT \
247 "TCP:${with peer.endpointsUpdater; addr + ":" + toString port}")
248
249 # Update the endpoint of each configured peer
250 while read -t "$wait" -n 128 -r public_key endpoint x; do
251 if [ "$endpoint" != "(none)" -a "''${configured_keys[$public_key]}" ]; then
252 wg set "${interfaceName}" peer "$public_key" endpoint "$endpoint"
253 fi;
254 done
255
256 sleep "$wait"
257 done
258 '';
259 };
260
261 all_peers = flatten (
262 mapAttrsToList (
263 interfaceName: interfaceCfg:
264 map (peer: { inherit interfaceName interfaceCfg peer; }) interfaceCfg.peers
265 ) cfg.interfaces
266 );
267 in
268 {
269 options.networking.wireguard = {
270 interfaces = mkOption {
271 type = with types; attrsOf (submodule interfaceOpts);
272 };
273 };
274 config = {
275 systemd.sockets = mapAttrs' generatePeersAnnouncingSocket cfg.interfaces;
276
277 systemd.services =
278 (mapAttrs' generatePeersAnnouncingUnit cfg.interfaces)
279 // (listToAttrs (
280 map generateEndpointsUpdaterUnit (filter ({ peer, ... }: peer.endpointsUpdater.enable) all_peers)
281 ));
282 };
283 }