about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/rosenpass.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests/rosenpass.nix')
-rw-r--r--nixpkgs/nixos/tests/rosenpass.nix217
1 files changed, 217 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/rosenpass.nix b/nixpkgs/nixos/tests/rosenpass.nix
new file mode 100644
index 000000000000..ec4046c8c035
--- /dev/null
+++ b/nixpkgs/nixos/tests/rosenpass.nix
@@ -0,0 +1,217 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  deviceName = "rp0";
+
+  server = {
+    ip = "fe80::1";
+    wg = {
+      public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
+      secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
+      listen = 10000;
+    };
+  };
+  client = {
+    ip = "fe80::2";
+    wg = {
+      public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
+      secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
+    };
+  };
+in
+{
+  name = "rosenpass";
+
+  nodes =
+    let
+      shared = peer: { config, modulesPath, ... }: {
+        imports = [ "${modulesPath}/services/networking/rosenpass.nix" ];
+
+        boot.kernelModules = [ "wireguard" ];
+
+        services.rosenpass = {
+          enable = true;
+          defaultDevice = deviceName;
+          settings = {
+            verbosity = "Verbose";
+            public_key = "/etc/rosenpass/pqpk";
+            secret_key = "/etc/rosenpass/pqsk";
+          };
+        };
+
+        networking.firewall.allowedUDPPorts = [ 9999 ];
+
+        systemd.network = {
+          enable = true;
+          networks."rosenpass" = {
+            matchConfig.Name = deviceName;
+            networkConfig.IPForward = true;
+            address = [ "${peer.ip}/64" ];
+          };
+
+          netdevs."10-rp0" = {
+            netdevConfig = {
+              Kind = "wireguard";
+              Name = deviceName;
+            };
+            wireguardConfig.PrivateKeyFile = "/etc/wireguard/wgsk";
+          };
+        };
+
+        environment.etc."wireguard/wgsk" = {
+          text = peer.wg.secret;
+          user = "systemd-network";
+          group = "systemd-network";
+        };
+      };
+    in
+    {
+      server = {
+        imports = [ (shared server) ];
+
+        networking.firewall.allowedUDPPorts = [ server.wg.listen ];
+
+        systemd.network.netdevs."10-${deviceName}" = {
+          wireguardConfig.ListenPort = server.wg.listen;
+          wireguardPeers = [
+            {
+              wireguardPeerConfig = {
+                AllowedIPs = [ "::/0" ];
+                PublicKey = client.wg.public;
+              };
+            }
+          ];
+        };
+
+        services.rosenpass.settings = {
+          listen = [ "0.0.0.0:9999" ];
+          peers = [
+            {
+              public_key = "/etc/rosenpass/peers/client/pqpk";
+              peer = client.wg.public;
+            }
+          ];
+        };
+      };
+      client = {
+        imports = [ (shared client) ];
+
+        systemd.network.netdevs."10-${deviceName}".wireguardPeers = [
+          {
+            wireguardPeerConfig = {
+              AllowedIPs = [ "::/0" ];
+              PublicKey = server.wg.public;
+              Endpoint = "server:${builtins.toString server.wg.listen}";
+            };
+          }
+        ];
+
+        services.rosenpass.settings.peers = [
+          {
+            public_key = "/etc/rosenpass/peers/server/pqpk";
+            endpoint = "server:9999";
+            peer = server.wg.public;
+          }
+        ];
+      };
+    };
+
+  testScript = { ... }: ''
+    from os import system
+
+    # Full path to rosenpass in the store, to avoid fiddling with `$PATH`.
+    rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
+
+    # Path in `/etc` where keys will be placed.
+    etc = "/etc/rosenpass"
+
+    start_all()
+
+    for machine in [server, client]:
+        machine.wait_for_unit("multi-user.target")
+
+    # Gently stop Rosenpass to avoid crashes during key generation/distribution.
+    for machine in [server, client]:
+        machine.execute("systemctl stop rosenpass.service")
+
+    for (name, machine, remote) in [("server", server, client), ("client", client, server)]:
+        pk, sk = f"{name}.pqpk", f"{name}.pqsk"
+        system(f"{rosenpass} gen-keys --force --secret-key {sk} --public-key {pk}")
+        machine.copy_from_host(sk, f"{etc}/pqsk")
+        machine.copy_from_host(pk, f"{etc}/pqpk")
+        remote.copy_from_host(pk, f"{etc}/peers/{name}/pqpk")
+
+    for machine in [server, client]:
+        machine.execute("systemctl start rosenpass.service")
+
+    for machine in [server, client]:
+        machine.wait_for_unit("rosenpass.service")
+
+    with subtest("ping"):
+        client.succeed("ping -c 2 -i 0.5 ${server.ip}%${deviceName}")
+
+    with subtest("preshared-keys"):
+        # Rosenpass works by setting the WireGuard preshared key at regular intervals.
+        # Thus, if it is not active, then no key will be set, and the output of `wg show` will contain "none".
+        # Otherwise, if it is active, then the key will be set and "none" will not be found in the output of `wg show`.
+        for machine in [server, client]:
+            machine.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5)
+  '';
+
+  # NOTE: Below configuration is for "interactive" (=developing/debugging) only.
+  interactive.nodes =
+    let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPublicKey snakeOilPrivateKey;
+
+      sshAndKeyGeneration = {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        environment.systemPackages = [
+          (pkgs.writeShellApplication {
+            name = "gen-keys";
+            runtimeInputs = [ pkgs.rosenpass ];
+            text = ''
+              HOST="$(hostname)"
+              if [ "$HOST" == "server" ]
+              then
+                PEER="client"
+              else
+                PEER="server"
+              fi
+
+              # Generate keypair.
+              mkdir -vp /etc/rosenpass/peers/$PEER
+              rosenpass gen-keys --force --secret-key /etc/rosenpass/pqsk --public-key /etc/rosenpass/pqpk
+
+              # Set up SSH key.
+              mkdir -p /root/.ssh
+              cp ${snakeOilPrivateKey} /root/.ssh/id_ecdsa
+              chmod 0400 /root/.ssh/id_ecdsa
+
+              # Copy public key to other peer.
+              # shellcheck disable=SC2029
+              ssh -o StrictHostKeyChecking=no $PEER "mkdir -pv /etc/rosenpass/peers/$HOST"
+              scp /etc/rosenpass/pqpk "$PEER:/etc/rosenpass/peers/$HOST/pqpk"
+            '';
+          })
+        ];
+      };
+
+      # Use kmscon <https://www.freedesktop.org/wiki/Software/kmscon/>
+      # to provide a slightly nicer console, and while we're at it,
+      # also use a nice font.
+      # With kmscon, we can for example zoom in/out using [Ctrl] + [+]
+      # and [Ctrl] + [-]
+      niceConsoleAndAutologin.services.kmscon = {
+        enable = true;
+        autologinUser = "root";
+        fonts = [{
+          name = "Fira Code";
+          package = pkgs.fira-code;
+        }];
+      };
+    in
+    {
+      server = sshAndKeyGeneration // niceConsoleAndAutologin;
+      client = sshAndKeyGeneration // niceConsoleAndAutologin;
+    };
+})