at master 12 kB view raw
1{ pkgs, ... }: 2 3let 4 inherit (import ./ssh-keys.nix pkgs) 5 snakeOilPrivateKey 6 snakeOilPublicKey 7 snakeOilEd25519PrivateKey 8 snakeOilEd25519PublicKey 9 ; 10in 11{ 12 name = "openssh"; 13 meta = with pkgs.lib.maintainers; { 14 maintainers = [ aszlig ]; 15 }; 16 17 nodes = { 18 19 server = 20 { ... }: 21 22 { 23 services.openssh.enable = true; 24 security.pam.services.sshd.limits = [ 25 { 26 domain = "*"; 27 item = "memlock"; 28 type = "-"; 29 value = 1024; 30 } 31 ]; 32 users.users.root.openssh.authorizedKeys.keys = [ 33 snakeOilPublicKey 34 ]; 35 }; 36 37 server-allowed-users = 38 { ... }: 39 40 { 41 services.openssh = { 42 enable = true; 43 settings.AllowUsers = [ 44 "alice" 45 "bob" 46 ]; 47 }; 48 users.groups = { 49 alice = { }; 50 bob = { }; 51 carol = { }; 52 }; 53 users.users = { 54 alice = { 55 isNormalUser = true; 56 group = "alice"; 57 openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 58 }; 59 bob = { 60 isNormalUser = true; 61 group = "bob"; 62 openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 63 }; 64 carol = { 65 isNormalUser = true; 66 group = "carol"; 67 openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 68 }; 69 }; 70 }; 71 72 server-lazy = 73 { ... }: 74 75 { 76 services.openssh = { 77 enable = true; 78 startWhenNeeded = true; 79 }; 80 security.pam.services.sshd.limits = [ 81 { 82 domain = "*"; 83 item = "memlock"; 84 type = "-"; 85 value = 1024; 86 } 87 ]; 88 users.users.root.openssh.authorizedKeys.keys = [ 89 snakeOilPublicKey 90 ]; 91 }; 92 93 server-lazy-socket = { 94 virtualisation.vlans = [ 95 1 96 2 97 ]; 98 services.openssh = { 99 enable = true; 100 startWhenNeeded = true; 101 ports = [ 2222 ]; 102 listenAddresses = [ { addr = "0.0.0.0"; } ]; 103 }; 104 users.users.root.openssh.authorizedKeys.keys = [ 105 snakeOilPublicKey 106 ]; 107 }; 108 109 server-localhost-only = 110 { ... }: 111 112 { 113 services.openssh = { 114 enable = true; 115 listenAddresses = [ 116 { 117 addr = "127.0.0.1"; 118 port = 22; 119 } 120 ]; 121 }; 122 }; 123 124 server-localhost-only-lazy = 125 { ... }: 126 127 { 128 services.openssh = { 129 enable = true; 130 startWhenNeeded = true; 131 listenAddresses = [ 132 { 133 addr = "127.0.0.1"; 134 port = 22; 135 } 136 ]; 137 }; 138 }; 139 140 server-match-rule = 141 { ... }: 142 143 { 144 services.openssh = { 145 enable = true; 146 listenAddresses = [ 147 { 148 addr = "127.0.0.1"; 149 port = 22; 150 } 151 { 152 addr = "[::]"; 153 port = 22; 154 } 155 ]; 156 extraConfig = '' 157 # Combined test for two (predictable) Match criterias 158 Match LocalAddress 127.0.0.1 LocalPort 22 159 PermitRootLogin yes 160 161 # Separate tests for Match criterias 162 Match User root 163 PermitRootLogin yes 164 Match Group root 165 PermitRootLogin yes 166 Match Host nohost.example 167 PermitRootLogin yes 168 Match LocalAddress 127.0.0.1 169 PermitRootLogin yes 170 Match LocalPort 22 171 PermitRootLogin yes 172 Match RDomain nohost.example 173 PermitRootLogin yes 174 Match Address 127.0.0.1 175 PermitRootLogin yes 176 ''; 177 }; 178 }; 179 180 server-no-openssl = 181 { ... }: 182 { 183 services.openssh = { 184 enable = true; 185 package = pkgs.opensshPackages.openssh.override { 186 linkOpenssl = false; 187 }; 188 hostKeys = [ 189 { 190 type = "ed25519"; 191 path = "/etc/ssh/ssh_host_ed25519_key"; 192 } 193 ]; 194 settings = { 195 # Since this test is against an OpenSSH-without-OpenSSL, 196 # we have to override NixOS's defaults ciphers (which require OpenSSL) 197 # and instead set these to null, which will mean OpenSSH uses its defaults. 198 # Expectedly, OpenSSH's defaults don't require OpenSSL when it's compiled 199 # without OpenSSL. 200 Ciphers = null; 201 KexAlgorithms = null; 202 Macs = null; 203 }; 204 }; 205 users.users.root.openssh.authorizedKeys.keys = [ 206 snakeOilEd25519PublicKey 207 ]; 208 }; 209 210 server-no-pam = 211 { pkgs, ... }: 212 { 213 services.openssh = { 214 enable = true; 215 package = pkgs.opensshPackages.openssh.override { 216 withPAM = false; 217 }; 218 settings = { 219 UsePAM = false; 220 }; 221 }; 222 users.users.root.openssh.authorizedKeys.keys = [ 223 snakeOilPublicKey 224 ]; 225 }; 226 227 server-sftp = 228 { pkgs, ... }: 229 { 230 services.openssh = { 231 enable = true; 232 extraConfig = '' 233 Match Group sftponly 234 ChrootDirectory /srv/sftp 235 ForceCommand internal-sftp 236 ''; 237 }; 238 239 users.groups = { 240 sftponly = { }; 241 }; 242 users.users = { 243 alice = { 244 isNormalUser = true; 245 createHome = false; 246 group = "sftponly"; 247 shell = "/run/current-system/sw/bin/nologin"; 248 openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 249 }; 250 }; 251 }; 252 253 client = 254 { ... }: 255 { 256 virtualisation.vlans = [ 257 1 258 2 259 ]; 260 }; 261 262 }; 263 264 testScript = '' 265 start_all() 266 267 server.wait_for_unit("sshd", timeout=30) 268 server_allowed_users.wait_for_unit("sshd", timeout=30) 269 server_localhost_only.wait_for_unit("sshd", timeout=30) 270 server_match_rule.wait_for_unit("sshd", timeout=30) 271 server_no_openssl.wait_for_unit("sshd", timeout=30) 272 server_no_pam.wait_for_unit("sshd", timeout=30) 273 server_sftp.wait_for_unit("sshd", timeout=30) 274 275 server_lazy.wait_for_unit("sshd.socket", timeout=30) 276 server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30) 277 server_lazy_socket.wait_for_unit("sshd.socket", timeout=30) 278 279 with subtest("manual-authkey"): 280 client.succeed( 281 '${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""' 282 ) 283 public_key = client.succeed( 284 "${pkgs.openssh}/bin/ssh-keygen -y -f /root/.ssh/id_ed25519" 285 ) 286 public_key = public_key.strip() 287 client.succeed("chmod 600 /root/.ssh/id_ed25519") 288 289 server.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key)) 290 server_lazy.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key)) 291 292 client.wait_for_unit("network.target") 293 client.succeed( 294 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2", 295 timeout=30 296 ) 297 client.succeed( 298 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024", 299 timeout=30 300 ) 301 302 client.succeed( 303 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'echo hello world' >&2", 304 timeout=30 305 ) 306 client.succeed( 307 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'ulimit -l' | grep 1024", 308 timeout=30 309 ) 310 311 with subtest("socket activation on a non-standard port"): 312 client.succeed( 313 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 314 ) 315 client.succeed("chmod 600 privkey.snakeoil") 316 # The final segment in this IP is allocated according to the alphabetical order of machines in this test. 317 client.succeed( 318 "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.5 true", 319 timeout=30 320 ) 321 322 with subtest("configured-authkey"): 323 client.succeed( 324 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 325 ) 326 client.succeed("chmod 600 privkey.snakeoil") 327 client.succeed( 328 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true", 329 timeout=30 330 ) 331 client.succeed( 332 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-lazy true", 333 timeout=30 334 ) 335 336 with subtest("localhost-only"): 337 server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'") 338 server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'") 339 340 with subtest("match-rules"): 341 server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'") 342 343 with subtest("allowed-users"): 344 client.succeed( 345 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 346 ) 347 client.succeed("chmod 600 privkey.snakeoil") 348 client.succeed( 349 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server-allowed-users true", 350 timeout=30 351 ) 352 client.succeed( 353 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server-allowed-users true", 354 timeout=30 355 ) 356 client.fail( 357 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server-allowed-users true", 358 timeout=30 359 ) 360 361 with subtest("no-openssl"): 362 client.succeed( 363 "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil" 364 ) 365 client.succeed("chmod 600 privkey.snakeoil") 366 client.succeed( 367 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-openssl true", 368 timeout=30 369 ) 370 371 with subtest("no-pam"): 372 client.succeed( 373 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 374 ) 375 client.succeed("chmod 600 privkey.snakeoil") 376 client.succeed( 377 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-pam true", 378 timeout=30 379 ) 380 381 with subtest("sftp"): 382 server_sftp.succeed( 383 "mkdir -p /srv/sftp/uploads" 384 ) 385 server_sftp.succeed( 386 "chown alice:sftponly /srv/sftp/uploads" 387 ) 388 server_sftp.succeed( 389 "chmod 0755 /srv/sftp/uploads" 390 ) 391 392 client.succeed( 393 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 394 ) 395 client.succeed("chmod 600 privkey.snakeoil") 396 397 client.succeed( 398 "echo 'hello-sftp-world' > test-file" 399 ) 400 client.succeed( 401 "echo 'put test-file uploads/' > put-batch-file" 402 ) 403 404 client.succeed( 405 "sftp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil -b put-batch-file alice@server-sftp", 406 timeout=30 407 ) 408 409 server_sftp.wait_for_file("/srv/sftp/uploads/test-file") 410 411 # None of the per-connection units should have failed. 412 server_lazy.fail("systemctl is-failed 'sshd@*.service'") 413 ''; 414}