at 24.05-pre 6.7 kB view raw
1import ./make-test-python.nix ({ pkgs, ... }: 2let 3 deviceName = "rp0"; 4 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 }; 20in 21{ 22 name = "rosenpass"; 23 24 nodes = 25 let 26 shared = peer: { config, modulesPath, ... }: { 27 imports = [ "${modulesPath}/services/networking/rosenpass.nix" ]; 28 29 boot.kernelModules = [ "wireguard" ]; 30 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 }; 40 41 networking.firewall.allowedUDPPorts = [ 9999 ]; 42 43 systemd.network = { 44 enable = true; 45 networks."rosenpass" = { 46 matchConfig.Name = deviceName; 47 networkConfig.IPForward = true; 48 address = [ "${peer.ip}/64" ]; 49 }; 50 51 netdevs."10-rp0" = { 52 netdevConfig = { 53 Kind = "wireguard"; 54 Name = deviceName; 55 }; 56 wireguardConfig.PrivateKeyFile = "/etc/wireguard/wgsk"; 57 }; 58 }; 59 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) ]; 70 71 networking.firewall.allowedUDPPorts = [ server.wg.listen ]; 72 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 }; 84 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) ]; 97 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 ]; 107 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 }; 117 118 testScript = { ... }: '' 119 from os import system 120 121 # Full path to rosenpass in the store, to avoid fiddling with `$PATH`. 122 rosenpass = "${pkgs.rosenpass}/bin/rosenpass" 123 124 # Path in `/etc` where keys will be placed. 125 etc = "/etc/rosenpass" 126 127 start_all() 128 129 for machine in [server, client]: 130 machine.wait_for_unit("multi-user.target") 131 132 # Gently stop Rosenpass to avoid crashes during key generation/distribution. 133 for machine in [server, client]: 134 machine.execute("systemctl stop rosenpass.service") 135 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") 142 143 for machine in [server, client]: 144 machine.execute("systemctl start rosenpass.service") 145 146 for machine in [server, client]: 147 machine.wait_for_unit("rosenpass.service") 148 149 with subtest("ping"): 150 client.succeed("ping -c 2 -i 0.5 ${server.ip}%${deviceName}") 151 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 ''; 159 160 # NOTE: Below configuration is for "interactive" (=developing/debugging) only. 161 interactive.nodes = 162 let 163 inherit (import ./ssh-keys.nix pkgs) snakeOilPublicKey snakeOilPrivateKey; 164 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 180 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 184 185 # Set up SSH key. 186 mkdir -p /root/.ssh 187 cp ${snakeOilPrivateKey} /root/.ssh/id_ecdsa 188 chmod 0400 /root/.ssh/id_ecdsa 189 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 }; 198 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 }; 217})