diff options
author | Lucas Savva <lucas@m1cr0man.com> | 2020-02-09 02:09:34 +0000 |
---|---|---|
committer | Lucas Savva <lucas@m1cr0man.com> | 2020-02-09 02:09:34 +0000 |
commit | ac983cff48b70c82c6984895c7d7558d7a1bb26a (patch) | |
tree | 675dc3dd8c66023ea071ba23af7c3c871633fe1e /nixos/tests/acme.nix | |
parent | 2181313c542364d63674ed34cbf69d7691ddfa0d (diff) |
nixos/acme: add dns-01 test, fix cert locating bug
Diffstat (limited to 'nixos/tests/acme.nix')
-rw-r--r-- | nixos/tests/acme.nix | 118 |
1 files changed, 101 insertions, 17 deletions
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index 6bd315ff1eaa..480e95e67c76 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -1,17 +1,51 @@ let commonConfig = ./common/letsencrypt/common.nix; + + dnsScript = {writeScript, dnsAddress, bash, curl}: writeScript "dns-hook.sh" '' + #!${bash}/bin/bash + set -euo pipefail + echo '[INFO]' "[$2]" 'dns-hook.sh' $* + if [ "$1" = "present" ]; then + ${curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt + else + ${curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt + fi + ''; + in import ./make-test-python.nix { name = "acme"; nodes = rec { - letsencrypt = ./common/letsencrypt; + letsencrypt = { nodes, lib, ... }: { + imports = [ ./common/letsencrypt ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; + }; + + dnsserver = { nodes, pkgs, ... }: { + networking.firewall.allowedTCPPorts = [ 8055 53 ]; + networking.firewall.allowedUDPPorts = [ 53 ]; + systemd.services.pebble-challtestsrv = { + enable = true; + description = "Pebble ACME challenge test server"; + requires = [ ]; + 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. + User = "root"; + Group = "root"; + }; + }; + }; - acmeStandalone = { config, pkgs, ... }: { + acmeStandalone = { nodes, lib, config, pkgs, ... }: { imports = [ commonConfig ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; networking.firewall.allowedTCPPorts = [ 80 ]; - networking.extraHosts = '' - ${config.networking.primaryIPAddress} standalone.com - ''; security.acme = { server = "https://acme-v02.api.letsencrypt.org/dir"; certs."standalone.com" = { @@ -29,14 +63,12 @@ in import ./make-test-python.nix { }; }; - webserver = { config, pkgs, ... }: { + webserver = { nodes, config, pkgs, lib, ... }: let parentConfig = config; in { imports = [ commonConfig ]; networking.firewall.allowedTCPPorts = [ 80 443 ]; - - networking.extraHosts = '' - ${config.networking.primaryIPAddress} a.example.com - ${config.networking.primaryIPAddress} b.example.com - ''; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; # A target remains active. Use this to probe the fact that # a service fired eventhough it is not RemainAfterExit @@ -61,10 +93,6 @@ in import ./make-test-python.nix { nesting.clone = [ ({pkgs, ...}: { - - networking.extraHosts = '' - ${config.networking.primaryIPAddress} b.example.com - ''; systemd.targets."acme-finished-b.example.com" = {}; systemd.services."acme-b.example.com" = { wants = [ "acme-finished-b.example.com.target" ]; @@ -79,15 +107,48 @@ in import ./make-test-python.nix { ''; }; }) + ({pkgs, config, nodes, lib, ...}: { + security.acme.certs."example.com" = { + domain = "*.example.com"; + dnsProvider = "exec"; + dnsPropagationCheck = false; + credentialsFile = with pkgs; writeText "wildcard.env" '' + EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }} + ''; + user = config.services.nginx.user; + group = config.services.nginx.group; + }; + systemd.targets."acme-finished-example.com" = {}; + systemd.services."acme-example.com" = { + wants = [ "acme-finished-example.com.target" ]; + before = [ "acme-finished-example.com.target" "nginx.service" ]; + wantedBy = [ "nginx.service" ]; + }; + services.nginx.virtualHosts."c.example.com" = { + forceSSL = true; + sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem"; + sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem"; + sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem"; + locations."/".root = pkgs.runCommand "docroot" {} '' + mkdir -p "$out" + echo hello world > "$out/index.html" + ''; + }; + }) ]; }; - client = commonConfig; + client = {nodes, lib, ...}: { + imports = [ commonConfig ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; + }; }; testScript = {nodes, ...}: let - newServerSystem = nodes.webserver2.config.system.build.toplevel; + newServerSystem = nodes.webserver.config.system.build.toplevel; switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test"; in # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true, @@ -97,6 +158,17 @@ in import ./make-test-python.nix { # can use them to probe that a oneshot fired. It is a bit ugly, but it is the best we can do '' client.start() + dnsserver.start() + + letsencrypt.wait_for_unit("default.target") + dnsserver.wait_for_unit("pebble-challtestsrv.service") + client.succeed( + 'curl --data \'{"host": "acme-v02.api.letsencrypt.org", "addresses": ["${nodes.letsencrypt.config.networking.primaryIPAddress}"]}\' http://${nodes.dnsserver.config.networking.primaryIPAddress}:8055/add-a' + ) + client.succeed( + 'curl --data \'{"host": "standalone.com", "addresses": ["${nodes.acmeStandalone.config.networking.primaryIPAddress}"]}\' http://${nodes.dnsserver.config.networking.primaryIPAddress}:8055/add-a' + ) + letsencrypt.start() acmeStandalone.start() @@ -129,5 +201,17 @@ in import ./make-test-python.nix { client.succeed( "curl --cacert /tmp/ca.crt https://b.example.com/ | grep -qF 'hello world'" ) + + with subtest("Can request wildcard certificates using DNS-01 challenge"): + webserver.succeed( + "${switchToNewServer}" + ) + webserver.succeed( + "/run/current-system/fine-tune/child-2/bin/switch-to-configuration test" + ) + webserver.wait_for_unit("acme-finished-example.com.target") + client.succeed( + "curl --cacert /tmp/ca.crt https://c.example.com/ | grep -qF 'hello world'" + ) ''; } |