at 23.11-pre 27 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 credentialsFile = 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 # Test lego internal server (listenHTTP option) 270 # Also tests useRoot option 271 lego-server.configuration = { ... }: { 272 security.acme.useRoot = true; 273 security.acme.certs."lego.example.test" = { 274 listenHTTP = ":80"; 275 group = "nginx"; 276 }; 277 services.nginx.enable = true; 278 services.nginx.virtualHosts."lego.example.test" = { 279 useACMEHost = "lego.example.test"; 280 onlySSL = true; 281 }; 282 }; 283 284 # Test compatibility with Caddy 285 # It only supports useACMEHost, hence not using mkServerConfigs 286 } // (let 287 baseCaddyConfig = { nodes, config, ... }: { 288 security.acme = { 289 defaults = (dnsConfig nodes); 290 # One manual wildcard cert 291 certs."example.test" = { 292 domain = "*.example.test"; 293 }; 294 }; 295 296 users.users."${config.services.caddy.user}".extraGroups = ["acme"]; 297 298 services.caddy = { 299 enable = true; 300 virtualHosts."a.exmaple.test" = { 301 useACMEHost = "example.test"; 302 extraConfig = '' 303 root * ${documentRoot} 304 ''; 305 }; 306 }; 307 }; 308 in { 309 caddy.configuration = baseCaddyConfig; 310 311 # Test that the server reloads when only the acme configuration is changed. 312 "caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [ 313 (baseCaddyConfig { 314 inherit nodes config; 315 }) 316 { 317 security.acme.certs."example.test" = { 318 keyType = "ec384"; 319 }; 320 } 321 ]; 322 323 # Test compatibility with Nginx 324 }) // (mkServerConfigs { 325 server = "nginx"; 326 group = "nginx"; 327 vhostBaseData = vhostBase; 328 }) 329 330 # Test compatibility with Apache HTTPD 331 // (mkServerConfigs { 332 server = "httpd"; 333 group = "wwwrun"; 334 vhostBaseData = vhostBaseHttpd; 335 extraConfig = { 336 services.httpd.adminAddr = config.security.acme.defaults.email; 337 }; 338 }); 339 }; 340 341 # The client will be used to curl the webserver to validate configuration 342 client = { nodes, ... }: { 343 imports = [ commonConfig ]; 344 networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; 345 346 # OpenSSL will be used for more thorough certificate validation 347 environment.systemPackages = [ pkgs.openssl ]; 348 }; 349 }; 350 351 testScript = { nodes, ... }: 352 let 353 caDomain = nodes.acme.test-support.acme.caDomain; 354 newServerSystem = nodes.webserver.config.system.build.toplevel; 355 switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test"; 356 in 357 # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true, 358 # this is because a oneshot goes from inactive => activating => inactive, and never 359 # reaches the active state. Targets do not have this issue. 360 '' 361 import time 362 363 364 TOTAL_RETRIES = 20 365 366 367 class BackoffTracker(object): 368 delay = 1 369 increment = 1 370 371 def handle_fail(self, retries, message) -> int: 372 assert retries < TOTAL_RETRIES, message 373 374 print(f"Retrying in {self.delay}s, {retries + 1}/{TOTAL_RETRIES}") 375 time.sleep(self.delay) 376 377 # Only increment after the first try 378 if retries == 0: 379 self.delay += self.increment 380 self.increment *= 2 381 382 return retries + 1 383 384 385 backoff = BackoffTracker() 386 387 388 def switch_to(node, name): 389 # On first switch, this will create a symlink to the current system so that we can 390 # quickly switch between derivations 391 root_specs = "/tmp/specialisation" 392 node.execute( 393 f"test -e {root_specs}" 394 f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}" 395 ) 396 397 switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration" 398 rc, _ = node.execute(f"test -e '{switcher_path}'") 399 if rc > 0: 400 switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration" 401 402 node.succeed( 403 f"{switcher_path} test" 404 ) 405 406 407 # Ensures the issuer of our cert matches the chain 408 # and matches the issuer we expect it to be. 409 # It's a good validation to ensure the cert.pem and fullchain.pem 410 # are not still selfsigned after verification 411 def check_issuer(node, cert_name, issuer): 412 for fname in ("cert.pem", "fullchain.pem"): 413 actual_issuer = node.succeed( 414 f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}" 415 ).partition("=")[2] 416 print(f"{fname} issuer: {actual_issuer}") 417 assert issuer.lower() in actual_issuer.lower() 418 419 420 # Ensure cert comes before chain in fullchain.pem 421 def check_fullchain(node, cert_name): 422 subject_data = node.succeed( 423 f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem" 424 " | openssl pkcs7 -print_certs -noout" 425 ) 426 for line in subject_data.lower().split("\n"): 427 if "subject" in line: 428 print(f"First subject in fullchain.pem: {line}") 429 assert cert_name.lower() in line 430 return 431 432 assert False 433 434 435 def check_connection(node, domain, retries=0): 436 result = node.succeed( 437 "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt" 438 f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1" 439 ) 440 441 for line in result.lower().split("\n"): 442 if "verification" in line and "error" in line: 443 retries = backoff.handle_fail(retries, f"Failed to connect to https://{domain}") 444 return check_connection(node, domain, retries) 445 446 447 def check_connection_key_bits(node, domain, bits, retries=0): 448 result = node.succeed( 449 "openssl s_client -CAfile /tmp/ca.crt" 450 f" -servername {domain} -connect {domain}:443 < /dev/null" 451 " | openssl x509 -noout -text | grep -i Public-Key" 452 ) 453 print("Key type:", result) 454 455 if bits not in result: 456 retries = backoff.handle_fail(retries, f"Did not find expected number of bits ({bits}) in key") 457 return check_connection_key_bits(node, domain, bits, retries) 458 459 460 def check_stapling(node, domain, retries=0): 461 # Pebble doesn't provide a full OCSP responder, so just check the URL 462 result = node.succeed( 463 "openssl s_client -CAfile /tmp/ca.crt" 464 f" -servername {domain} -connect {domain}:443 < /dev/null" 465 " | openssl x509 -noout -ocsp_uri" 466 ) 467 print("OCSP Responder URL:", result) 468 469 if "${caDomain}:4002" not in result.lower(): 470 retries = backoff.handle_fail(retries, "OCSP Stapling check failed") 471 return check_stapling(node, domain, retries) 472 473 474 def download_ca_certs(node, retries=0): 475 exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") 476 exit_code_2, _ = node.execute( 477 "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt" 478 ) 479 480 if exit_code + exit_code_2 > 0: 481 retries = backoff.handle_fail(retries, "Failed to connect to pebble to download root CA certs") 482 return download_ca_certs(node, retries) 483 484 485 start_all() 486 487 dnsserver.wait_for_unit("pebble-challtestsrv.service") 488 client.wait_for_unit("default.target") 489 490 client.succeed( 491 'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a' 492 ) 493 494 acme.wait_for_unit("network-online.target") 495 acme.wait_for_unit("pebble.service") 496 497 download_ca_certs(client) 498 499 # Perform http-01 w/ lego test first 500 with subtest("Can request certificate with Lego's built in web server"): 501 switch_to(webserver, "http01lego") 502 webserver.wait_for_unit("acme-finished-http.example.test.target") 503 check_fullchain(webserver, "http.example.test") 504 check_issuer(webserver, "http.example.test", "pebble") 505 506 # Perform renewal test 507 with subtest("Can renew certificates when they expire"): 508 hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 509 switch_to(webserver, "renew") 510 webserver.wait_for_unit("acme-finished-http.example.test.target") 511 check_fullchain(webserver, "http.example.test") 512 check_issuer(webserver, "http.example.test", "pebble") 513 hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 514 assert hash != hash_after 515 516 # Perform account change test 517 with subtest("Handles email change correctly"): 518 hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 519 switch_to(webserver, "accountchange") 520 webserver.wait_for_unit("acme-finished-http.example.test.target") 521 check_fullchain(webserver, "http.example.test") 522 check_issuer(webserver, "http.example.test", "pebble") 523 hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") 524 # Has to do a full run to register account, which creates new certs. 525 assert hash != hash_after 526 527 # Perform general tests 528 switch_to(webserver, "general") 529 530 with subtest("Can request certificate with HTTP-01 challenge"): 531 webserver.wait_for_unit("acme-finished-a.example.test.target") 532 check_fullchain(webserver, "a.example.test") 533 check_issuer(webserver, "a.example.test", "pebble") 534 webserver.wait_for_unit("nginx.service") 535 check_connection(client, "a.example.test") 536 537 with subtest("Runs 1 cert for account creation before others"): 538 webserver.wait_for_unit("acme-finished-b.example.test.target") 539 webserver.wait_for_unit("acme-finished-c.example.test.target") 540 check_connection(client, "b.example.test") 541 check_connection(client, "c.example.test") 542 543 with subtest("Certificates and accounts have safe + valid permissions"): 544 # Nginx will set the group appropriately when enableACME is used 545 group = "nginx" 546 webserver.succeed( 547 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" 548 ) 549 webserver.succeed( 550 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" 551 ) 552 webserver.succeed( 553 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" 554 ) 555 webserver.succeed( 556 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" 557 ) 558 559 # Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal 560 with subtest("Can generate valid selfsigned certs"): 561 webserver.succeed("systemctl clean acme-a.example.test.service --what=state") 562 webserver.succeed("systemctl start acme-selfsigned-a.example.test.service") 563 check_fullchain(webserver, "a.example.test") 564 check_issuer(webserver, "a.example.test", "minica") 565 # Check selfsigned permissions 566 webserver.succeed( 567 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" 568 ) 569 # Will succeed if nginx can load the certs 570 webserver.succeed("systemctl start nginx-config-reload.service") 571 572 with subtest("Correctly implements OCSP stapling"): 573 switch_to(webserver, "ocsp-stapling") 574 webserver.wait_for_unit("acme-finished-a.example.test.target") 575 check_stapling(client, "a.example.test") 576 577 with subtest("Can request certificate with HTTP-01 using lego's internal web server"): 578 switch_to(webserver, "lego-server") 579 webserver.wait_for_unit("acme-finished-lego.example.test.target") 580 webserver.wait_for_unit("nginx.service") 581 webserver.succeed("echo HENLO && systemctl cat nginx.service") 582 webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"") 583 check_connection(client, "a.example.test") 584 check_connection(client, "lego.example.test") 585 586 with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"): 587 webserver.execute("systemctl stop nginx") 588 switch_to(webserver, "slow-startup") 589 webserver.wait_for_unit("acme-finished-slow.example.test.target") 590 check_issuer(webserver, "slow.example.test", "pebble") 591 webserver.wait_for_unit("nginx.service") 592 check_connection(client, "slow.example.test") 593 594 with subtest("Works with caddy"): 595 switch_to(webserver, "caddy") 596 webserver.wait_for_unit("acme-finished-example.test.target") 597 webserver.wait_for_unit("caddy.service") 598 # FIXME reloading caddy is not sufficient to load new certs. 599 # Restart it manually until this is fixed. 600 webserver.succeed("systemctl restart caddy.service") 601 check_connection(client, "a.example.test") 602 603 with subtest("security.acme changes reflect on caddy"): 604 switch_to(webserver, "caddy-change-acme-conf") 605 webserver.wait_for_unit("acme-finished-example.test.target") 606 webserver.wait_for_unit("caddy.service") 607 # FIXME reloading caddy is not sufficient to load new certs. 608 # Restart it manually until this is fixed. 609 webserver.succeed("systemctl restart caddy.service") 610 check_connection_key_bits(client, "a.example.test", "384") 611 612 domains = ["http", "dns", "wildcard"] 613 for server, logsrc in [ 614 ("nginx", "journalctl -n 30 -u nginx.service"), 615 ("httpd", "tail -n 30 /var/log/httpd/*.log"), 616 ]: 617 wait_for_server = lambda: webserver.wait_for_unit(f"{server}.service") 618 with subtest(f"Works with {server}"): 619 try: 620 switch_to(webserver, server) 621 # Skip wildcard domain for this check ([:-1]) 622 for domain in domains[:-1]: 623 webserver.wait_for_unit( 624 f"acme-finished-{server}-{domain}.example.test.target" 625 ) 626 except Exception as err: 627 _, output = webserver.execute( 628 f"{logsrc} && ls -al /var/lib/acme/acme-challenge" 629 ) 630 print(output) 631 raise err 632 633 wait_for_server() 634 635 for domain in domains[:-1]: 636 check_issuer(webserver, f"{server}-{domain}.example.test", "pebble") 637 for domain in domains: 638 check_connection(client, f"{server}-{domain}.example.test") 639 check_connection(client, f"{server}-{domain}-alias.example.test") 640 641 test_domain = f"{server}-{domains[0]}.example.test" 642 643 with subtest(f"Can reload {server} when timer triggers renewal"): 644 # Switch to selfsigned first 645 webserver.succeed(f"systemctl clean acme-{test_domain}.service --what=state") 646 webserver.succeed(f"systemctl start acme-selfsigned-{test_domain}.service") 647 check_issuer(webserver, test_domain, "minica") 648 webserver.succeed(f"systemctl start {server}-config-reload.service") 649 webserver.succeed(f"systemctl start test-renew-{server}.target") 650 check_issuer(webserver, test_domain, "pebble") 651 check_connection(client, test_domain) 652 653 with subtest("Can remove an alias from a domain + cert is updated"): 654 test_alias = f"{server}-{domains[0]}-alias.example.test" 655 switch_to(webserver, f"{server}-remove-alias") 656 webserver.wait_for_unit(f"acme-finished-{test_domain}.target") 657 wait_for_server() 658 check_connection(client, test_domain) 659 rc, _s = client.execute( 660 f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443" 661 " </dev/null 2>/dev/null | openssl x509 -noout -text" 662 f" | grep DNS: | grep {test_alias}" 663 ) 664 assert rc > 0, "Removed extraDomainName was not removed from the cert" 665 666 with subtest("security.acme changes reflect on web server"): 667 # Switch back to normal server config first, reset everything. 668 switch_to(webserver, server) 669 wait_for_server() 670 switch_to(webserver, f"{server}-change-acme-conf") 671 webserver.wait_for_unit(f"acme-finished-{test_domain}.target") 672 wait_for_server() 673 check_connection_key_bits(client, test_domain, "384") 674 ''; 675}