Release NixOS 23.11
[NixPkgs.git] / nixos / tests / rosenpass.nix
blobec4046c8c035be211436520b64842d43911b09b3
1 import ./make-test-python.nix ({ pkgs, ... }:
2 let
3   deviceName = "rp0";
5   server = {
6     ip = "fe80::1";
7     wg = {
8       public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
9       secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
10       listen = 10000;
11     };
12   };
13   client = {
14     ip = "fe80::2";
15     wg = {
16       public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
17       secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
18     };
19   };
22   name = "rosenpass";
24   nodes =
25     let
26       shared = peer: { config, modulesPath, ... }: {
27         imports = [ "${modulesPath}/services/networking/rosenpass.nix" ];
29         boot.kernelModules = [ "wireguard" ];
31         services.rosenpass = {
32           enable = true;
33           defaultDevice = deviceName;
34           settings = {
35             verbosity = "Verbose";
36             public_key = "/etc/rosenpass/pqpk";
37             secret_key = "/etc/rosenpass/pqsk";
38           };
39         };
41         networking.firewall.allowedUDPPorts = [ 9999 ];
43         systemd.network = {
44           enable = true;
45           networks."rosenpass" = {
46             matchConfig.Name = deviceName;
47             networkConfig.IPForward = true;
48             address = [ "${peer.ip}/64" ];
49           };
51           netdevs."10-rp0" = {
52             netdevConfig = {
53               Kind = "wireguard";
54               Name = deviceName;
55             };
56             wireguardConfig.PrivateKeyFile = "/etc/wireguard/wgsk";
57           };
58         };
60         environment.etc."wireguard/wgsk" = {
61           text = peer.wg.secret;
62           user = "systemd-network";
63           group = "systemd-network";
64         };
65       };
66     in
67     {
68       server = {
69         imports = [ (shared server) ];
71         networking.firewall.allowedUDPPorts = [ server.wg.listen ];
73         systemd.network.netdevs."10-${deviceName}" = {
74           wireguardConfig.ListenPort = server.wg.listen;
75           wireguardPeers = [
76             {
77               wireguardPeerConfig = {
78                 AllowedIPs = [ "::/0" ];
79                 PublicKey = client.wg.public;
80               };
81             }
82           ];
83         };
85         services.rosenpass.settings = {
86           listen = [ "0.0.0.0:9999" ];
87           peers = [
88             {
89               public_key = "/etc/rosenpass/peers/client/pqpk";
90               peer = client.wg.public;
91             }
92           ];
93         };
94       };
95       client = {
96         imports = [ (shared client) ];
98         systemd.network.netdevs."10-${deviceName}".wireguardPeers = [
99           {
100             wireguardPeerConfig = {
101               AllowedIPs = [ "::/0" ];
102               PublicKey = server.wg.public;
103               Endpoint = "server:${builtins.toString server.wg.listen}";
104             };
105           }
106         ];
108         services.rosenpass.settings.peers = [
109           {
110             public_key = "/etc/rosenpass/peers/server/pqpk";
111             endpoint = "server:9999";
112             peer = server.wg.public;
113           }
114         ];
115       };
116     };
118   testScript = { ... }: ''
119     from os import system
121     # Full path to rosenpass in the store, to avoid fiddling with `$PATH`.
122     rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
124     # Path in `/etc` where keys will be placed.
125     etc = "/etc/rosenpass"
127     start_all()
129     for machine in [server, client]:
130         machine.wait_for_unit("multi-user.target")
132     # Gently stop Rosenpass to avoid crashes during key generation/distribution.
133     for machine in [server, client]:
134         machine.execute("systemctl stop rosenpass.service")
136     for (name, machine, remote) in [("server", server, client), ("client", client, server)]:
137         pk, sk = f"{name}.pqpk", f"{name}.pqsk"
138         system(f"{rosenpass} gen-keys --force --secret-key {sk} --public-key {pk}")
139         machine.copy_from_host(sk, f"{etc}/pqsk")
140         machine.copy_from_host(pk, f"{etc}/pqpk")
141         remote.copy_from_host(pk, f"{etc}/peers/{name}/pqpk")
143     for machine in [server, client]:
144         machine.execute("systemctl start rosenpass.service")
146     for machine in [server, client]:
147         machine.wait_for_unit("rosenpass.service")
149     with subtest("ping"):
150         client.succeed("ping -c 2 -i 0.5 ${server.ip}%${deviceName}")
152     with subtest("preshared-keys"):
153         # Rosenpass works by setting the WireGuard preshared key at regular intervals.
154         # Thus, if it is not active, then no key will be set, and the output of `wg show` will contain "none".
155         # Otherwise, if it is active, then the key will be set and "none" will not be found in the output of `wg show`.
156         for machine in [server, client]:
157             machine.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5)
158   '';
160   # NOTE: Below configuration is for "interactive" (=developing/debugging) only.
161   interactive.nodes =
162     let
163       inherit (import ./ssh-keys.nix pkgs) snakeOilPublicKey snakeOilPrivateKey;
165       sshAndKeyGeneration = {
166         services.openssh.enable = true;
167         users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
168         environment.systemPackages = [
169           (pkgs.writeShellApplication {
170             name = "gen-keys";
171             runtimeInputs = [ pkgs.rosenpass ];
172             text = ''
173               HOST="$(hostname)"
174               if [ "$HOST" == "server" ]
175               then
176                 PEER="client"
177               else
178                 PEER="server"
179               fi
181               # Generate keypair.
182               mkdir -vp /etc/rosenpass/peers/$PEER
183               rosenpass gen-keys --force --secret-key /etc/rosenpass/pqsk --public-key /etc/rosenpass/pqpk
185               # Set up SSH key.
186               mkdir -p /root/.ssh
187               cp ${snakeOilPrivateKey} /root/.ssh/id_ecdsa
188               chmod 0400 /root/.ssh/id_ecdsa
190               # Copy public key to other peer.
191               # shellcheck disable=SC2029
192               ssh -o StrictHostKeyChecking=no $PEER "mkdir -pv /etc/rosenpass/peers/$HOST"
193               scp /etc/rosenpass/pqpk "$PEER:/etc/rosenpass/peers/$HOST/pqpk"
194             '';
195           })
196         ];
197       };
199       # Use kmscon <https://www.freedesktop.org/wiki/Software/kmscon/>
200       # to provide a slightly nicer console, and while we're at it,
201       # also use a nice font.
202       # With kmscon, we can for example zoom in/out using [Ctrl] + [+]
203       # and [Ctrl] + [-]
204       niceConsoleAndAutologin.services.kmscon = {
205         enable = true;
206         autologinUser = "root";
207         fonts = [{
208           name = "Fira Code";
209           package = pkgs.fira-code;
210         }];
211       };
212     in
213     {
214       server = sshAndKeyGeneration // niceConsoleAndAutologin;
215       client = sshAndKeyGeneration // niceConsoleAndAutologin;
216     };