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