about summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/all-tests.nix3
-rw-r--r--nixos/tests/clatd.nix189
-rw-r--r--nixos/tests/fish.nix2
-rw-r--r--nixos/tests/installed-tests/default.nix2
-rw-r--r--nixos/tests/installed-tests/gnome-photos.nix2
-rw-r--r--nixos/tests/jotta-cli.nix25
-rw-r--r--nixos/tests/k3s/single-node.nix12
-rw-r--r--nixos/tests/kanidm.nix19
-rw-r--r--nixos/tests/libreswan.nix6
-rw-r--r--nixos/tests/openssh.nix38
-rw-r--r--nixos/tests/patroni.nix2
-rw-r--r--nixos/tests/postgresql-jit.nix15
-rw-r--r--nixos/tests/postgresql-wal-receiver.nix202
-rw-r--r--nixos/tests/ssh-keys.nix12
14 files changed, 406 insertions, 123 deletions
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 6ef1d8d537980..ba876fe31fc04 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -193,6 +193,7 @@ in {
   cinnamon = handleTest ./cinnamon.nix {};
   cinnamon-wayland = handleTest ./cinnamon-wayland.nix {};
   cjdns = handleTest ./cjdns.nix {};
+  clatd = handleTest ./clatd.nix {};
   clickhouse = handleTest ./clickhouse.nix {};
   cloud-init = handleTest ./cloud-init.nix {};
   cloud-init-hostname = handleTest ./cloud-init-hostname.nix {};
@@ -451,6 +452,7 @@ in {
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
   jool = import ./jool.nix { inherit pkgs runTest; };
+  jotta-cli = handleTest ./jotta-cli.nix {};
   k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
@@ -992,6 +994,7 @@ in {
   v2ray = handleTest ./v2ray.nix {};
   varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; };
   varnish74 = handleTest ./varnish.nix { package = pkgs.varnish74; };
+  varnish75 = handleTest ./varnish.nix { package = pkgs.varnish75; };
   vault = handleTest ./vault.nix {};
   vault-agent = handleTest ./vault-agent.nix {};
   vault-dev = handleTest ./vault-dev.nix {};
diff --git a/nixos/tests/clatd.nix b/nixos/tests/clatd.nix
new file mode 100644
index 0000000000000..00021d87ba5f4
--- /dev/null
+++ b/nixos/tests/clatd.nix
@@ -0,0 +1,189 @@
+# This test verifies that we can ping an IPv4-only server from an IPv6-only
+# client via a NAT64 router using CLAT on the client. The hosts and networks
+# are configured as follows:
+#
+#        +------
+# Client | clat    Address: 192.0.0.1/32  (configured via clatd)
+#        |         Route:   default
+#        |
+#        | eth1    Address: 2001:db8::2/64
+#        |  |      Route:   default via 2001:db8::1
+#        +--|---
+#           | VLAN 3
+#        +--|---
+#        | eth2    Address: 2001:db8::1/64
+# Router |
+#        | nat64   Address: 64:ff9b::1/128
+#        |         Route:   64:ff9b::/96
+#        |         Address: 192.0.2.0/32
+#        |         Route:   192.0.2.0/24
+#        |
+#        | eth1    Address: 100.64.0.1/24
+#        +--|---
+#           | VLAN 2
+#        +--|---
+# Server | eth1    Address: 100.64.0.2/24
+#        |         Route:   192.0.2.0/24 via 100.64.0.1
+#        +------
+
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "clatd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes = {
+    # The server is configured with static IPv4 addresses. RFC 6052 Section 3.1
+    # disallows the mapping of non-global IPv4 addresses like RFC 1918 into the
+    # Well-Known Prefix 64:ff9b::/96. TAYGA also does not allow the mapping of
+    # documentation space (RFC 5737). To circumvent this, 100.64.0.2/24 from
+    # RFC 6589 (Carrier Grade NAT) is used here.
+    # To reach the IPv4 address pool of the NAT64 gateway, there is a static
+    # route configured. In normal cases, where the router would also source NAT
+    # the pool addresses to one IPv4 addresses, this would not be needed.
+    server = {
+      virtualisation.vlans = [
+        2 # towards router
+      ];
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "100.64.0.2/24"
+          ];
+          routes = [
+            { routeConfig = { Destination = "192.0.2.0/24"; Gateway = "100.64.0.1"; }; }
+          ];
+        };
+      };
+    };
+
+    # The router is configured with static IPv4 addresses towards the server
+    # and IPv6 addresses towards the client. For NAT64, the Well-Known prefix
+    # 64:ff9b::/96 is used. NAT64 is done with TAYGA which provides the
+    # tun-interface nat64 and does the translation over it. The IPv6 packets
+    # are sent to this interfaces and received as IPv4 packets and vice versa.
+    # As TAYGA only translates IPv6 addresses to dedicated IPv4 addresses, it
+    # needs a pool of IPv4 addresses which must be at least as big as the
+    # expected amount of clients. In this test, the packets from the pool are
+    # directly routed towards the client. In normal cases, there would be a
+    # second source NAT44 to map all clients behind one IPv4 address.
+    router = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        useNetworkd = true;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    # The client is configured with static IPv6 addresses. It has also a static
+    # default route towards the router. To reach the IPv4-only server, the
+    # client starts the clat daemon which starts and configures the local
+    # IPv4 -> IPv6 translation via Tayga.
+    client = {
+      virtualisation.vlans = [
+        3 # towards router
+      ];
+
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "2001:db8::2/64"
+          ];
+          routes = [
+            { routeConfig = { Destination = "::/0"; Gateway = "2001:db8::1"; }; }
+          ];
+        };
+      };
+
+      services.clatd = {
+        enable = true;
+        settings.plat-prefix = "64:ff9b::/96";
+      };
+
+      environment.systemPackages = [ pkgs.mtr ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    # wait for all machines to start up
+    for machine in client, router, server:
+      machine.wait_for_unit("network-online.target")
+
+    with subtest("Wait for tayga and clatd"):
+      router.wait_for_unit("tayga.service")
+      client.wait_for_unit("clatd.service")
+      # clatd checks if this system has IPv4 connectivity for 10 seconds
+      client.wait_until_succeeds(
+        'journalctl -u clatd -e | grep -q "Starting up TAYGA, using config file"'
+      )
+
+    with subtest("Test ICMP"):
+      client.wait_until_succeeds("ping -c 3 100.64.0.2 >&2")
+
+    with subtest("Test ICMP and show a traceroute"):
+      client.wait_until_succeeds("mtr --show-ips --report-wide 100.64.0.2 >&2")
+
+    client.log(client.execute("systemd-analyze security clatd.service")[1])
+  '';
+})
diff --git a/nixos/tests/fish.nix b/nixos/tests/fish.nix
index 3d9b13c6af70a..c9a1bef51478e 100644
--- a/nixos/tests/fish.nix
+++ b/nixos/tests/fish.nix
@@ -10,6 +10,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         coreutils
         procps # kill collides with coreutils' to test https://github.com/NixOS/nixpkgs/issues/56432
       ];
+      # TODO: remove if/when #267880 is merged and this is a default
+      services.logrotate.enable = false;
     };
 
   testScript =
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index e87edb2007e93..b1ddfe3dcbd80 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -1,5 +1,5 @@
 # NixOS tests for gnome-desktop-testing-runner using software
-# See https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests
+# See https://github.com/NixOS/nixpkgs/issues/34987
 
 { system ? builtins.currentSystem,
   config ? {},
diff --git a/nixos/tests/installed-tests/gnome-photos.nix b/nixos/tests/installed-tests/gnome-photos.nix
index bcb6479ee89c6..010ad97024026 100644
--- a/nixos/tests/installed-tests/gnome-photos.nix
+++ b/nixos/tests/installed-tests/gnome-photos.nix
@@ -13,7 +13,7 @@ makeInstalledTest {
       (stdenv.mkDerivation {
         name = "desktop-gsettings";
         dontUnpack = true;
-        nativeBuildInputs = [ glib wrapGAppsHook ];
+        nativeBuildInputs = [ glib wrapGAppsHook3 ];
         buildInputs = [ gsettings-desktop-schemas ];
         installPhase = ''
           runHook preInstall
diff --git a/nixos/tests/jotta-cli.nix b/nixos/tests/jotta-cli.nix
new file mode 100644
index 0000000000000..0df23ee2cba5c
--- /dev/null
+++ b/nixos/tests/jotta-cli.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+
+  name = "jotta-cli";
+  meta.maintainers = with pkgs.lib.maintainers; [ evenbrenden ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.jotta-cli.enable = true;
+    imports = [ ./common/user-account.nix ];
+  };
+
+  testScript = { nodes, ... }:
+    let uid = toString nodes.machine.users.users.alice.uid;
+    in ''
+      machine.start()
+
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_for_unit("user@${uid}.service")
+
+      machine.wait_for_unit("jottad.service", "alice")
+      machine.wait_for_open_unix_socket("/run/user/${uid}/jottad/jottad.socket")
+
+      # "jotta-cli version" should fail if jotta-cli cannot connect to jottad
+      machine.succeed('XDG_RUNTIME_DIR=/run/user/${uid} su alice -c "jotta-cli version"')
+    '';
+})
diff --git a/nixos/tests/k3s/single-node.nix b/nixos/tests/k3s/single-node.nix
index fd64a050e61ef..b7ac5d9eeeac7 100644
--- a/nixos/tests/k3s/single-node.nix
+++ b/nixos/tests/k3s/single-node.nix
@@ -78,6 +78,18 @@ import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
       # regression test for #176445
       machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'")
 
+      with subtest("Run k3s-killall"):
+          # Call the killall script with a clean path to assert that
+          # all required commands are wrapped
+          output = machine.succeed("PATH= ${k3s}/bin/k3s-killall.sh 2>&1 | tee /dev/stderr")
+          assert "command not found" not in output, "killall script contains unknown command"
+
+          # Check that killall cleaned up properly
+          machine.fail("systemctl is-active k3s.service")
+          machine.fail("systemctl list-units | grep containerd")
+          machine.fail("ip link show | awk -F': ' '{print $2}' | grep -e flannel -e cni0")
+          machine.fail("ip netns show | grep cni-")
+
       machine.shutdown()
     '';
   })
diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix
index fa24d4a8a5e13..8ed9af63f1d41 100644
--- a/nixos/tests/kanidm.nix
+++ b/nixos/tests/kanidm.nix
@@ -76,14 +76,17 @@ import ./make-test-python.nix ({ pkgs, ... }:
         with subtest("Test LDAP interface"):
             server.succeed("ldapsearch -H ldaps://${serverDomain}:636 -b '${ldapBaseDN}' -x '(name=test)'")
 
-        with subtest("Test CLI login"):
-            client.succeed("kanidm login -D anonymous")
-            client.succeed("kanidm self whoami | grep anonymous@${serverDomain}")
-            client.succeed("kanidm logout")
-
         with subtest("Recover idm_admin account"):
             idm_admin_password = server.succeed("su - kanidm -c 'kanidmd recover-account -c ${serverConfigFile} idm_admin 2>&1 | rg -o \'[A-Za-z0-9]{48}\' '").strip().removeprefix("'").removesuffix("'")
 
+        with subtest("Test CLI login"):
+            client.wait_until_tty_matches("1", "login: ")
+            client.send_chars("root\n")
+            client.send_chars("kanidm login -D idm_admin\n")
+            client.wait_until_tty_matches("1", "Enter password: ")
+            client.send_chars(f"{idm_admin_password}\n")
+            client.wait_until_tty_matches("1", "Login Success for idm_admin")
+
         with subtest("Test unixd connection"):
             client.wait_for_unit("kanidm-unixd.service")
             client.wait_for_file("/run/kanidm-unixd/sock")
@@ -92,12 +95,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
         with subtest("Test user creation"):
             client.wait_for_unit("getty@tty1.service")
             client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
-            client.wait_until_tty_matches("1", "login: ")
-            client.send_chars("root\n")
-            client.send_chars("kanidm login -D idm_admin\n")
-            client.wait_until_tty_matches("1", "Enter password: ")
-            client.send_chars(f"{idm_admin_password}\n")
-            client.wait_until_tty_matches("1", "Login Success for idm_admin")
             client.succeed("kanidm person create testuser TestUser")
             client.succeed("kanidm person posix set --shell \"$SHELL\" testuser")
             client.send_chars("kanidm person posix set-password testuser\n")
diff --git a/nixos/tests/libreswan.nix b/nixos/tests/libreswan.nix
index aadba941fab17..c798a04645bc0 100644
--- a/nixos/tests/libreswan.nix
+++ b/nixos/tests/libreswan.nix
@@ -119,11 +119,11 @@ in
       with subtest("Libreswan is ready"):
           alice.wait_for_unit("ipsec")
           bob.wait_for_unit("ipsec")
-          alice.succeed("ipsec verify 1>&2")
+          alice.succeed("ipsec checkconfig")
 
       with subtest("Alice and Bob can start the tunnel"):
-          alice.execute("ipsec auto --start tunnel >&2 &")
-          bob.succeed("ipsec auto --start tunnel")
+          alice.execute("ipsec start tunnel >&2 &")
+          bob.succeed("ipsec start tunnel")
           # apparently this is needed to "wake" the tunnel
           bob.execute("ping -c1 alice")
 
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index a039986621cab..2684b6f45e84e 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }:
 
 let inherit (import ./ssh-keys.nix pkgs)
-      snakeOilPrivateKey snakeOilPublicKey;
+      snakeOilPrivateKey snakeOilPublicKey snakeOilEd25519PrivateKey snakeOilEd25519PublicKey;
 in {
   name = "openssh";
   meta = with pkgs.lib.maintainers; {
@@ -108,6 +108,31 @@ in {
         };
       };
 
+    server-no-openssl =
+      { ... }:
+      {
+        programs.ssh.package = pkgs.opensshPackages.openssh.override {
+          linkOpenssl = false;
+        };
+        services.openssh = {
+          enable = true;
+          hostKeys = [
+            { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
+          ];
+          settings = {
+            # Must not specify the OpenSSL provided algorithms.
+            Ciphers = [ "chacha20-poly1305@openssh.com" ];
+            KexAlgorithms = [
+              "curve25519-sha256"
+              "curve25519-sha256@libssh.org"
+            ];
+          };
+        };
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilEd25519PublicKey
+        ];
+      };
+
     server-no-pam =
       { pkgs, ... }:
       {
@@ -139,6 +164,7 @@ in {
     server_allowed_users.wait_for_unit("sshd", timeout=30)
     server_localhost_only.wait_for_unit("sshd", timeout=30)
     server_match_rule.wait_for_unit("sshd", timeout=30)
+    server_no_openssl.wait_for_unit("sshd", timeout=30)
     server_no_pam.wait_for_unit("sshd", timeout=30)
 
     server_lazy.wait_for_unit("sshd.socket", timeout=30)
@@ -230,6 +256,16 @@ in {
             timeout=30
         )
 
+    with subtest("no-openssl"):
+        client.succeed(
+            "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-openssl true",
+            timeout=30
+        )
+
     with subtest("no-pam"):
         client.succeed(
             "cat ${snakeOilPrivateKey} > privkey.snakeoil"
diff --git a/nixos/tests/patroni.nix b/nixos/tests/patroni.nix
index 1f15cd59677ad..68fce4051553e 100644
--- a/nixos/tests/patroni.nix
+++ b/nixos/tests/patroni.nix
@@ -155,7 +155,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
               print(node.succeed("patronictl list cluster1"))
               node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'length') == {expected_replicas + 1} ]")
               node.wait_until_succeeds("[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Leader$\"))) | map(select(.State | test(\"^running$\"))) | length') == 1 ]")
-              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Replica$\"))) | map(select(.State | test(\"^running$\"))) | length') == {expected_replicas} ]")
+              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Replica$\"))) | map(select(.State | test(\"^streaming$\"))) | length') == {expected_replicas} ]")
               print(node.succeed("patronictl list cluster1"))
           client.wait_until_succeeds("psql -h 127.0.0.1 -U postgres --command='select 1;'")
 
diff --git a/nixos/tests/postgresql-jit.nix b/nixos/tests/postgresql-jit.nix
index baf26b8da2b39..f4b1d07a7faf8 100644
--- a/nixos/tests/postgresql-jit.nix
+++ b/nixos/tests/postgresql-jit.nix
@@ -1,6 +1,7 @@
 { system ? builtins.currentSystem
 , config ? {}
 , pkgs ? import ../.. { inherit system config; }
+, package ? null
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -9,14 +10,17 @@ let
   inherit (pkgs) lib;
   packages = builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs);
 
-  mkJitTest = packageName: makeTest {
-    name = "${packageName}";
+  mkJitTestFromName = name:
+    mkJitTest pkgs.${name};
+
+  mkJitTest = package: makeTest {
+    name = package.name;
     meta.maintainers = with lib.maintainers; [ ma27 ];
     nodes.machine = { pkgs, lib, ... }: {
       services.postgresql = {
+        inherit package;
         enable = true;
         enableJIT = true;
-        package = pkgs.${packageName};
         initialScript = pkgs.writeText "init.sql" ''
           create table demo (id int);
           insert into demo (id) select generate_series(1, 5);
@@ -45,4 +49,7 @@ let
     '';
   };
 in
-lib.genAttrs packages mkJitTest
+if package == null then
+  lib.genAttrs packages mkJitTestFromName
+else
+  mkJitTest package
diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix
index b0bd7711dbcd9..ab2ab4ad0d4fa 100644
--- a/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixos/tests/postgresql-wal-receiver.nix
@@ -1,6 +1,7 @@
 { system ? builtins.currentSystem,
   config ? {},
-  pkgs ? import ../.. { inherit system config; }
+  pkgs ? import ../.. { inherit system config; },
+  package ? null
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -9,111 +10,110 @@ let
   lib = pkgs.lib;
 
   # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`.
-  makePostgresqlWalReceiverTest = postgresqlPackage:
+  makeTestAttribute = name:
   {
-    name = postgresqlPackage;
-    value =
-      let
-        pkg = pkgs."${postgresqlPackage}";
-        postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}";
-        replicationUser = "wal_receiver_user";
-        replicationSlot = "wal_receiver_slot";
-        replicationConn = "postgresql://${replicationUser}@localhost";
-        baseBackupDir = "/tmp/pg_basebackup";
-        walBackupDir = "/tmp/pg_wal";
-        atLeast12 = lib.versionAtLeast pkg.version "12.0";
-
-        recoveryFile = if atLeast12
-            then pkgs.writeTextDir "recovery.signal" ""
-            else pkgs.writeTextDir "recovery.conf" "restore_command = 'cp ${walBackupDir}/%f %p'";
-
-      in makeTest {
-        name = "postgresql-wal-receiver-${postgresqlPackage}";
-        meta.maintainers = with lib.maintainers; [ pacien ];
-
-        nodes.machine = { ... }: {
-          services.postgresql = {
-            package = pkg;
-            enable = true;
-            settings = lib.mkMerge [
-              {
-                wal_level = "archive"; # alias for replica on pg >= 9.6
-                max_wal_senders = 10;
-                max_replication_slots = 10;
-              }
-              (lib.mkIf atLeast12 {
-                restore_command = "cp ${walBackupDir}/%f %p";
-                recovery_end_command = "touch recovery.done";
-              })
-            ];
-            authentication = ''
-              host replication ${replicationUser} all trust
-            '';
-            initialScript = pkgs.writeText "init.sql" ''
-              create user ${replicationUser} replication;
-              select * from pg_create_physical_replication_slot('${replicationSlot}');
-            '';
-          };
+    inherit name;
+    value = makePostgresqlWalReceiverTest pkgs."${name}";
+  };
+
+  makePostgresqlWalReceiverTest = pkg:
+    let
+      postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}";
+      replicationUser = "wal_receiver_user";
+      replicationSlot = "wal_receiver_slot";
+      replicationConn = "postgresql://${replicationUser}@localhost";
+      baseBackupDir = "/tmp/pg_basebackup";
+      walBackupDir = "/tmp/pg_wal";
+
+      recoveryFile = pkgs.writeTextDir "recovery.signal" "";
 
-          services.postgresqlWalReceiver.receivers.main = {
-            postgresqlPackage = pkg;
-            connection = replicationConn;
-            slot = replicationSlot;
-            directory = walBackupDir;
+    in makeTest {
+      name = "postgresql-wal-receiver-${pkg.name}";
+      meta.maintainers = with lib.maintainers; [ pacien ];
+
+      nodes.machine = { ... }: {
+        services.postgresql = {
+          package = pkg;
+          enable = true;
+          settings = {
+            max_replication_slots = 10;
+            max_wal_senders = 10;
+            recovery_end_command = "touch recovery.done";
+            restore_command = "cp ${walBackupDir}/%f %p";
+            wal_level = "archive"; # alias for replica on pg >= 9.6
           };
-          # This is only to speedup test, it isn't time racing. Service is set to autorestart always,
-          # default 60sec is fine for real system, but is too much for a test
-          systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
+          authentication = ''
+            host replication ${replicationUser} all trust
+          '';
+          initialScript = pkgs.writeText "init.sql" ''
+            create user ${replicationUser} replication;
+            select * from pg_create_physical_replication_slot('${replicationSlot}');
+          '';
         };
 
-        testScript = ''
-          # make an initial base backup
-          machine.wait_for_unit("postgresql")
-          machine.wait_for_unit("postgresql-wal-receiver-main")
-          # WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
-          # required only for 9.4
-          machine.sleep(5)
-          machine.succeed(
-              "${pkg}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
-          )
-
-          # create a dummy table with 100 records
-          machine.succeed(
-              "sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
-          )
-
-          # stop postgres and destroy data
-          machine.systemctl("stop postgresql")
-          machine.systemctl("stop postgresql-wal-receiver-main")
-          machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
-
-          # restore the base backup
-          machine.succeed(
-              "cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
-          )
-
-          # prepare WAL and recovery
-          machine.succeed("chmod a+rX -R ${walBackupDir}")
-          machine.execute(
-              "for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
-          )  # make use of partial segments too
-          machine.succeed(
-              "cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
-          )
-
-          # replay WAL
-          machine.systemctl("start postgresql")
-          machine.wait_for_file("${postgresqlDataDir}/recovery.done")
-          machine.systemctl("restart postgresql")
-          machine.wait_for_unit("postgresql")
-
-          # check that our records have been restored
-          machine.succeed(
-              "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
-          )
-        '';
+        services.postgresqlWalReceiver.receivers.main = {
+          postgresqlPackage = pkg;
+          connection = replicationConn;
+          slot = replicationSlot;
+          directory = walBackupDir;
+        };
+        # This is only to speedup test, it isn't time racing. Service is set to autorestart always,
+        # default 60sec is fine for real system, but is too much for a test
+        systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
       };
+
+      testScript = ''
+        # make an initial base backup
+        machine.wait_for_unit("postgresql")
+        machine.wait_for_unit("postgresql-wal-receiver-main")
+        # WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
+        # required only for 9.4
+        machine.sleep(5)
+        machine.succeed(
+            "${pkg}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
+        )
+
+        # create a dummy table with 100 records
+        machine.succeed(
+            "sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
+        )
+
+        # stop postgres and destroy data
+        machine.systemctl("stop postgresql")
+        machine.systemctl("stop postgresql-wal-receiver-main")
+        machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
+
+        # restore the base backup
+        machine.succeed(
+            "cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
+        )
+
+        # prepare WAL and recovery
+        machine.succeed("chmod a+rX -R ${walBackupDir}")
+        machine.execute(
+            "for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
+        )  # make use of partial segments too
+        machine.succeed(
+            "cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
+        )
+
+        # replay WAL
+        machine.systemctl("start postgresql")
+        machine.wait_for_file("${postgresqlDataDir}/recovery.done")
+        machine.systemctl("restart postgresql")
+        machine.wait_for_unit("postgresql")
+
+        # check that our records have been restored
+        machine.succeed(
+            "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
+        )
+      '';
     };
 
-# Maps the generic function over all attributes of PostgreSQL packages
-in builtins.listToAttrs (map makePostgresqlWalReceiverTest (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)))
+in
+if package == null then
+  # all-tests.nix: Maps the generic function over all attributes of PostgreSQL packages
+  builtins.listToAttrs (map makeTestAttribute (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)))
+else
+  # Called directly from <package>.tests
+  makePostgresqlWalReceiverTest package
diff --git a/nixos/tests/ssh-keys.nix b/nixos/tests/ssh-keys.nix
index df9ff38a3b22a..675f3a0b43947 100644
--- a/nixos/tests/ssh-keys.nix
+++ b/nixos/tests/ssh-keys.nix
@@ -12,4 +12,16 @@ pkgs:
     "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
     "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= snakeoil"
   ];
+
+  snakeOilEd25519PrivateKey = pkgs.writeText "privkey.snakeoil" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACAYBTIWo1J4PkY4/7AhVyPT8xvAUI67tp+yYFFRdSm7+QAAAJC89yCivPcg
+    ogAAAAtzc2gtZWQyNTUxOQAAACAYBTIWo1J4PkY4/7AhVyPT8xvAUI67tp+yYFFRdSm7+Q
+    AAAEDJmKp3lX6Pz0unTc0QZwrHb8Eyr9fJUopE9d2/+q+eCxgFMhajUng+Rjj/sCFXI9Pz
+    G8BQjru2n7JgUVF1Kbv5AAAACnRvbUBvemRlc2sBAgM=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  snakeOilEd25519PublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBgFMhajUng+Rjj/sCFXI9PzG8BQjru2n7JgUVF1Kbv5 snakeoil";
 }