From 6048ebd3d97235cba0ae74ca5bd7b0ee52bf9471 Mon Sep 17 00:00:00 2001 From: Jacek Galowicz Date: Thu, 14 Nov 2024 17:20:45 +0100 Subject: [PATCH] rp systemd unit file: introduce and test --- flake.nix | 1 + pkgs/rosenpass.nix | 1 + systemd/rp@.service | 48 +++++++++++++++ tests/systemd/rp.nix | 139 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 systemd/rp@.service create mode 100644 tests/systemd/rp.nix diff --git a/flake.nix b/flake.nix index 8e2248f..14baded 100644 --- a/flake.nix +++ b/flake.nix @@ -136,6 +136,7 @@ checks = { systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix; + systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix; cargo-fmt = pkgs.runCommand "check-cargo-fmt" { inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } '' diff --git a/pkgs/rosenpass.nix b/pkgs/rosenpass.nix index a1d0270..ff4e0ec 100644 --- a/pkgs/rosenpass.nix +++ b/pkgs/rosenpass.nix @@ -74,6 +74,7 @@ rustPlatform.buildRustPackage { postInstall = '' mkdir -p $out/lib/systemd/system install systemd/rosenpass@.service $out/lib/systemd/system + install systemd/rp@.service $out/lib/systemd/system install systemd/rosenpass.target $out/lib/systemd/system ''; diff --git a/systemd/rp@.service b/systemd/rp@.service new file mode 100644 index 0000000..8134c9e --- /dev/null +++ b/systemd/rp@.service @@ -0,0 +1,48 @@ +[Unit] +Description=Rosenpass key exchange for %I +Documentation=man:rosenpass(1) +Documentation=https://rosenpass.eu/docs + +After=network-online.target nss-lookup.target +Wants=network-online.target nss-lookup.target +PartOf=rosenpass.target + +[Service] +ExecStart=rp exchange-config /etc/rosenpass/%i.toml +LoadCredential=pqpk:/etc/rosenpass/%i/pqpk +LoadCredential=pqsk:/etc/rosenpass/%i/pqsk +LoadCredential=wgsk:/etc/rosenpass/%i/wgsk + +AmbientCapabilities=CAP_NET_ADMIN +CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM +DynamicUser=true +LockPersonality=true +MemoryDenyWriteExecute=true +PrivateDevices=true +ProcSubset=pid +ProtectClock=true +ProtectControlGroups=true +ProtectHome=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=noaccess +RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6 +RestrictNamespaces=true +RestrictRealtime=true +SystemCallArchitectures=native +SystemCallFilter=~@clock +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@swap +UMask=0077 + +[Install] +WantedBy=multi-user.target diff --git a/tests/systemd/rp.nix b/tests/systemd/rp.nix new file mode 100644 index 0000000..7218d15 --- /dev/null +++ b/tests/systemd/rp.nix @@ -0,0 +1,139 @@ +{ pkgs, ... }: + +let + server = { + ip4 = "192.168.0.1"; + ip6 = "fd00::1"; + wg = { + ip6 = "fc00::1"; + listen = 10000; + }; + }; + + client = { + ip4 = "192.168.0.2"; + ip6 = "fd00::2"; + wg = { + ip6 = "fc00::2"; + }; + }; + + server_config = { + listen = "${server.ip4}:9999"; + private_keys_dir = "/run/credentials/rp@test-rp-device0.service"; + verbose = true; + dev = "test-rp-device0"; + ip = "fc00::1/64"; + peers = [{ + public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/client"; + allowed_ips = "fc00::2"; + }]; + }; + client_config = { + private_keys_dir = "/run/credentials/rp@test-rp-device0.service"; + verbose = true; + dev = "test-rp-device0"; + ip = "fc00::2/128"; + peers = [{ + public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/server"; + endpoint = "${server.ip4}:9999"; + allowed_ips = "fc00::/64"; + }]; + }; + + config = pkgs.runCommand "config" { } '' + mkdir -pv $out + cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" server_config} $out/server + cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" client_config} $out/client + ''; +in +{ + name = "rp systemd unit"; + + nodes = + let + shared = peer: { config, modulesPath, pkgs, ... }: { + # Need to work around a problem in recent systemd changes. + # It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific + # https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767 + # This can potentially be removed in future nixpkgs updates + systemd.packages = [ + (pkgs.runCommand "rp@.service" { } '' + mkdir -p $out/lib/systemd/system + < ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target + < ${pkgs.rosenpass}/lib/systemd/system/rp@.service \ + sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.iproute2}/bin:${pkgs.wireguard-tools}/bin@' | + sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' | + sed 's@^ExecStart=rp @ExecStart='"${pkgs.rosenpass}"'/bin/rp @' > $out/lib/systemd/system/rp@.service + '') + ]; + environment.systemPackages = [ pkgs.wireguard-tools ]; + networking.interfaces.eth1 = { + ipv4.addresses = [{ + address = peer.ip4; + prefixLength = 24; + }]; + ipv6.addresses = [{ + address = peer.ip6; + prefixLength = 64; + }]; + }; + }; + in + { + server = { + imports = [ (shared server) ]; + networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ]; + }; + client = { + imports = [ (shared client) ]; + }; + }; + testScript = { ... }: '' + from os import system + rp = "${pkgs.rosenpass}/bin/rp" + + start_all() + + for machine in [server, client]: + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("network-online.target") + + with subtest("Key, Config, and Service Setup"): + for name, machine, remote in [("server", server, client), ("client", client, server)]: + # create all the keys + system(f"{rp} genkey {name}-sk") + system(f"{rp} pubkey {name}-sk {name}-pk") + + # copy secret keys to our side + for file in ["pqpk", "pqsk", "wgsk"]: + machine.copy_from_host(f"{name}-sk/{file}", f"/etc/rosenpass/test-rp-device0/{file}") + # copy public keys to other side + for file in ["pqpk", "wgpk"]: + remote.copy_from_host(f"{name}-pk/{file}", f"/etc/rosenpass/test-rp-device0/peers/{name}/{file}") + + machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/test-rp-device0.toml") + + for machine in [server, client]: + machine.succeed("systemctl start rp@test-rp-device0.service") + + for machine in [server, client]: + machine.wait_for_unit("rp@test-rp-device0.service") + + with subtest("compare preshared keys"): + client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5); + server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5); + + def get_psk(m): + psk = m.succeed("wg show test-rp-device0 preshared-keys | awk '{print $2}'") + psk = psk.strip() + assert len(psk.split()) == 1, "Only one PSK" + return psk + + assert get_psk(client) == get_psk(server), "preshared keys need to match" + + with subtest("network test"): + client.succeed("ping -c5 ${server.wg.ip6}") + server.succeed("ping -c5 ${client.wg.ip6}") + ''; +}