diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index cf25ae3157e6..2ba034735a33 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 000000000000..7553c2aebb10 --- /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 debc60a21d01..94fba23675df 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 000000000000..bb4c41e6d709 --- /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 000000000000..5926a48af00f --- /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 000000000000..248a91a3e3d5 --- /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-----