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