nixos/acme: add dns-01 test, fix cert locating bug
This commit is contained in:
parent
2181313c54
commit
ac983cff48
|
@ -316,23 +316,26 @@ in
|
|||
'';
|
||||
ExecStartPost =
|
||||
let
|
||||
keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
|
||||
script = pkgs.writeScript "acme-post-start" ''
|
||||
#!${pkgs.runtimeShell} -e
|
||||
cd ${apath}
|
||||
|
||||
# Test that existing cert is older than new cert
|
||||
KEY=${spath}/certificates/*${data.domain}.key
|
||||
KEY=${spath}/certificates/${keyName}.key
|
||||
if [ -e $KEY -a $KEY -nt key.pem ]; then
|
||||
cp -p ${spath}/certificates/*${data.domain}.key key.pem
|
||||
cp -p ${spath}/certificates/*${data.domain}.crt cert.pem
|
||||
cp -p ${spath}/certificates/*${data.domain}.issuer.crt chain.pem
|
||||
cp -p ${spath}/certificates/${keyName}.key key.pem
|
||||
cp -p ${spath}/certificates/${keyName}.crt cert.pem
|
||||
cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem
|
||||
cat cert.pem chain.pem > fullchain.pem
|
||||
cat key.pem cert.pem chain.pem > full.pem
|
||||
chmod ${rights} *.pem
|
||||
chown '${data.user}:${data.group}' *.pem
|
||||
fi
|
||||
|
||||
echo post stuff
|
||||
${data.postRun}
|
||||
echo done
|
||||
'';
|
||||
in
|
||||
"+${script}";
|
||||
|
|
|
@ -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
|
||||
];
|
||||
};
|
||||
|
||||
acmeStandalone = { config, pkgs, ... }: {
|
||||
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 = { 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'"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -5,5 +5,8 @@ in {
|
|||
nodes.letsencrypt.config.networking.primaryIPAddress
|
||||
];
|
||||
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.email = "webmaster@example.com";
|
||||
|
||||
security.pki.certificateFiles = [ letsencrypt-ca ];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue