From c684398c6afaf90d9dc86466ca36e4ea3263d77f Mon Sep 17 00:00:00 2001 From: CRTified Date: Wed, 1 Jul 2020 00:02:56 +0200 Subject: [PATCH] nixos/system/boot/initrd-openvpn: Add openvpn options for initrd nixos/tests/initrd-openvpn: Add test for openvpn in the initramfs The module in this commit adds new options that allows the integration of an OpenVPN client into the initrd. This can be used e.g. to remotely unlock LUKS devices. This commit also adds two tests for `boot.initrd.network.openvpn`. The first one is a basic test to validate that a failing connection does not prevent the machine from booting. The second test validates that this module actually creates a valid openvpn connection. For this, it spawns three nodes: - The client that uses boot.initrd.network.openvpn - An OpenVPN server that acts as gateway and forwards a port to the client - A node that is external to the OpenVPN network The client connects to the OpenVPN server and spawns a netcat instance that echos a value to every client. Afterwards, the external node checks if it receives this value over the forwarded port on the OpenVPN gateway. --- nixos/modules/module-list.nix | 1 + nixos/modules/system/boot/initrd-openvpn.nix | 81 ++++++++++ nixos/tests/all-tests.nix | 1 + .../tests/initrd-network-openvpn/default.nix | 145 ++++++++++++++++++ .../tests/initrd-network-openvpn/initrd.ovpn | 29 ++++ nixos/tests/initrd-network-openvpn/shared.key | 21 +++ 6 files changed, 278 insertions(+) create mode 100644 nixos/modules/system/boot/initrd-openvpn.nix create mode 100644 nixos/tests/initrd-network-openvpn/default.nix create mode 100644 nixos/tests/initrd-network-openvpn/initrd.ovpn create mode 100644 nixos/tests/initrd-network-openvpn/shared.key diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index cf25ae3157e..2ba034735a3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -937,6 +937,7 @@ ./system/boot/grow-partition.nix ./system/boot/initrd-network.nix ./system/boot/initrd-ssh.nix + ./system/boot/initrd-openvpn.nix ./system/boot/kernel.nix ./system/boot/kexec.nix ./system/boot/loader/efi.nix diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix new file mode 100644 index 00000000000..7553c2aebb1 --- /dev/null +++ b/nixos/modules/system/boot/initrd-openvpn.nix @@ -0,0 +1,81 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.boot.initrd.network.openvpn; + +in + +{ + + options = { + + boot.initrd.network.openvpn.enable = mkOption { + type = types.bool; + default = false; + description = '' + Starts an OpenVPN client during initrd boot. It can be used to e.g. + remotely accessing the SSH service controlled by + or other network services + included. Service is killed when stage-1 boot is finished. + ''; + }; + + boot.initrd.network.openvpn.configuration = mkOption { + type = types.path; # Same type as boot.initrd.secrets + description = '' + The configuration file for OpenVPN. + + + + Unless your bootloader supports initrd secrets, this configuration + is stored insecurely in the global Nix store. + + + ''; + example = "./configuration.ovpn"; + }; + + }; + + config = mkIf (config.boot.initrd.network.enable && cfg.enable) { + assertions = [ + { + assertion = cfg.configuration != null; + message = "You should specify a configuration for initrd OpenVPN"; + } + ]; + + # Add kernel modules needed for OpenVPN + boot.initrd.kernelModules = [ "tun" "tap" ]; + + # Add openvpn and ip binaries to the initrd + # The shared libraries are required for DNS resolution + boot.initrd.extraUtilsCommands = '' + copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn + copy_bin_and_libs ${pkgs.iproute}/bin/ip + + cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib + cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib + ''; + + boot.initrd.secrets = { + "/etc/initrd.ovpn" = cfg.configuration; + }; + + # openvpn --version would exit with 1 instead of 0 + boot.initrd.extraUtilsCommandsTest = '' + $out/bin/openvpn --show-gateway + ''; + + # Add `iproute /bin/ip` to the config, to ensure that openvpn + # is able to set the routes + boot.initrd.network.postCommands = '' + (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \ + openvpn /dev/stdin & + ''; + }; + +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index debc60a21d0..94fba23675d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -150,6 +150,7 @@ in incron = handleTest ./incron.nix {}; influxdb = handleTest ./influxdb.nix {}; initrd-network-ssh = handleTest ./initrd-network-ssh {}; + initrd-network-openvpn = handleTest ./initrd-network-openvpn {}; initrdNetwork = handleTest ./initrd-network.nix {}; installer = handleTest ./installer.nix {}; iodine = handleTest ./iodine.nix {}; diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix new file mode 100644 index 00000000000..bb4c41e6d70 --- /dev/null +++ b/nixos/tests/initrd-network-openvpn/default.nix @@ -0,0 +1,145 @@ +import ../make-test-python.nix ({ lib, ...}: + +{ + name = "initrd-network-openvpn"; + + nodes = + let + + # Inlining of the shared secret for the + # OpenVPN server and client + secretblock = '' + secret [inline] + + ${lib.readFile ./shared.key} + + ''; + + in + { + + # Minimal test case to check a successful boot, even with invalid config + minimalboot = + { ... }: + { + boot.initrd.network = { + enable = true; + openvpn = { + enable = true; + configuration = "/dev/null"; + }; + }; + }; + + # initrd VPN client + ovpnclient = + { ... }: + { + virtualisation.useBootLoader = true; + virtualisation.vlans = [ 1 ]; + + boot.initrd = { + # This command does not fork to keep the VM in the state where + # only the initramfs is loaded + preLVMCommands = + '' + /bin/nc -p 1234 -lke /bin/echo TESTVALUE + ''; + + network = { + enable = true; + + # Work around udhcpc only getting a lease on eth0 + postCommands = '' + /bin/ip addr add 192.168.1.2/24 dev eth1 + ''; + + # Example configuration for OpenVPN + # This is the main reason for this test + openvpn = { + enable = true; + configuration = "${./initrd.ovpn}"; + }; + }; + }; + }; + + # VPN server and gateway for ovpnclient between vlan 1 and 2 + ovpnserver = + { ... }: + { + virtualisation.vlans = [ 1 2 ]; + + # Enable NAT and forward port 12345 to port 1234 + networking.nat = { + enable = true; + internalInterfaces = [ "tun0" ]; + externalInterface = "eth2"; + forwardPorts = [ { destination = "10.8.0.2:1234"; + sourcePort = 12345; } ]; + }; + + # Trust tun0 and allow the VPN Server to be reached + networking.firewall = { + trustedInterfaces = [ "tun0" ]; + allowedUDPPorts = [ 1194 ]; + }; + + # Minimal OpenVPN server configuration + services.openvpn.servers.testserver = + { + config = '' + dev tun0 + ifconfig 10.8.0.1 10.8.0.2 + ${secretblock} + ''; + }; + }; + + # Client that resides in the "external" VLAN + testclient = + { ... }: + { + virtualisation.vlans = [ 2 ]; + }; + }; + + + testScript = + '' + # Minimal test case, checks whether enabling (with invalid config) harms + # the boot process + with subtest("Check for successful boot with broken openvpn config"): + minimalboot.start() + # If we get to multi-user.target, we booted successfully + minimalboot.wait_for_unit("multi-user.target") + minimalboot.shutdown() + + # Elaborated test case where the ovpnclient (where this module is used) + # can be reached by testclient only over ovpnserver. + # This is an indirect test for success. + with subtest("Check for connection from initrd VPN client, config as file"): + ovpnserver.start() + testclient.start() + ovpnclient.start() + + # Wait until the OpenVPN Server is available + ovpnserver.wait_for_unit("openvpn-testserver.service") + ovpnserver.succeed("ping -c 1 10.8.0.1") + + # Wait for the client to connect + ovpnserver.wait_until_succeeds("ping -c 1 10.8.0.2") + + # Wait until the testclient has network + testclient.wait_for_unit("network.target") + + # Check that ovpnclient is reachable over vlan 1 + ovpnserver.succeed("nc -w 2 192.168.1.2 1234 | grep -q TESTVALUE") + + # Check that ovpnclient is reachable over tun0 + ovpnserver.succeed("nc -w 2 10.8.0.2 1234 | grep -q TESTVALUE") + + # Check that ovpnclient is reachable from testclient over the gateway + testclient.succeed("nc -w 2 192.168.2.3 12345 | grep -q TESTVALUE") + ''; +}) diff --git a/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixos/tests/initrd-network-openvpn/initrd.ovpn new file mode 100644 index 00000000000..5926a48af00 --- /dev/null +++ b/nixos/tests/initrd-network-openvpn/initrd.ovpn @@ -0,0 +1,29 @@ +remote 192.168.1.3 +dev tun +ifconfig 10.8.0.2 10.8.0.1 +# Only force VLAN 2 through the VPN +route 192.168.2.0 255.255.255.0 10.8.0.1 +secret [inline] + +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +553aabe853acdfe51cd6fcfea93dcbb0 +c8797deadd1187606b1ea8f2315eb5e6 +67c0d7e830f50df45686063b189d6c6b +aab8bb3430cc78f7bb1f78628d5c3742 +0cef4f53a5acab2894905f4499f95d8e +e69b7b6748b17016f89e19e91481a9fd +bf8c10651f41a1d4fdf5f438925a6733 +13cec8f04701eb47b8f7ffc48bc3d7af +65f07bce766015b87c3db4d668c655ff +be5a69522a8e60ccb217f8521681b45d +27c0b70bdfbfbb426c7646d80adf7482 +3ddac58b25cb1c1bb100de974478b4c6 +8b45a94261a2405e99810cb2b3abd49f +21b3198ada87ff3c4e656a008e540a8d +e7811584363597599cce2040a68ac00e +f2125540e0f7f4adc37cb3f0d922eeb7 +-----END OpenVPN Static key V1----- + \ No newline at end of file diff --git a/nixos/tests/initrd-network-openvpn/shared.key b/nixos/tests/initrd-network-openvpn/shared.key new file mode 100644 index 00000000000..248a91a3e3d --- /dev/null +++ b/nixos/tests/initrd-network-openvpn/shared.key @@ -0,0 +1,21 @@ +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +553aabe853acdfe51cd6fcfea93dcbb0 +c8797deadd1187606b1ea8f2315eb5e6 +67c0d7e830f50df45686063b189d6c6b +aab8bb3430cc78f7bb1f78628d5c3742 +0cef4f53a5acab2894905f4499f95d8e +e69b7b6748b17016f89e19e91481a9fd +bf8c10651f41a1d4fdf5f438925a6733 +13cec8f04701eb47b8f7ffc48bc3d7af +65f07bce766015b87c3db4d668c655ff +be5a69522a8e60ccb217f8521681b45d +27c0b70bdfbfbb426c7646d80adf7482 +3ddac58b25cb1c1bb100de974478b4c6 +8b45a94261a2405e99810cb2b3abd49f +21b3198ada87ff3c4e656a008e540a8d +e7811584363597599cce2040a68ac00e +f2125540e0f7f4adc37cb3f0d922eeb7 +-----END OpenVPN Static key V1-----