at 23.11-beta 29 kB view raw
1{ pkgs, lib, ... }: let 2 commonConfig = ./common/acme/client; 3 4 dnsServerIP = nodes: nodes.dnsserver.networking.primaryIPAddress; 5 6 dnsScript = nodes: let 7 dnsAddress = dnsServerIP nodes; 8 in pkgs.writeShellScript "dns-hook.sh" '' 9 set -euo pipefail 10 echo '[INFO]' "[$2]" 'dns-hook.sh' $* 11 if [ "$1" = "present" ]; then 12 ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt 13 else 14 ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt 15 fi 16 ''; 17 18 dnsConfig = nodes: { 19 dnsProvider = "exec"; 20 dnsPropagationCheck = false; 21 environmentFile = pkgs.writeText "wildcard.env" '' 22 EXEC_PATH=${dnsScript nodes} 23 EXEC_POLLING_INTERVAL=1 24 EXEC_PROPAGATION_TIMEOUT=1 25 EXEC_SEQUENCE_INTERVAL=1 26 ''; 27 }; 28 29 documentRoot = pkgs.runCommand "docroot" {} '' 30 mkdir -p "$out" 31 echo hello world > "$out/index.html" 32 ''; 33 34 vhostBase = { 35 forceSSL = true; 36 locations."/".root = documentRoot; 37 }; 38 39 vhostBaseHttpd = { 40 forceSSL = true; 41 inherit documentRoot; 42 }; 43 44 simpleConfig = { 45 security.acme = { 46 certs."http.example.test" = { 47 listenHTTP = ":80"; 48 }; 49 }; 50 51 networking.firewall.allowedTCPPorts = [ 80 ]; 52 }; 53 54 # Base specialisation config for testing general ACME features 55 webserverBasicConfig = { 56 services.nginx.enable = true; 57 services.nginx.virtualHosts."a.example.test" = vhostBase // { 58 enableACME = true; 59 }; 60 }; 61 62 # Generate specialisations for testing a web server 63 mkServerConfigs = { server, group, vhostBaseData, extraConfig ? {} }: let 64 baseConfig = { nodes, config, specialConfig ? {} }: lib.mkMerge [ 65 { 66 security.acme = { 67 defaults = (dnsConfig nodes); 68 # One manual wildcard cert 69 certs."example.test" = { 70 domain = "*.example.test"; 71 }; 72 }; 73 74 users.users."${config.services."${server}".user}".extraGroups = ["acme"]; 75 76 services."${server}" = { 77 enable = true; 78 virtualHosts = { 79 # Run-of-the-mill vhost using HTTP-01 validation 80 "${server}-http.example.test" = vhostBaseData // { 81 serverAliases = [ "${server}-http-alias.example.test" ]; 82 enableACME = true; 83 }; 84 85 # Another which inherits the DNS-01 config 86 "${server}-dns.example.test" = vhostBaseData // { 87 serverAliases = [ "${server}-dns-alias.example.test" ]; 88 enableACME = true; 89 # Set acmeRoot to null instead of using the default of "/var/lib/acme/acme-challenge" 90 # webroot + dnsProvider are mutually exclusive. 91 acmeRoot = null; 92 }; 93 94 # One using the wildcard certificate 95 "${server}-wildcard.example.test" = vhostBaseData // { 96 serverAliases = [ "${server}-wildcard-alias.example.test" ]; 97 useACMEHost = "example.test"; 98 }; 99 }; 100 }; 101 102 # Used to determine if service reload was triggered 103 systemd.targets."test-renew-${server}" = { 104 wants = [ "acme-${server}-http.example.test.service" ]; 105 after = [ "acme-${server}-http.example.test.service" "${server}-config-reload.service" ]; 106 }; 107 } 108 specialConfig 109 extraConfig 110 ]; 111 in { 112 "${server}".configuration = { nodes, config, ... }: baseConfig { 113 inherit nodes config; 114 }; 115 116 # Test that server reloads when an alias is removed (and subsequently test removal works in acme) 117 "${server}-remove-alias".configuration = { nodes, config, ... }: baseConfig { 118 inherit nodes config; 119 specialConfig = { 120 # Remove an alias, but create a standalone vhost in its place for testing. 121 # This configuration results in certificate errors as useACMEHost does not imply 122 # append extraDomains, and thus we can validate the SAN is removed. 123 services."${server}" = { 124 virtualHosts."${server}-http.example.test".serverAliases = lib.mkForce []; 125 virtualHosts."${server}-http-alias.example.test" = vhostBaseData // { 126 useACMEHost = "${server}-http.example.test"; 127 }; 128 }; 129 }; 130 }; 131 132 # Test that the server reloads when only the acme configuration is changed. 133 "${server}-change-acme-conf".configuration = { nodes, config, ... }: baseConfig { 134 inherit nodes config; 135 specialConfig = { 136 security.acme.certs."${server}-http.example.test" = { 137 keyType = "ec384"; 138 # Also test that postRun is exec'd as root 139 postRun = "id | grep root"; 140 }; 141 }; 142 }; 143 }; 144 145in { 146 name = "acme"; 147 meta = { 148 maintainers = lib.teams.acme.members; 149 # Hard timeout in seconds. Average run time is about 7 minutes. 150 timeout = 1800; 151 }; 152 153 nodes = { 154 # The fake ACME server which will respond to client requests 155 acme = { nodes, ... }: { 156 imports = [ ./common/acme/server ]; 157 networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; 158 }; 159 160 # A fake DNS server which can be configured with records as desired 161 # Used to test DNS-01 challenge 162 dnsserver = { nodes, ... }: { 163 networking.firewall.allowedTCPPorts = [ 8055 53 ]; 164 networking.firewall.allowedUDPPorts = [ 53 ]; 165 systemd.services.pebble-challtestsrv = { 166 enable = true; 167 description = "Pebble ACME challenge test server"; 168 wantedBy = [ "network.target" ]; 169 serviceConfig = { 170 ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.networking.primaryIPAddress}'"; 171 # Required to bind on privileged ports. 172 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; 173 }; 174 }; 175 }; 176 177 # A web server which will be the node requesting certs 178 webserver = { nodes, config, ... }: { 179 imports = [ commonConfig ]; 180 networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; 181 networking.firewall.allowedTCPPorts = [ 80 443 ]; 182 183 # OpenSSL will be used for more thorough certificate validation 184 environment.systemPackages = [ pkgs.openssl ]; 185 186 # Set log level to info so that we can see when the service is reloaded 187 services.nginx.logError = "stderr info"; 188 189 specialisation = { 190 # Tests HTTP-01 verification using Lego's built-in web server 191 http01lego.configuration = simpleConfig; 192 193 renew.configuration = lib.mkMerge [ 194 simpleConfig 195 { 196 # Pebble provides 5 year long certs, 197 # needs to be higher than that to test renewal 198 security.acme.certs."http.example.test".validMinDays = 9999; 199 } 200 ]; 201 202 # Tests that account creds can be safely changed. 203 accountchange.configuration = lib.mkMerge [ 204 simpleConfig 205 { 206 security.acme.certs."http.example.test".email = "admin@example.test"; 207 } 208 ]; 209 210 # First derivation used to test general ACME features 211 general.configuration = { ... }: let 212 caDomain = nodes.acme.test-support.acme.caDomain; 213 email = config.security.acme.defaults.email; 214 # Exit 99 to make it easier to track if this is the reason a renew failed 215 accountCreateTester = '' 216 test -e accounts/${caDomain}/${email}/account.json || exit 99 217 ''; 218 in lib.mkMerge [ 219 webserverBasicConfig 220 { 221 # Used to test that account creation is collated into one service. 222 # These should not run until after acme-finished-a.example.test.target 223 systemd.services."b.example.test".preStart = accountCreateTester; 224 systemd.services."c.example.test".preStart = accountCreateTester; 225 226 services.nginx.virtualHosts."b.example.test" = vhostBase // { 227 enableACME = true; 228 }; 229 services.nginx.virtualHosts."c.example.test" = vhostBase // { 230 enableACME = true; 231 }; 232 } 233 ]; 234 235 # Test OCSP Stapling 236 ocsp-stapling.configuration = { ... }: lib.mkMerge [ 237 webserverBasicConfig 238 { 239 security.acme.certs."a.example.test".ocspMustStaple = true; 240 services.nginx.virtualHosts."a.example.test" = { 241 extraConfig = '' 242 ssl_stapling on; 243 ssl_stapling_verify on; 244 ''; 245 }; 246 } 247 ]; 248 249 # Validate service relationships by adding a slow start service to nginx' wants. 250 # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842 251 slow-startup.configuration = { ... }: lib.mkMerge [ 252 webserverBasicConfig 253 { 254 systemd.services.my-slow-service = { 255 wantedBy = [ "multi-user.target" "nginx.service" ]; 256 before = [ "nginx.service" ]; 257 preStart = "sleep 5"; 258 script = "${pkgs.python3}/bin/python -m http.server"; 259 }; 260 261 services.nginx.virtualHosts."slow.example.test" = { 262 forceSSL = true; 263 enableACME = true; 264 locations."/".proxyPass = "http://localhost:8000"; 265 }; 266 } 267 ]; 268 269 concurrency-limit.configuration = {pkgs, ...}: lib.mkMerge [ 270 webserverBasicConfig { 271 security.acme.maxConcurrentRenewals = 1; 272 273 services.nginx.virtualHosts = { 274 "f.example.test" = vhostBase // { 275 enableACME = true; 276 }; 277 "g.example.test" = vhostBase // { 278 enableACME = true; 279 }; 280 "h.example.test" = vhostBase // { 281 enableACME = true; 282 }; 283 }; 284 285 systemd.services = { 286 # check for mutual exclusion of starting renew services 287 "acme-f.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-f" '' 288 test "$(systemctl is-active acme-{g,h}.example.test.service | grep activating | wc -l)" -le 0 289 ''); 290 "acme-g.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-g" '' 291 test "$(systemctl is-active acme-{f,h}.example.test.service | grep activating | wc -l)" -le 0 292 ''); 293 "acme-h.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-h" '' 294 test "$(systemctl is-active acme-{g,f}.example.test.service | grep activating | wc -l)" -le 0 295 ''); 296 }; 297 } 298 ]; 299 300 # Test lego internal server (listenHTTP option) 301 # Also tests useRoot option 302 lego-server.configuration = { ... }: { 303 security.acme.useRoot = true; 304 security.acme.certs."lego.example.test" = { 305 listenHTTP = ":80"; 306 group = "nginx"; 307 }; 308 services.nginx.enable = true; 309 services.nginx.virtualHosts."lego.example.test" = { 310 useACMEHost = "lego.example.test"; 311 onlySSL = true; 312 }; 313 }; 314 315 # Test compatibility with Caddy 316 # It only supports useACMEHost, hence not using mkServerConfigs 317 } // (let 318 baseCaddyConfig = { nodes, config, ... }: { 319 security.acme = { 320 defaults = (dnsConfig nodes); 321 # One manual wildcard cert 322 certs."example.test" = { 323 domain = "*.example.test"; 324 }; 325 }; 326 327 users.users."${config.services.caddy.user}".extraGroups = ["acme"]; 328 329 services.caddy = { 330 enable = true; 331 virtualHosts."a.example.test" = { 332 useACMEHost = "example.test"; 333 extraConfig = '' 334 root * ${documentRoot} 335 ''; 336 }; 337 }; 338 }; 339 in { 340 caddy.configuration = baseCaddyConfig; 341 342 # Test that the server reloads when only the acme configuration is changed. 343 "caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [ 344 (baseCaddyConfig { 345 inherit nodes config; 346 }) 347 { 348 security.acme.certs."example.test" = { 349 keyType = "ec384"; 350 }; 351 } 352 ]; 353 354 # Test compatibility with Nginx 355 }) // (mkServerConfigs { 356 server = "nginx"; 357 group = "nginx"; 358 vhostBaseData = vhostBase; 359 }) 360 361 # Test compatibility with Apache HTTPD 362 // (mkServerConfigs { 363 server = "httpd"; 364 group = "wwwrun"; 365 vhostBaseData = vhostBaseHttpd; 366 extraConfig = { 367 services.httpd.adminAddr = config.security.acme.defaults.email; 368 }; 369 }); 370 }; 371 372 # The client will be used to curl the webserver to validate configuration 373 client = { nodes, ... }: { 374 imports = [ commonConfig ]; 375 networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; 376 377 # OpenSSL will be used for more thorough certificate validation 378 environment.systemPackages = [ pkgs.openssl ]; 379 }; 380 }; 381 382 testScript = { nodes, ... }: 383 let 384 caDomain = nodes.acme.test-support.acme.caDomain; 385 newServerSystem = nodes.webserver.config.system.build.toplevel; 386 switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test"; 387 in 388 # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true, 389 # this is because a oneshot goes from inactive => activating => inactive, and never 390 # reaches the active state. Targets do not have this issue. 391 '' 392 import time 393 394 395 TOTAL_RETRIES = 20 396 397 398 class BackoffTracker(object): 399 delay = 1 400 increment = 1 401 402 def handle_fail(self, retries, message) -> int: 403 assert retries < TOTAL_RETRIES, message 404 405 print(f"Retrying in {self.delay}s, {retries + 1}/{TOTAL_RETRIES}") 406 time.sleep(self.delay) 407 408 # Only increment after the first try 409 if retries == 0: 410 self.delay += self.increment 411 self.increment *= 2 412 413 return retries + 1 414 415 416 backoff = BackoffTracker() 417 418 419 def switch_to(node, name): 420 # On first switch, this will create a symlink to the current system so that we can 421 # quickly switch between derivations 422 root_specs = "/tmp/specialisation" 423 node.execute( 424 f"test -e {root_specs}" 425 f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}" 426 ) 427 428 switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration" 429 rc, _ = node.execute(f"test -e '{switcher_path}'") 430 if rc > 0: 431 switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration" 432 433 node.succeed( 434 f"{switcher_path} test" 435 ) 436 437 438 # Ensures the issuer of our cert matches the chain 439 # and matches the issuer we expect it to be. 440 # It's a good validation to ensure the cert.pem and fullchain.pem 441 # are not still selfsigned after verification 442 def check_issuer(node, cert_name, issuer): 443 for fname in ("cert.pem", "fullchain.pem"): 444 actual_issuer = node.succeed( 445 f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}" 446 ).partition("=")[2] 447 print(f"{fname} issuer: {actual_issuer}") 448 assert issuer.lower() in actual_issuer.lower() 449 450 451 # Ensure cert comes before chain in fullchain.pem 452 def check_fullchain(node, cert_name): 453 subject_data = node.succeed( 454 f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem" 455 " | openssl pkcs7 -print_certs -noout" 456 ) 457 for line in subject_data.lower().split("\n"): 458 if "subject" in line: 459 print(f"First subject in fullchain.pem: {line}") 460 assert cert_name.lower() in line 461 return 462 463 assert False 464 465 466 def check_connection(node, domain, retries=0): 467 result = node.succeed( 468 "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt" 469 f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1" 470 ) 471 472 for line in result.lower().split("\n"): 473 if "verification" in line and "error" in line: 474 retries = backoff.handle_fail(retries, f"Failed to connect to https://{domain}") 475 return check_connection(node, domain, retries) 476 477 478 def check_connection_key_bits(node, domain, bits, retries=0): 479 result = node.succeed( 480 "openssl s_client -CAfile /tmp/ca.crt" 481 f" -servername {domain} -connect {domain}:443 < /dev/null" 482 " | openssl x509 -noout -text | grep -i Public-Key" 483 ) 484 print("Key type:", result) 485 486 if bits not in result: 487 retries = backoff.handle_fail(retries, f"Did not find expected number of bits ({bits}) in key") 488 return check_connection_key_bits(node, domain, bits, retries) 489 490 491 def check_stapling(node, domain, retries=0): 492 # Pebble doesn't provide a full OCSP responder, so just check the URL 493 result = node.succeed( 494 "openssl s_client -CAfile /tmp/ca.crt" 495 f" -servername {domain} -connect {domain}:443 < /dev/null" 496 " | openssl x509 -noout -ocsp_uri" 497 ) 498 print("OCSP Responder URL:", result) 499 500 if "${caDomain}:4002" not in result.lower(): 501 retries = backoff.handle_fail(retries, "OCSP Stapling check failed") 502 return check_stapling(node, domain, retries) 503 504 505 def download_ca_certs(node, retries=0): 506 exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") 507 exit_code_2, _ = node.execute( 508 "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt" 509 ) 510 511 if exit_code + exit_code_2 > 0: 512 retries = backoff.handle_fail(retries, "Failed to connect to pebble to download root CA certs") 513 return download_ca_certs(node, retries) 514 515 516 start_all() 517 518 dnsserver.wait_for_unit("pebble-challtestsrv.service") 519 client.wait_for_unit("default.target") 520 521 client.succeed( 522 'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a' 523 ) 524 525 acme.wait_for_unit("network-online.target") 526 acme.wait_for_unit("pebble.service") 527 528 download_ca_certs(client) 529 530 # Perform http-01 w/ lego test first 531 with subtest("Can request certificate with Lego's built in web server"): 532 switch_to(webserver, "http01lego") 533 webserver.wait_for_unit("acme-finished-http.example.test.target") 534 check_fullchain(webserver, "http.example.test") 535 check_issuer(webserver, "http.example.test", "pebble") 536 537 # Perform renewal test 538 with subtest("Can renew certificates when they expire"): 539 hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 540 switch_to(webserver, "renew") 541 webserver.wait_for_unit("acme-finished-http.example.test.target") 542 check_fullchain(webserver, "http.example.test") 543 check_issuer(webserver, "http.example.test", "pebble") 544 hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 545 assert hash != hash_after 546 547 # Perform account change test 548 with subtest("Handles email change correctly"): 549 hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 550 switch_to(webserver, "accountchange") 551 webserver.wait_for_unit("acme-finished-http.example.test.target") 552 check_fullchain(webserver, "http.example.test") 553 check_issuer(webserver, "http.example.test", "pebble") 554 hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 555 # Has to do a full run to register account, which creates new certs. 556 assert hash != hash_after 557 558 # Perform general tests 559 switch_to(webserver, "general") 560 561 with subtest("Can request certificate with HTTP-01 challenge"): 562 webserver.wait_for_unit("acme-finished-a.example.test.target") 563 check_fullchain(webserver, "a.example.test") 564 check_issuer(webserver, "a.example.test", "pebble") 565 webserver.wait_for_unit("nginx.service") 566 check_connection(client, "a.example.test") 567 568 with subtest("Runs 1 cert for account creation before others"): 569 webserver.wait_for_unit("acme-finished-b.example.test.target") 570 webserver.wait_for_unit("acme-finished-c.example.test.target") 571 check_connection(client, "b.example.test") 572 check_connection(client, "c.example.test") 573 574 with subtest("Certificates and accounts have safe + valid permissions"): 575 # Nginx will set the group appropriately when enableACME is used 576 group = "nginx" 577 webserver.succeed( 578 f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 579 ) 580 webserver.succeed( 581 f"test $(stat -L -c '%a %U %G' /var/lib/acme/.lego/a.example.test/**/a.example.test* | tee /dev/stderr | grep '600 acme {group}' | wc -l) -eq 4" 582 ) 583 webserver.succeed( 584 f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1" 585 ) 586 webserver.succeed( 587 f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c '%a %U %G' {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0" 588 ) 589 590 # Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal 591 with subtest("Can generate valid selfsigned certs"): 592 webserver.succeed("systemctl clean acme-a.example.test.service --what=state") 593 webserver.succeed("systemctl start acme-selfsigned-a.example.test.service") 594 check_fullchain(webserver, "a.example.test") 595 check_issuer(webserver, "a.example.test", "minica") 596 # Check selfsigned permissions 597 webserver.succeed( 598 f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 599 ) 600 # Will succeed if nginx can load the certs 601 webserver.succeed("systemctl start nginx-config-reload.service") 602 603 with subtest("Correctly implements OCSP stapling"): 604 switch_to(webserver, "ocsp-stapling") 605 webserver.wait_for_unit("acme-finished-a.example.test.target") 606 check_stapling(client, "a.example.test") 607 608 with subtest("Can request certificate with HTTP-01 using lego's internal web server"): 609 switch_to(webserver, "lego-server") 610 webserver.wait_for_unit("acme-finished-lego.example.test.target") 611 webserver.wait_for_unit("nginx.service") 612 webserver.succeed("echo HENLO && systemctl cat nginx.service") 613 webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"") 614 check_connection(client, "a.example.test") 615 check_connection(client, "lego.example.test") 616 617 with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"): 618 webserver.execute("systemctl stop nginx") 619 switch_to(webserver, "slow-startup") 620 webserver.wait_for_unit("acme-finished-slow.example.test.target") 621 check_issuer(webserver, "slow.example.test", "pebble") 622 webserver.wait_for_unit("nginx.service") 623 check_connection(client, "slow.example.test") 624 625 with subtest("Can limit concurrency of running renewals"): 626 switch_to(webserver, "concurrency-limit") 627 webserver.wait_for_unit("acme-finished-f.example.test.target") 628 webserver.wait_for_unit("acme-finished-g.example.test.target") 629 webserver.wait_for_unit("acme-finished-h.example.test.target") 630 check_connection(client, "f.example.test") 631 check_connection(client, "g.example.test") 632 check_connection(client, "h.example.test") 633 634 with subtest("Works with caddy"): 635 switch_to(webserver, "caddy") 636 webserver.wait_for_unit("acme-finished-example.test.target") 637 webserver.wait_for_unit("caddy.service") 638 # FIXME reloading caddy is not sufficient to load new certs. 639 # Restart it manually until this is fixed. 640 webserver.succeed("systemctl restart caddy.service") 641 check_connection(client, "a.example.test") 642 643 with subtest("security.acme changes reflect on caddy"): 644 switch_to(webserver, "caddy-change-acme-conf") 645 webserver.wait_for_unit("acme-finished-example.test.target") 646 webserver.wait_for_unit("caddy.service") 647 # FIXME reloading caddy is not sufficient to load new certs. 648 # Restart it manually until this is fixed. 649 webserver.succeed("systemctl restart caddy.service") 650 check_connection_key_bits(client, "a.example.test", "384") 651 652 domains = ["http", "dns", "wildcard"] 653 for server, logsrc in [ 654 ("nginx", "journalctl -n 30 -u nginx.service"), 655 ("httpd", "tail -n 30 /var/log/httpd/*.log"), 656 ]: 657 wait_for_server = lambda: webserver.wait_for_unit(f"{server}.service") 658 with subtest(f"Works with {server}"): 659 try: 660 switch_to(webserver, server) 661 # Skip wildcard domain for this check ([:-1]) 662 for domain in domains[:-1]: 663 webserver.wait_for_unit( 664 f"acme-finished-{server}-{domain}.example.test.target" 665 ) 666 except Exception as err: 667 _, output = webserver.execute( 668 f"{logsrc} && ls -al /var/lib/acme/acme-challenge" 669 ) 670 print(output) 671 raise err 672 673 wait_for_server() 674 675 for domain in domains[:-1]: 676 check_issuer(webserver, f"{server}-{domain}.example.test", "pebble") 677 for domain in domains: 678 check_connection(client, f"{server}-{domain}.example.test") 679 check_connection(client, f"{server}-{domain}-alias.example.test") 680 681 test_domain = f"{server}-{domains[0]}.example.test" 682 683 with subtest(f"Can reload {server} when timer triggers renewal"): 684 # Switch to selfsigned first 685 webserver.succeed(f"systemctl clean acme-{test_domain}.service --what=state") 686 webserver.succeed(f"systemctl start acme-selfsigned-{test_domain}.service") 687 check_issuer(webserver, test_domain, "minica") 688 webserver.succeed(f"systemctl start {server}-config-reload.service") 689 webserver.succeed(f"systemctl start test-renew-{server}.target") 690 check_issuer(webserver, test_domain, "pebble") 691 check_connection(client, test_domain) 692 693 with subtest("Can remove an alias from a domain + cert is updated"): 694 test_alias = f"{server}-{domains[0]}-alias.example.test" 695 switch_to(webserver, f"{server}-remove-alias") 696 webserver.wait_for_unit(f"acme-finished-{test_domain}.target") 697 wait_for_server() 698 check_connection(client, test_domain) 699 rc, _s = client.execute( 700 f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443" 701 " </dev/null 2>/dev/null | openssl x509 -noout -text" 702 f" | grep DNS: | grep {test_alias}" 703 ) 704 assert rc > 0, "Removed extraDomainName was not removed from the cert" 705 706 with subtest("security.acme changes reflect on web server"): 707 # Switch back to normal server config first, reset everything. 708 switch_to(webserver, server) 709 wait_for_server() 710 switch_to(webserver, f"{server}-change-acme-conf") 711 webserver.wait_for_unit(f"acme-finished-{test_domain}.target") 712 wait_for_server() 713 check_connection_key_bits(client, test_domain, "384") 714 ''; 715}