]> Git — Sourcephile - julm/julm-nix.git/blob - nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch
ffmpeg: -full is broken
[julm/julm-nix.git] / nixpkgs / patches / openvpn / 0002-nixos-openvpn-add-netns-support.patch
1 From 600839a7759be481904d029798e563e23df930db Mon Sep 17 00:00:00 2001
2 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
3 Date: Sun, 17 Jan 2021 15:28:25 +0100
4 Subject: [PATCH 2/2] nixos/openvpn: add netns support
5
6 ---
7 nixos/modules/services/networking/openvpn.nix | 395 ++++++++++++++----
8 1 file changed, 314 insertions(+), 81 deletions(-)
9
10 diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
11 index 56b1f6f5ab8f..9805099789b1 100644
12 --- a/nixos/modules/services/networking/openvpn.nix
13 +++ b/nixos/modules/services/networking/openvpn.nix
14 @@ -5,55 +5,205 @@ with lib;
15 let
16
17 cfg = config.services.openvpn;
18 + enabledServers = filterAttrs (name: srv: srv.enable) cfg.servers;
19
20 inherit (pkgs) openvpn;
21
22 + PATH = name: makeBinPath config.systemd.services."openvpn-${name}".path;
23 +
24 makeOpenVPNJob = cfg: name:
25 let
26
27 - path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
28 + configFile = pkgs.writeText "openvpn-config-${name}" (
29 + generators.toKeyValue
30 + {
31 + mkKeyValue = key: value:
32 + if hasAttr key scripts
33 + then "${key} " + pkgs.writeShellScript "openvpn-${name}-${key}" (scripts.${key} value)
34 + else if builtins.isBool value
35 + then optionalString value key
36 + else if builtins.isPath value
37 + then "${key} ${value}"
38 + else if builtins.isList value
39 + then concatMapStringsSep "\n" (v: "${key} ${generators.mkValueStringDefault {} v}") value
40 + else "${key} ${generators.mkValueStringDefault {} value}";
41 + }
42 + cfg.settings
43 + );
44
45 - upScript = ''
46 - export PATH=${path}
47 + scripts = {
48 + up = script:
49 + let
50 + init = ''
51 + export PATH=${PATH name}
52
53 - # For convenience in client scripts, extract the remote domain
54 - # name and name server.
55 - for var in ''${!foreign_option_*}; do
56 - x=(''${!var})
57 - if [ "''${x[0]}" = dhcp-option ]; then
58 - if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
59 - elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
60 - fi
61 - fi
62 - done
63 + # For convenience in client scripts, extract the remote domain
64 + # name and name server.
65 + for var in ''${!foreign_option_*}; do
66 + x=(''${!var})
67 + if [ "''${x[0]}" = dhcp-option ]; then
68 + if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
69 + elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
70 + fi
71 + fi
72 + done
73
74 - ${cfg.up}
75 - ${optionalString cfg.updateResolvConf
76 - "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
77 - '';
78 + ${optionalString cfg.updateResolvConf
79 + "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
80 + '';
81 + # Add DNS settings given in foreign DHCP options to the resolv.conf of the netns.
82 + # Note that JoinsNamespaceOf="netns-${cfg.netns}.service" will not
83 + # BindReadOnlyPaths=["/etc/netns/${cfg.netns}/resolv.conf:/etc/resolv.conf"];
84 + # this will have to be added in each service joining the namespace.
85 + setNetNSResolvConf = ''
86 + mkdir -p /etc/netns/${cfg.netns}
87 + # This file is normally created by netns-${cfg.netns}.service,
88 + # care must be taken to not delete it but to truncate it
89 + # in order to propagate the changes to bind-mounted versions.
90 + : > /etc/netns/${cfg.netns}/resolv.conf
91 + chmod 644 /etc/netns/${cfg.netns}/resolv.conf
92 + foreign_opt_domains=
93 + process_foreign_option () {
94 + case "$1:$2" in
95 + dhcp-option:DNS) echo "nameserver $3" >>/etc/netns/'${cfg.netns}'/resolv.conf ;;
96 + dhcp-option:DOMAIN) foreign_opt_domains="$foreign_opt_domains $3" ;;
97 + esac
98 + }
99 + i=1
100 + while
101 + eval opt=\"\''${foreign_option_$i-}\"
102 + [ -n "$opt" ]
103 + do
104 + process_foreign_option $opt
105 + i=$(( i + 1 ))
106 + done
107 + for d in $foreign_opt_domains; do
108 + printf '%s\n' "domain $1" "search $*" \
109 + >>/etc/netns/'${cfg.netns}'/resolv.conf
110 + done
111 + '';
112 + in
113 + if cfg.netns == null
114 + then ''
115 + ${init}
116 + ${script}
117 + ''
118 + else ''
119 + export PATH=${PATH name}
120 + set -eux
121 + ${setNetNSResolvConf}
122 + ip link set dev '${cfg.settings.dev}' up netns '${cfg.netns}' mtu "$tun_mtu"
123 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-up-netns.sh" ''
124 + ${init}
125 + set -eux
126 + export PATH=${PATH name}
127
128 - downScript = ''
129 - export PATH=${path}
130 - ${optionalString cfg.updateResolvConf
131 - "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
132 - ${cfg.down}
133 - '';
134 + ip link set dev lo up
135
136 - configFile = pkgs.writeText "openvpn-config-${name}"
137 - ''
138 - errors-to-stderr
139 - ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
140 - ${cfg.config}
141 - ${optionalString (cfg.up != "" || cfg.updateResolvConf)
142 - "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
143 - ${optionalString (cfg.down != "" || cfg.updateResolvConf)
144 - "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
145 - ${optionalString (cfg.authUserPass != null)
146 - "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
147 - ${cfg.authUserPass.username}
148 - ${cfg.authUserPass.password}
149 - ''}"}
150 - '';
151 + netmask4="''${ifconfig_netmask:-30}"
152 + netbits6="''${ifconfig_ipv6_netbits:-112}"
153 + if [ -n "''${ifconfig_local-}" ]; then
154 + if [ -n "''${ifconfig_remote-}" ]; then
155 + ip -4 addr replace \
156 + local "$ifconfig_local" \
157 + peer "$ifconfig_remote/$netmask4" \
158 + ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
159 + dev '${cfg.settings.dev}'
160 + else
161 + ip -4 addr replace \
162 + local "$ifconfig_local/$netmask4" \
163 + ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
164 + dev '${cfg.settings.dev}'
165 + fi
166 + fi
167 + if [ -n "''${ifconfig_ipv6_local-}" ]; then
168 + if [ -n "''${ifconfig_ipv6_remote-}" ]; then
169 + ip -6 addr replace \
170 + local "$ifconfig_ipv6_local" \
171 + peer "$ifconfig_ipv6_remote/$netbits6" \
172 + dev '${cfg.settings.dev}'
173 + else
174 + ip -6 addr replace \
175 + local "$ifconfig_ipv6_local/$netbits6" \
176 + dev '${cfg.settings.dev}'
177 + fi
178 + fi
179 + set +eux
180 + ${script}
181 + ''}
182 + '';
183 + route-up = script:
184 + if cfg.netns == null
185 + then script
186 + else ''
187 + export PATH=${PATH name}
188 + set -eux
189 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-route-up-netns" ''
190 + export PATH=${PATH name}
191 + set -eux
192 + i=1
193 + while
194 + eval net=\"\''${route_network_$i-}\"
195 + eval mask=\"\''${route_netmask_$i-}\"
196 + eval gw=\"\''${route_gateway_$i-}\"
197 + eval mtr=\"\''${route_metric_$i-}\"
198 + [ -n "$net" ]
199 + do
200 + ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
201 + i=$(( i + 1 ))
202 + done
203 +
204 + if [ -n "''${route_vpn_gateway-}" ]; then
205 + ip -4 route replace default via "$route_vpn_gateway"
206 + fi
207 +
208 + i=1
209 + while
210 + # There doesn't seem to be $route_ipv6_metric_<n>
211 + # according to the manpage.
212 + eval net=\"\''${route_ipv6_network_$i-}\"
213 + eval gw=\"\''${route_ipv6_gateway_$i-}\"
214 + [ -n "$net" ]
215 + do
216 + ip -6 route replace "$net" via "$gw" metric 100
217 + i=$(( i + 1 ))
218 + done
219 +
220 + # There's no $route_vpn_gateway for IPv6. It's not
221 + # documented if OpenVPN includes default route in
222 + # $route_ipv6_*. Set default route to remote VPN
223 + # endpoint address if there is one. Use higher metric
224 + # than $route_ipv6_* routes to give preference to a
225 + # possible default route in them.
226 + if [ -n "''${ifconfig_ipv6_remote-}" ]; then
227 + ip -6 route replace default \
228 + via "$ifconfig_ipv6_remote" metric 200
229 + fi
230 + ${script}
231 + ''}
232 + '';
233 + down = script:
234 + let
235 + init = ''
236 + export PATH=${PATH name}
237 + ${optionalString cfg.updateResolvConf
238 + "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
239 + '';
240 + in
241 + if cfg.netns == null
242 + then ''
243 + ${init}
244 + ${script}
245 + ''
246 + else ''
247 + export PATH=${PATH name}
248 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-down-netns.sh" ''
249 + ${init}
250 + ${script}
251 + ''}
252 + rm -f /etc/netns/'${cfg.netns}'/resolv.conf
253 + '';
254 + };
255
256 in
257 {
258 @@ -61,11 +211,14 @@ let
259
260 wantedBy = optional cfg.autoStart "multi-user.target";
261 after = [ "network.target" ];
262 + bindsTo = optional (cfg.netns != null) "netns-${cfg.netns}.service";
263 + requires = optional (cfg.netns != null) "netns-${cfg.netns}.service";
264
265 path = [ pkgs.iptables pkgs.iproute2 pkgs.nettools ];
266
267 serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
268 serviceConfig.Restart = "always";
269 + serviceConfig.RestartSec = "5s";
270 serviceConfig.Type = "notify";
271 };
272
273 @@ -97,30 +250,30 @@ in
274 example = literalExpression ''
275 {
276 server = {
277 - config = '''
278 + settings = {
279 # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
280 # server :
281 - dev tun
282 - ifconfig 10.8.0.1 10.8.0.2
283 - secret /root/static.key
284 - ''';
285 - up = "ip route add ...";
286 - down = "ip route del ...";
287 + dev = "tun";
288 + ifconfig = "10.8.0.1 10.8.0.2";
289 + secret = "/root/static.key";
290 + up = "ip route add ...";
291 + down = "ip route del ...";
292 + };
293 };
294
295 client = {
296 - config = '''
297 - client
298 - remote vpn.example.org
299 - dev tun
300 - proto tcp-client
301 - port 8080
302 - ca /root/.vpn/ca.crt
303 - cert /root/.vpn/alice.crt
304 - key /root/.vpn/alice.key
305 - ''';
306 - up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
307 - down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
308 + settings = {
309 + client = true;
310 + remote = "vpn.example.org";
311 + dev = "tun";
312 + proto = "tcp-client";
313 + port = 8080;
314 + ca = "/root/.vpn/ca.crt";
315 + cert = "/root/.vpn/alice.crt";
316 + key = "/root/.vpn/alice.key";
317 + up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
318 + down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
319 + };
320 };
321 }
322 '';
323 @@ -134,36 +287,80 @@ in
324 attribute name.
325 '';
326
327 - type = with types; attrsOf (submodule {
328 + type = with types; attrsOf (submodule ({ name, config, options, ... }: {
329
330 - options = {
331 + options = rec {
332 + enable = mkEnableOption "OpenVPN server" // { default = true; };
333
334 - config = mkOption {
335 - type = types.lines;
336 + settings = mkOption {
337 description = ''
338 Configuration of this OpenVPN instance. See
339 {manpage}`openvpn(8)`
340 for details.
341
342 To import an external config file, use the following definition:
343 - `config = "config /path/to/config.ovpn"`
344 - '';
345 - };
346 -
347 - up = mkOption {
348 - default = "";
349 - type = types.lines;
350 - description = ''
351 - Shell commands executed when the instance is starting.
352 - '';
353 - };
354 -
355 - down = mkOption {
356 - default = "";
357 - type = types.lines;
358 - description = ''
359 - Shell commands executed when the instance is shutting down.
360 + config = /path/to/config.ovpn;
361 '';
362 + default = { };
363 + type = types.submodule {
364 + freeformType = with types;
365 + attrsOf (
366 + nullOr (
367 + oneOf [
368 + bool
369 + int
370 + str
371 + path
372 + (listOf (oneOf [ bool int str path ]))
373 + ]
374 + )
375 + );
376 + options.dev = mkOption {
377 + default = null;
378 + type = types.str;
379 + description = ''
380 + Shell commands executed when the instance is starting.
381 + '';
382 + };
383 + options.down = mkOption {
384 + default = "";
385 + type = types.lines;
386 + description = ''
387 + Shell commands executed when the instance is shutting down.
388 + '';
389 + };
390 + options.errors-to-stderr = mkOption {
391 + default = true;
392 + type = types.bool;
393 + description = ''
394 + Output errors to stderr instead of stdout
395 + unless log output is redirected by one of the `--log` options.
396 + '';
397 + };
398 + options.route-up = mkOption {
399 + default = "";
400 + type = types.lines;
401 + description = ''
402 + Run command after routes are added.
403 + '';
404 + };
405 + options.up = mkOption {
406 + default = "";
407 + type = types.lines;
408 + description = ''
409 + Shell commands executed when the instance is starting.
410 + '';
411 + };
412 + options.script-security = mkOption {
413 + default = 1;
414 + type = types.enum [ 1 2 3 ];
415 + description = ''
416 + - 1 — (Default) Only call built-in executables such as ifconfig, ip, route, or netsh.
417 + - 2 — Allow calling of built-in executables and user-defined scripts.
418 + - 3 — Allow passwords to be passed to scripts via environmental variables (potentially unsafe).
419 + '';
420 + };
421 + };
422 };
423
424 autoStart = mkOption {
425 @@ -172,6 +369,10 @@ in
426 description = "Whether this OpenVPN instance should be started automatically.";
427 };
428
429 + # Legacy options
430 + down = (elemAt settings.type.functor.payload.modules 0).options.down;
431 + up = (elemAt settings.type.functor.payload.modules 0).options.up;
432 +
433 updateResolvConf = mkOption {
434 default = false;
435 type = types.bool;
436 @@ -205,9 +406,41 @@ in
437 };
438 });
439 };
440 +
441 + netns = mkOption {
442 + default = null;
443 + type = with types; nullOr str;
444 + description = "Network namespace.";
445 + };
446 };
447
448 - });
449 + config.settings = mkMerge
450 + [
451 + (mkIf (config.netns != null) {
452 + # Useless to setup the interface
453 + # because moving it to the netns will reset it
454 + ifconfig-noexec = true;
455 + route-noexec = true;
456 + script-security = 2;
457 + })
458 + (mkIf (config.authUserPass != null) {
459 + auth-user-pass = pkgs.writeText "openvpn-auth-user-pass-${name}" ''
460 + ${config.authUserPass.username}
461 + ${config.authUserPass.password}
462 + '';
463 + })
464 + (mkIf config.updateResolvConf {
465 + script-security = 2;
466 + })
467 + {
468 + # Aliases legacy options
469 + down = modules.mkAliasAndWrapDefsWithPriority id (options.down or { });
470 + up = modules.mkAliasAndWrapDefsWithPriority id (options.up or { });
471 + }
472 + ];
473 +
474 +
475 + }));
476
477 };
478
479 @@ -222,9 +455,9 @@ in
480
481 ###### implementation
482
483 - config = mkIf (cfg.servers != { }) {
484 + config = mkIf (enabledServers != { }) {
485
486 - systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
487 + systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) enabledServers))
488 // restartService;
489
490 environment.systemPackages = [ openvpn ];
491 --
492 2.44.1
493