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