| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  | let | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |   commonConfig = ./common/acme/client; | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |   dnsServerIP = nodes: nodes.dnsserver.config.networking.primaryIPAddress; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   dnsScript = {pkgs, nodes}: let | 
					
						
							|  |  |  |     dnsAddress = dnsServerIP nodes; | 
					
						
							|  |  |  |   in pkgs.writeShellScript "dns-hook.sh" ''
 | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     set -euo pipefail | 
					
						
							|  |  |  |     echo '[INFO]' "[$2]" 'dns-hook.sh' $* | 
					
						
							|  |  |  |     if [ "$1" = "present" ]; then | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     fi | 
					
						
							|  |  |  |   '';
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |   documentRoot = pkgs: pkgs.runCommand "docroot" {} ''
 | 
					
						
							|  |  |  |     mkdir -p "$out" | 
					
						
							|  |  |  |     echo hello world > "$out/index.html" | 
					
						
							|  |  |  |   '';
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   vhostBase = pkgs: { | 
					
						
							|  |  |  |     forceSSL = true; | 
					
						
							|  |  |  |     locations."/".root = documentRoot pkgs; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 01:36:29 +01:00
										 |  |  | in import ./make-test-python.nix ({ lib, ... }: { | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |   name = "acme"; | 
					
						
							| 
									
										
										
										
											2020-04-20 01:36:29 +01:00
										 |  |  |   meta.maintainers = lib.teams.acme.members; | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |   nodes = { | 
					
						
							|  |  |  |     # The fake ACME server which will respond to client requests | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |     acme = { nodes, lib, ... }: { | 
					
						
							|  |  |  |       imports = [ ./common/acme/server ]; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |     # A fake DNS server which can be configured with records as desired | 
					
						
							|  |  |  |     # Used to test DNS-01 challenge | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     dnsserver = { nodes, pkgs, ... }: { | 
					
						
							|  |  |  |       networking.firewall.allowedTCPPorts = [ 8055 53 ]; | 
					
						
							|  |  |  |       networking.firewall.allowedUDPPorts = [ 53 ]; | 
					
						
							|  |  |  |       systemd.services.pebble-challtestsrv = { | 
					
						
							|  |  |  |         enable = true; | 
					
						
							|  |  |  |         description = "Pebble ACME challenge test server"; | 
					
						
							|  |  |  |         wantedBy = [ "network.target" ]; | 
					
						
							|  |  |  |         serviceConfig = { | 
					
						
							|  |  |  |           ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.config.networking.primaryIPAddress}'"; | 
					
						
							|  |  |  |           # Required to bind on privileged ports. | 
					
						
							| 
									
										
										
										
											2020-03-23 17:58:36 +00:00
										 |  |  |           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |         }; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |     # A web server which will be the node requesting certs | 
					
						
							|  |  |  |     webserver = { pkgs, nodes, lib, config, ... }: { | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |       imports = [ commonConfig ]; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |       networking.firewall.allowedTCPPorts = [ 80 443 ]; | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # OpenSSL will be used for more thorough certificate validation | 
					
						
							|  |  |  |       environment.systemPackages = [ pkgs.openssl ]; | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-30 18:38:30 +01:00
										 |  |  |       # Set log level to info so that we can see when the service is reloaded | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       services.nginx.enable = true; | 
					
						
							| 
									
										
										
										
											2020-08-30 18:38:30 +01:00
										 |  |  |       services.nginx.logError = "stderr info"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # First tests configure a basic cert and run a bunch of openssl checks | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       services.nginx.virtualHosts."a.example.test" = (vhostBase pkgs) // { | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |         enableACME = true; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # Used to determine if service reload was triggered | 
					
						
							|  |  |  |       systemd.targets.test-renew-nginx = { | 
					
						
							|  |  |  |         wants = [ "acme-a.example.test.service" ]; | 
					
						
							|  |  |  |         after = [ "acme-a.example.test.service" "nginx-config-reload.service" ]; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Cert config changes will not cause the nginx configuration to change. | 
					
						
							|  |  |  |       # This tests that the reload service is correctly triggered. | 
					
						
							| 
									
										
										
										
											2020-09-04 18:48:47 +01:00
										 |  |  |       # It also tests that postRun is exec'd as root | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       specialisation.cert-change.configuration = { pkgs, ... }: { | 
					
						
							|  |  |  |         security.acme.certs."a.example.test".keyType = "ec384"; | 
					
						
							| 
									
										
										
										
											2020-09-04 18:48:47 +01:00
										 |  |  |         security.acme.certs."a.example.test".postRun = ''
 | 
					
						
							|  |  |  |           set -euo pipefail | 
					
						
							|  |  |  |           touch test | 
					
						
							|  |  |  |           chown root:root test | 
					
						
							|  |  |  |           echo testing > test | 
					
						
							|  |  |  |         '';
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-10-26 00:45:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # Now adding an alias to ensure that the certs are updated | 
					
						
							|  |  |  |       specialisation.nginx-aliases.configuration = { pkgs, ... }: { | 
					
						
							|  |  |  |         services.nginx.virtualHosts."a.example.test" = { | 
					
						
							|  |  |  |           serverAliases = [ "b.example.test" ]; | 
					
						
							| 
									
										
										
										
											2020-04-14 14:34:46 +02:00
										 |  |  |         }; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |       # Test OCSP Stapling | 
					
						
							|  |  |  |       specialisation.ocsp-stapling.configuration = { pkgs, ... }: { | 
					
						
							|  |  |  |         security.acme.certs."a.example.test" = { | 
					
						
							|  |  |  |           ocspMustStaple = true; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         services.nginx.virtualHosts."a.example.com" = { | 
					
						
							|  |  |  |           extraConfig = ''
 | 
					
						
							|  |  |  |             ssl_stapling on; | 
					
						
							|  |  |  |             ssl_stapling_verify on; | 
					
						
							|  |  |  |           '';
 | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # Test using Apache HTTPD | 
					
						
							|  |  |  |       specialisation.httpd-aliases.configuration = { pkgs, config, lib, ... }: { | 
					
						
							|  |  |  |         services.nginx.enable = lib.mkForce false; | 
					
						
							|  |  |  |         services.httpd.enable = true; | 
					
						
							|  |  |  |         services.httpd.adminAddr = config.security.acme.email; | 
					
						
							|  |  |  |         services.httpd.virtualHosts."c.example.test" = { | 
					
						
							|  |  |  |           serverAliases = [ "d.example.test" ]; | 
					
						
							| 
									
										
										
										
											2020-03-05 17:07:20 -05:00
										 |  |  |           forceSSL = true; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           enableACME = true; | 
					
						
							|  |  |  |           documentRoot = documentRoot pkgs; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Used to determine if service reload was triggered | 
					
						
							|  |  |  |         systemd.targets.test-renew-httpd = { | 
					
						
							|  |  |  |           wants = [ "acme-c.example.test.service" ]; | 
					
						
							|  |  |  |           after = [ "acme-c.example.test.service" "httpd-config-reload.service" ]; | 
					
						
							| 
									
										
										
										
											2020-03-05 17:07:20 -05:00
										 |  |  |         }; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2020-04-14 20:04:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # Validation via DNS-01 challenge | 
					
						
							|  |  |  |       specialisation.dns-01.configuration = { pkgs, config, nodes, ... }: { | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |         security.acme.certs."example.test" = { | 
					
						
							|  |  |  |           domain = "*.example.test"; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           group = config.services.nginx.group; | 
					
						
							| 
									
										
										
										
											2020-03-05 17:07:20 -05:00
										 |  |  |           dnsProvider = "exec"; | 
					
						
							|  |  |  |           dnsPropagationCheck = false; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           credentialsFile = pkgs.writeText "wildcard.env" ''
 | 
					
						
							|  |  |  |             EXEC_PATH=${dnsScript { inherit pkgs nodes; }} | 
					
						
							| 
									
										
										
										
											2020-03-05 17:07:20 -05:00
										 |  |  |           '';
 | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         services.nginx.virtualHosts."dns.example.test" = (vhostBase pkgs) // { | 
					
						
							|  |  |  |           useACMEHost = "example.test"; | 
					
						
							| 
									
										
										
										
											2020-03-05 17:07:20 -05:00
										 |  |  |         }; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2020-04-14 20:04:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       # Validate service relationships by adding a slow start service to nginx' wants. | 
					
						
							|  |  |  |       # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842 | 
					
						
							|  |  |  |       specialisation.slow-startup.configuration = { pkgs, config, nodes, lib, ... }: { | 
					
						
							| 
									
										
										
										
											2020-04-14 20:04:44 +02:00
										 |  |  |         systemd.services.my-slow-service = { | 
					
						
							|  |  |  |           wantedBy = [ "multi-user.target" "nginx.service" ]; | 
					
						
							|  |  |  |           before = [ "nginx.service" ]; | 
					
						
							|  |  |  |           preStart = "sleep 5"; | 
					
						
							|  |  |  |           script = "${pkgs.python3}/bin/python -m http.server"; | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         services.nginx.virtualHosts."slow.example.com" = { | 
					
						
							| 
									
										
										
										
											2020-04-14 20:04:44 +02:00
										 |  |  |           forceSSL = true; | 
					
						
							|  |  |  |           enableACME = true; | 
					
						
							|  |  |  |           locations."/".proxyPass = "http://localhost:8000"; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |     # The client will be used to curl the webserver to validate configuration | 
					
						
							|  |  |  |     client = {nodes, lib, pkgs, ...}: { | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       imports = [ commonConfig ]; | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # OpenSSL will be used for more thorough certificate validation | 
					
						
							|  |  |  |       environment.systemPackages = [ pkgs.openssl ]; | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-07-27 13:24:17 +02:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-18 19:13:04 +02:00
										 |  |  |   testScript = {nodes, ...}: | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  |     let | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |       caDomain = nodes.acme.config.test-support.acme.caDomain; | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       newServerSystem = nodes.webserver.config.system.build.toplevel; | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  |       switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test"; | 
					
						
							|  |  |  |     in | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  |     # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true, | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  |     # this is because a oneshot goes from inactive => activating => inactive, and never | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |     # reaches the active state. Targets do not have this issue. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  |     ''
 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |       import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       has_switched = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def switch_to(node, name): | 
					
						
							|  |  |  |           global has_switched | 
					
						
							|  |  |  |           if has_switched: | 
					
						
							|  |  |  |               node.succeed( | 
					
						
							|  |  |  |                   "${switchToNewServer}" | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |           has_switched = True | 
					
						
							|  |  |  |           node.succeed( | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |               f"/run/current-system/specialisation/{name}/bin/switch-to-configuration test" | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Ensures the issuer of our cert matches the chain | 
					
						
							|  |  |  |       # and matches the issuer we expect it to be. | 
					
						
							|  |  |  |       # It's a good validation to ensure the cert.pem and fullchain.pem | 
					
						
							|  |  |  |       # are not still selfsigned afer verification | 
					
						
							|  |  |  |       def check_issuer(node, cert_name, issuer): | 
					
						
							|  |  |  |           for fname in ("cert.pem", "fullchain.pem"): | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |               actual_issuer = node.succeed( | 
					
						
							|  |  |  |                   f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}" | 
					
						
							|  |  |  |               ).partition("=")[2] | 
					
						
							|  |  |  |               print(f"{fname} issuer: {actual_issuer}") | 
					
						
							|  |  |  |               assert issuer.lower() in actual_issuer.lower() | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Ensure cert comes before chain in fullchain.pem | 
					
						
							|  |  |  |       def check_fullchain(node, cert_name): | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |           subject_data = node.succeed( | 
					
						
							|  |  |  |               f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem" | 
					
						
							|  |  |  |               " | openssl pkcs7 -print_certs -noout" | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           ) | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |           for line in subject_data.lower().split("\n"): | 
					
						
							|  |  |  |               if "subject" in line: | 
					
						
							|  |  |  |                   print(f"First subject in fullchain.pem: ", line) | 
					
						
							|  |  |  |                   assert cert_name.lower() in line | 
					
						
							|  |  |  |                   return | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |           assert False | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       def check_connection(node, domain, retries=3): | 
					
						
							| 
									
										
										
										
											2020-09-04 20:28:46 +01:00
										 |  |  |           assert retries >= 0 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |           result = node.succeed( | 
					
						
							|  |  |  |               "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt" | 
					
						
							|  |  |  |               f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1" | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           for line in result.lower().split("\n"): | 
					
						
							|  |  |  |               if "verification" in line and "error" in line: | 
					
						
							|  |  |  |                   time.sleep(1) | 
					
						
							|  |  |  |                   return check_connection(node, domain, retries - 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def check_connection_key_bits(node, domain, bits, retries=3): | 
					
						
							| 
									
										
										
										
											2020-09-04 20:28:46 +01:00
										 |  |  |           assert retries >= 0 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |           result = node.succeed( | 
					
						
							|  |  |  |               "openssl s_client -CAfile /tmp/ca.crt" | 
					
						
							|  |  |  |               f" -servername {domain} -connect {domain}:443 < /dev/null" | 
					
						
							|  |  |  |               " | openssl x509 -noout -text | grep -i Public-Key" | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           ) | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |           print("Key type:", result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if bits not in result: | 
					
						
							|  |  |  |               time.sleep(1) | 
					
						
							|  |  |  |               return check_connection_key_bits(node, domain, bits, retries - 1) | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |       def check_stapling(node, domain, retries=3): | 
					
						
							|  |  |  |           assert retries >= 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Pebble doesn't provide a full OCSP responder, so just check the URL | 
					
						
							|  |  |  |           result = node.succeed( | 
					
						
							|  |  |  |               "openssl s_client -CAfile /tmp/ca.crt" | 
					
						
							|  |  |  |               f" -servername {domain} -connect {domain}:443 < /dev/null" | 
					
						
							|  |  |  |               " | openssl x509 -noout -ocsp_uri" | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |           print("OCSP Responder URL:", result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if "${caDomain}:4002" not in result.lower(): | 
					
						
							|  |  |  |               time.sleep(1) | 
					
						
							|  |  |  |               return check_stapling(node, domain, retries - 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  |       client.start() | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       dnsserver.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       dnsserver.wait_for_unit("pebble-challtestsrv.service") | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       client.wait_for_unit("default.target") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       client.succeed( | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |           'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.config.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a' | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |       acme.start() | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       webserver.start() | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |       acme.wait_for_unit("default.target") | 
					
						
							|  |  |  |       acme.wait_for_unit("pebble.service") | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |       client.succeed("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") | 
					
						
							|  |  |  |       client.succeed("curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt") | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       with subtest("Can request certificate with HTTPS-01 challenge"): | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |           webserver.wait_for_unit("acme-finished-a.example.test.target") | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           check_fullchain(webserver, "a.example.test") | 
					
						
							|  |  |  |           check_issuer(webserver, "a.example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "a.example.test") | 
					
						
							| 
									
										
										
										
											2019-10-27 13:53:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       with subtest("Can generate valid selfsigned certs"): | 
					
						
							|  |  |  |           webserver.succeed("systemctl clean acme-a.example.test.service --what=state") | 
					
						
							|  |  |  |           webserver.succeed("systemctl start acme-selfsigned-a.example.test.service") | 
					
						
							|  |  |  |           check_fullchain(webserver, "a.example.test") | 
					
						
							|  |  |  |           check_issuer(webserver, "a.example.test", "minica") | 
					
						
							|  |  |  |           # Will succeed if nginx can load the certs | 
					
						
							|  |  |  |           webserver.succeed("systemctl start nginx-config-reload.service") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       with subtest("Can reload nginx when timer triggers renewal"): | 
					
						
							|  |  |  |           webserver.succeed("systemctl start test-renew-nginx.target") | 
					
						
							|  |  |  |           check_issuer(webserver, "a.example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "a.example.test") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       with subtest("Can reload web server when cert configuration changes"): | 
					
						
							|  |  |  |           switch_to(webserver, "cert-change") | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-a.example.test.target") | 
					
						
							| 
									
										
										
										
											2020-09-03 15:31:06 +01:00
										 |  |  |           check_connection_key_bits(client, "a.example.test", "384") | 
					
						
							| 
									
										
										
										
											2020-09-04 18:48:47 +01:00
										 |  |  |           webserver.succeed("grep testing /var/lib/acme/a.example.test/test") | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 21:52:49 +01:00
										 |  |  |       with subtest("Correctly implements OCSP stapling"): | 
					
						
							|  |  |  |           switch_to(webserver, "ocsp-stapling") | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-a.example.test.target") | 
					
						
							|  |  |  |           check_stapling(client, "a.example.test") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |       with subtest("Can request certificate with HTTPS-01 when nginx startup is delayed"): | 
					
						
							|  |  |  |           switch_to(webserver, "slow-startup") | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-slow.example.com.target") | 
					
						
							|  |  |  |           check_issuer(webserver, "slow.example.com", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "slow.example.com") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       with subtest("Can request certificate for vhost + aliases (nginx)"): | 
					
						
							| 
									
										
										
										
											2020-09-04 23:39:22 +01:00
										 |  |  |           # Check the key hash before and after adding an alias. It should not change. | 
					
						
							|  |  |  |           # The previous test reverts the ed384 change | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-a.example.test.target") | 
					
						
							|  |  |  |           keyhash_old = webserver.succeed("md5sum /var/lib/acme/a.example.test/key.pem") | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           switch_to(webserver, "nginx-aliases") | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-a.example.test.target") | 
					
						
							|  |  |  |           check_issuer(webserver, "a.example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "a.example.test") | 
					
						
							|  |  |  |           check_connection(client, "b.example.test") | 
					
						
							| 
									
										
										
										
											2020-09-04 23:39:22 +01:00
										 |  |  |           keyhash_new = webserver.succeed("md5sum /var/lib/acme/a.example.test/key.pem") | 
					
						
							|  |  |  |           assert keyhash_old == keyhash_new | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       with subtest("Can request certificates for vhost + aliases (apache-httpd)"): | 
					
						
							|  |  |  |           switch_to(webserver, "httpd-aliases") | 
					
						
							|  |  |  |           webserver.wait_for_unit("acme-finished-c.example.test.target") | 
					
						
							|  |  |  |           check_issuer(webserver, "c.example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "c.example.test") | 
					
						
							|  |  |  |           check_connection(client, "d.example.test") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       with subtest("Can reload httpd when timer triggers renewal"): | 
					
						
							|  |  |  |           # Switch to selfsigned first | 
					
						
							|  |  |  |           webserver.succeed("systemctl clean acme-c.example.test.service --what=state") | 
					
						
							|  |  |  |           webserver.succeed("systemctl start acme-selfsigned-c.example.test.service") | 
					
						
							|  |  |  |           check_issuer(webserver, "c.example.test", "minica") | 
					
						
							|  |  |  |           webserver.succeed("systemctl start httpd-config-reload.service") | 
					
						
							|  |  |  |           webserver.succeed("systemctl start test-renew-httpd.target") | 
					
						
							|  |  |  |           check_issuer(webserver, "c.example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "c.example.test") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 02:09:34 +00:00
										 |  |  |       with subtest("Can request wildcard certificates using DNS-01 challenge"): | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           switch_to(webserver, "dns-01") | 
					
						
							| 
									
										
										
										
											2020-03-23 20:35:16 +00:00
										 |  |  |           webserver.wait_for_unit("acme-finished-example.test.target") | 
					
						
							| 
									
										
										
										
											2020-06-19 20:27:46 +01:00
										 |  |  |           check_issuer(webserver, "example.test", "pebble") | 
					
						
							|  |  |  |           check_connection(client, "dns.example.test") | 
					
						
							| 
									
										
										
										
											2019-08-29 16:32:59 +02:00
										 |  |  |     '';
 | 
					
						
							| 
									
										
										
										
											2020-04-20 01:36:29 +01:00
										 |  |  | }) |