aboutsummaryrefslogtreecommitdiff
path: root/nixos/tests/acme.nix
diff options
context:
space:
mode:
authorLucas Savva <lucas@m1cr0man.com>2020-02-09 02:09:34 +0000
committerLucas Savva <lucas@m1cr0man.com>2020-02-09 02:09:34 +0000
commitac983cff48b70c82c6984895c7d7558d7a1bb26a (patch)
tree675dc3dd8c66023ea071ba23af7c3c871633fe1e /nixos/tests/acme.nix
parent2181313c542364d63674ed34cbf69d7691ddfa0d (diff)
nixos/acme: add dns-01 test, fix cert locating bug
Diffstat (limited to 'nixos/tests/acme.nix')
-rw-r--r--nixos/tests/acme.nix118
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'"
+ )
'';
}