]> Git — Sourcephile - julm/julm-nix.git/blob - nixpkgs/patches/openvpn.diff
nix: update inputs
[julm/julm-nix.git] / nixpkgs / patches / openvpn.diff
1 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
2 index ff06a72ff9dd..022ad5bdb8c9 100644
3 --- a/nixos/modules/module-list.nix
4 +++ b/nixos/modules/module-list.nix
5 @@ -947,6 +947,7 @@
6 ./services/networking/netbird.nix
7 ./services/networking/networkd-dispatcher.nix
8 ./services/networking/networkmanager.nix
9 + ./services/networking/netns.nix
10 ./services/networking/nextdns.nix
11 ./services/networking/nftables.nix
12 ./services/networking/nghttpx/default.nix
13 diff --git a/nixos/modules/services/networking/netns.nix b/nixos/modules/services/networking/netns.nix
14 new file mode 100644
15 index 000000000000..b14aff993022
16 --- /dev/null
17 +++ b/nixos/modules/services/networking/netns.nix
18 @@ -0,0 +1,105 @@
19 +{ pkgs, lib, config, options, ... }:
20 +with lib;
21 +let
22 + cfg = config.services.netns;
23 + inherit (config) networking;
24 + # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
25 + escapeUnitName = name:
26 + lib.concatMapStrings (s: if lib.isList s then "-" else s)
27 + (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
28 +in
29 +{
30 +options.services.netns = {
31 + namespaces = mkOption {
32 + description = mdDoc ''
33 + Network namespaces to create.
34 +
35 + Other services can join a network namespace named `netns` with:
36 + ```
37 + PrivateNetwork=true;
38 + JoinsNamespaceOf="netns-''${netns}.service";
39 + ```
40 +
41 + So can `iproute` with: `ip -n ''${netns}`
42 +
43 + ::: {.warning}
44 + You should usually create (or update via your VPN configuration's up script)
45 + a file named `/etc/netns/''${netns}/resolv.conf`
46 + that will be bind-mounted by `ip -n ''${netns}` onto `/etc/resolv.conf`,
47 + which you'll also want to configure in the services joining this network namespace:
48 + ```
49 + BindReadOnlyPaths = ["/etc/netns/''${netns}/resolv.conf:/etc/resolv.conf"];
50 + ```
51 + :::
52 + '';
53 + default = {};
54 + type = types.attrsOf (types.submodule {
55 + options.nftables = mkOption {
56 + description = mdDoc "Nftables ruleset within the network namespace.";
57 + type = types.lines;
58 + default = networking.nftables.ruleset;
59 + defaultText = "config.networking.nftables.ruleset";
60 + };
61 + options.sysctl = options.boot.kernel.sysctl // {
62 + description = mdDoc "sysctl within the network namespace.";
63 + default = config.boot.kernel.sysctl;
64 + defaultText = literalMD "config.boot.kernel.sysctl";
65 + };
66 + options.service = mkOption {
67 + description = mdDoc "Systemd configuration specific to this netns service";
68 + type = types.attrs;
69 + default = {};
70 + };
71 + });
72 + };
73 +};
74 +config = {
75 + systemd.services = mapAttrs' (name: c:
76 + nameValuePair "netns-${escapeUnitName name}" (mkMerge [
77 + { description = "${name} network namespace";
78 + before = [ "network.target" ];
79 + serviceConfig = {
80 + Type = "oneshot";
81 + RemainAfterExit = true;
82 + # Let systemd create the netns so that PrivateNetwork=true
83 + # with JoinsNamespaceOf="netns-${name}.service" works.
84 + PrivateNetwork = true;
85 + ExecStart = [
86 + # For convenience, register the netns to the tracking mecanism of iproute,
87 + # and make sure resolv.conf can be used in BindReadOnlyPaths=
88 + # For propagating changes in that file to the services bind mounting it,
89 + # updating must not remove the file, but only truncate it.
90 + (pkgs.writeShellScript "ip-netns-attach" ''
91 + ${pkgs.iproute}/bin/ip netns attach ${escapeShellArg name} $$
92 + mkdir -p /etc/netns/${escapeShellArg name}
93 + touch /etc/netns/${escapeShellArg name}/resolv.conf
94 + '')
95 +
96 + # Bringing the loopback interface is almost always a good thing.
97 + "${pkgs.iproute}/bin/ip link set dev lo up"
98 +
99 + # Use --ignore because some keys may no longer exist in that new namespace,
100 + # like net.ipv6.conf.eth0.addr_gen_mode or net.core.rmem_max
101 + ''${pkgs.procps}/bin/sysctl --ignore -p ${pkgs.writeScript "sysctl"
102 + (concatStrings (mapAttrsToList (n: v:
103 + optionalString (v != null)
104 + "${n}=${if v == false then "0" else toString v}\n"
105 + ) c.sysctl))}
106 + ''
107 + ] ++
108 + # Load the nftables ruleset of this netns.
109 + optional networking.nftables.enable (pkgs.writeScript "nftables-ruleset" ''
110 + #!${pkgs.nftables}/bin/nft -f
111 + flush ruleset
112 + ${c.nftables}
113 + '');
114 + # Unregister the netns from the tracking mecanism of iproute.
115 + ExecStop = "${pkgs.iproute}/bin/ip netns delete ${escapeShellArg name}";
116 + };
117 + }
118 + c.service
119 + ]
120 + )) cfg.namespaces;
121 + meta.maintainers = with lib.maintainers; [ julm ];
122 +};
123 +}
124 diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
125 index 9a5866f2afd4..ace7409f9523 100644
126 --- a/nixos/modules/services/networking/openvpn.nix
127 +++ b/nixos/modules/services/networking/openvpn.nix
128 @@ -5,55 +5,205 @@ with lib;
129 let
130
131 cfg = config.services.openvpn;
132 + enabledServers = filterAttrs (name: srv: srv.enable) cfg.servers;
133
134 inherit (pkgs) openvpn;
135
136 + PATH = name: makeBinPath config.systemd.services."openvpn-${name}".path;
137 +
138 makeOpenVPNJob = cfg: name:
139 let
140
141 - path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
142 + configFile = pkgs.writeText "openvpn-config-${name}" (
143 + generators.toKeyValue
144 + {
145 + mkKeyValue = key: value:
146 + if hasAttr key scripts
147 + then "${key} " + pkgs.writeShellScript "openvpn-${name}-${key}" (scripts.${key} value)
148 + else if builtins.isBool value
149 + then optionalString value key
150 + else if builtins.isPath value
151 + then "${key} ${value}"
152 + else if builtins.isList value
153 + then concatMapStringsSep "\n" (v: "${key} ${generators.mkValueStringDefault {} v}") value
154 + else "${key} ${generators.mkValueStringDefault {} value}";
155 + }
156 + cfg.settings
157 + );
158
159 - upScript = ''
160 - export PATH=${path}
161 + scripts = {
162 + up = script:
163 + let
164 + init = ''
165 + export PATH=${PATH name}
166
167 - # For convenience in client scripts, extract the remote domain
168 - # name and name server.
169 - for var in ''${!foreign_option_*}; do
170 - x=(''${!var})
171 - if [ "''${x[0]}" = dhcp-option ]; then
172 - if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
173 - elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
174 - fi
175 - fi
176 - done
177 + # For convenience in client scripts, extract the remote domain
178 + # name and name server.
179 + for var in ''${!foreign_option_*}; do
180 + x=(''${!var})
181 + if [ "''${x[0]}" = dhcp-option ]; then
182 + if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
183 + elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
184 + fi
185 + fi
186 + done
187
188 - ${cfg.up}
189 - ${optionalString cfg.updateResolvConf
190 - "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
191 - '';
192 + ${optionalString cfg.updateResolvConf
193 + "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
194 + '';
195 + # Add DNS settings given in foreign DHCP options to the resolv.conf of the netns.
196 + # Note that JoinsNamespaceOf="netns-${cfg.netns}.service" will not
197 + # BindReadOnlyPaths=["/etc/netns/${cfg.netns}/resolv.conf:/etc/resolv.conf"];
198 + # this will have to be added in each service joining the namespace.
199 + setNetNSResolvConf = ''
200 + mkdir -p /etc/netns/${cfg.netns}
201 + # This file is normally created by netns-${cfg.netns}.service,
202 + # care must be taken to not delete it but to truncate it
203 + # in order to propagate the changes to bind-mounted versions.
204 + : > /etc/netns/${cfg.netns}/resolv.conf
205 + chmod 644 /etc/netns/${cfg.netns}/resolv.conf
206 + foreign_opt_domains=
207 + process_foreign_option () {
208 + case "$1:$2" in
209 + dhcp-option:DNS) echo "nameserver $3" >>/etc/netns/'${cfg.netns}'/resolv.conf ;;
210 + dhcp-option:DOMAIN) foreign_opt_domains="$foreign_opt_domains $3" ;;
211 + esac
212 + }
213 + i=1
214 + while
215 + eval opt=\"\''${foreign_option_$i-}\"
216 + [ -n "$opt" ]
217 + do
218 + process_foreign_option $opt
219 + i=$(( i + 1 ))
220 + done
221 + for d in $foreign_opt_domains; do
222 + printf '%s\n' "domain $1" "search $*" \
223 + >>/etc/netns/'${cfg.netns}'/resolv.conf
224 + done
225 + '';
226 + in
227 + if cfg.netns == null
228 + then ''
229 + ${init}
230 + ${script}
231 + ''
232 + else ''
233 + export PATH=${PATH name}
234 + set -eux
235 + ${setNetNSResolvConf}
236 + ip link set dev '${cfg.settings.dev}' up netns '${cfg.netns}' mtu "$tun_mtu"
237 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-up-netns.sh" ''
238 + ${init}
239 + set -eux
240 + export PATH=${PATH name}
241
242 - downScript = ''
243 - export PATH=${path}
244 - ${optionalString cfg.updateResolvConf
245 - "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
246 - ${cfg.down}
247 - '';
248 + ip link set dev lo up
249
250 - configFile = pkgs.writeText "openvpn-config-${name}"
251 - ''
252 - errors-to-stderr
253 - ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
254 - ${cfg.config}
255 - ${optionalString (cfg.up != "" || cfg.updateResolvConf)
256 - "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
257 - ${optionalString (cfg.down != "" || cfg.updateResolvConf)
258 - "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
259 - ${optionalString (cfg.authUserPass != null)
260 - "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
261 - ${cfg.authUserPass.username}
262 - ${cfg.authUserPass.password}
263 - ''}"}
264 - '';
265 + netmask4="''${ifconfig_netmask:-30}"
266 + netbits6="''${ifconfig_ipv6_netbits:-112}"
267 + if [ -n "''${ifconfig_local-}" ]; then
268 + if [ -n "''${ifconfig_remote-}" ]; then
269 + ip -4 addr replace \
270 + local "$ifconfig_local" \
271 + peer "$ifconfig_remote/$netmask4" \
272 + ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
273 + dev '${cfg.settings.dev}'
274 + else
275 + ip -4 addr replace \
276 + local "$ifconfig_local/$netmask4" \
277 + ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
278 + dev '${cfg.settings.dev}'
279 + fi
280 + fi
281 + if [ -n "''${ifconfig_ipv6_local-}" ]; then
282 + if [ -n "''${ifconfig_ipv6_remote-}" ]; then
283 + ip -6 addr replace \
284 + local "$ifconfig_ipv6_local" \
285 + peer "$ifconfig_ipv6_remote/$netbits6" \
286 + dev '${cfg.settings.dev}'
287 + else
288 + ip -6 addr replace \
289 + local "$ifconfig_ipv6_local/$netbits6" \
290 + dev '${cfg.settings.dev}'
291 + fi
292 + fi
293 + set +eux
294 + ${script}
295 + ''}
296 + '';
297 + route-up = script:
298 + if cfg.netns == null
299 + then script
300 + else ''
301 + export PATH=${PATH name}
302 + set -eux
303 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-route-up-netns" ''
304 + export PATH=${PATH name}
305 + set -eux
306 + i=1
307 + while
308 + eval net=\"\''${route_network_$i-}\"
309 + eval mask=\"\''${route_netmask_$i-}\"
310 + eval gw=\"\''${route_gateway_$i-}\"
311 + eval mtr=\"\''${route_metric_$i-}\"
312 + [ -n "$net" ]
313 + do
314 + ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
315 + i=$(( i + 1 ))
316 + done
317 +
318 + if [ -n "''${route_vpn_gateway-}" ]; then
319 + ip -4 route replace default via "$route_vpn_gateway"
320 + fi
321 +
322 + i=1
323 + while
324 + # There doesn't seem to be $route_ipv6_metric_<n>
325 + # according to the manpage.
326 + eval net=\"\''${route_ipv6_network_$i-}\"
327 + eval gw=\"\''${route_ipv6_gateway_$i-}\"
328 + [ -n "$net" ]
329 + do
330 + ip -6 route replace "$net" via "$gw" metric 100
331 + i=$(( i + 1 ))
332 + done
333 +
334 + # There's no $route_vpn_gateway for IPv6. It's not
335 + # documented if OpenVPN includes default route in
336 + # $route_ipv6_*. Set default route to remote VPN
337 + # endpoint address if there is one. Use higher metric
338 + # than $route_ipv6_* routes to give preference to a
339 + # possible default route in them.
340 + if [ -n "''${ifconfig_ipv6_remote-}" ]; then
341 + ip -6 route replace default \
342 + via "$ifconfig_ipv6_remote" metric 200
343 + fi
344 + ${script}
345 + ''}
346 + '';
347 + down = script:
348 + let
349 + init = ''
350 + export PATH=${PATH name}
351 + ${optionalString cfg.updateResolvConf
352 + "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
353 + '';
354 + in
355 + if cfg.netns == null
356 + then ''
357 + ${init}
358 + ${script}
359 + ''
360 + else ''
361 + export PATH=${PATH name}
362 + ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-down-netns.sh" ''
363 + ${init}
364 + ${script}
365 + ''}
366 + rm -f /etc/netns/'${cfg.netns}'/resolv.conf
367 + '';
368 + };
369
370 in
371 {
372 @@ -61,11 +211,14 @@ let
373
374 wantedBy = optional cfg.autoStart "multi-user.target";
375 after = [ "network.target" ];
376 + bindsTo = optional (cfg.netns != null) "netns-${cfg.netns}.service";
377 + requires = optional (cfg.netns != null) "netns-${cfg.netns}.service";
378
379 path = [ pkgs.iptables pkgs.iproute2 pkgs.nettools ];
380
381 serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
382 serviceConfig.Restart = "always";
383 + serviceConfig.RestartSec = "5s";
384 serviceConfig.Type = "notify";
385 };
386
387 @@ -96,30 +249,30 @@ in
388 example = literalExpression ''
389 {
390 server = {
391 - config = '''
392 + settings = {
393 # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
394 # server :
395 - dev tun
396 - ifconfig 10.8.0.1 10.8.0.2
397 - secret /root/static.key
398 - ''';
399 - up = "ip route add ...";
400 - down = "ip route del ...";
401 + dev = "tun";
402 + ifconfig = "10.8.0.1 10.8.0.2";
403 + secret = "/root/static.key";
404 + up = "ip route add ...";
405 + down = "ip route del ...";
406 + };
407 };
408
409 client = {
410 - config = '''
411 - client
412 - remote vpn.example.org
413 - dev tun
414 - proto tcp-client
415 - port 8080
416 - ca /root/.vpn/ca.crt
417 - cert /root/.vpn/alice.crt
418 - key /root/.vpn/alice.key
419 - ''';
420 - up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
421 - down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
422 + settings = {
423 + client = true;
424 + remote = "vpn.example.org";
425 + dev = "tun";
426 + proto = "tcp-client";
427 + port = 8080;
428 + ca = "/root/.vpn/ca.crt";
429 + cert = "/root/.vpn/alice.crt";
430 + key = "/root/.vpn/alice.key";
431 + up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
432 + down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
433 + };
434 };
435 }
436 '';
437 @@ -133,36 +286,80 @@ in
438 attribute name.
439 '';
440
441 - type = with types; attrsOf (submodule {
442 + type = with types; attrsOf (submodule ({ name, config, options, ... }: {
443
444 - options = {
445 + options = rec {
446 + enable = mkEnableOption (mdDoc "OpenVPN server") // { default = true; };
447
448 - config = mkOption {
449 - type = types.lines;
450 + settings = mkOption {
451 description = lib.mdDoc ''
452 Configuration of this OpenVPN instance. See
453 {manpage}`openvpn(8)`
454 for details.
455
456 To import an external config file, use the following definition:
457 - `config = "config /path/to/config.ovpn"`
458 - '';
459 - };
460 -
461 - up = mkOption {
462 - default = "";
463 - type = types.lines;
464 - description = lib.mdDoc ''
465 - Shell commands executed when the instance is starting.
466 - '';
467 - };
468 -
469 - down = mkOption {
470 - default = "";
471 - type = types.lines;
472 - description = lib.mdDoc ''
473 - Shell commands executed when the instance is shutting down.
474 + config = /path/to/config.ovpn;
475 '';
476 + default = { };
477 + type = types.submodule {
478 + freeformType = with types;
479 + attrsOf (
480 + nullOr (
481 + oneOf [
482 + bool
483 + int
484 + str
485 + path
486 + (listOf (oneOf [ bool int str path ]))
487 + ]
488 + )
489 + );
490 + options.dev = mkOption {
491 + default = null;
492 + type = types.str;
493 + description = lib.mdDoc ''
494 + Shell commands executed when the instance is starting.
495 + '';
496 + };
497 + options.down = mkOption {
498 + default = "";
499 + type = types.lines;
500 + description = lib.mdDoc ''
501 + Shell commands executed when the instance is shutting down.
502 + '';
503 + };
504 + options.errors-to-stderr = mkOption {
505 + default = true;
506 + type = types.bool;
507 + description = lib.mdDoc ''
508 + Output errors to stderr instead of stdout
509 + unless log output is redirected by one of the `--log` options.
510 + '';
511 + };
512 + options.route-up = mkOption {
513 + default = "";
514 + type = types.lines;
515 + description = lib.mdDoc ''
516 + Run command after routes are added.
517 + '';
518 + };
519 + options.up = mkOption {
520 + default = "";
521 + type = types.lines;
522 + description = lib.mdDoc ''
523 + Shell commands executed when the instance is starting.
524 + '';
525 + };
526 + options.script-security = mkOption {
527 + default = 1;
528 + type = types.enum [ 1 2 3 ];
529 + description = lib.mdDoc ''
530 + - 1 — (Default) Only call built-in executables such as ifconfig, ip, route, or netsh.
531 + - 2 — Allow calling of built-in executables and user-defined scripts.
532 + - 3 — Allow passwords to be passed to scripts via environmental variables (potentially unsafe).
533 + '';
534 + };
535 + };
536 };
537
538 autoStart = mkOption {
539 @@ -171,6 +368,10 @@ in
540 description = lib.mdDoc "Whether this OpenVPN instance should be started automatically.";
541 };
542
543 + # Legacy options
544 + down = (elemAt settings.type.functor.payload.modules 0).options.down;
545 + up = (elemAt settings.type.functor.payload.modules 0).options.up;
546 +
547 updateResolvConf = mkOption {
548 default = false;
549 type = types.bool;
550 @@ -204,9 +405,41 @@ in
551 };
552 });
553 };
554 +
555 + netns = mkOption {
556 + default = null;
557 + type = with types; nullOr str;
558 + description = lib.mdDoc "Network namespace.";
559 + };
560 };
561
562 - });
563 + config.settings = mkMerge
564 + [
565 + (mkIf (config.netns != null) {
566 + # Useless to setup the interface
567 + # because moving it to the netns will reset it
568 + ifconfig-noexec = true;
569 + route-noexec = true;
570 + script-security = 2;
571 + })
572 + (mkIf (config.authUserPass != null) {
573 + auth-user-pass = pkgs.writeText "openvpn-auth-user-pass-${name}" ''
574 + ${config.authUserPass.username}
575 + ${config.authUserPass.password}
576 + '';
577 + })
578 + (mkIf config.updateResolvConf {
579 + script-security = 2;
580 + })
581 + {
582 + # Aliases legacy options
583 + down = modules.mkAliasAndWrapDefsWithPriority id (options.down or { });
584 + up = modules.mkAliasAndWrapDefsWithPriority id (options.up or { });
585 + }
586 + ];
587 +
588 +
589 + }));
590
591 };
592
593 @@ -221,9 +454,9 @@ in
594
595 ###### implementation
596
597 - config = mkIf (cfg.servers != { }) {
598 + config = mkIf (enabledServers != { }) {
599
600 - systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
601 + systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) enabledServers))
602 // restartService;
603
604 environment.systemPackages = [ openvpn ];