2017-07-27 12:24:17 +01:00
|
|
|
let
|
2018-09-24 20:06:31 +01:00
|
|
|
commonConfig = ./common/letsencrypt/common.nix;
|
2017-07-27 12:24:17 +01:00
|
|
|
in import ./make-test.nix {
|
|
|
|
name = "acme";
|
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
nodes = rec {
|
2018-07-11 23:56:48 +01:00
|
|
|
letsencrypt = ./common/letsencrypt;
|
2017-07-27 12:24:17 +01:00
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
acmeStandalone = { config, pkgs, ... }: {
|
|
|
|
imports = [ commonConfig ];
|
|
|
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
|
|
|
networking.extraHosts = ''
|
|
|
|
${config.networking.primaryIPAddress} standalone.com
|
|
|
|
'';
|
2019-10-25 23:45:19 +01:00
|
|
|
security.acme = {
|
|
|
|
server = "https://acme-v02.api.letsencrypt.org/dir";
|
|
|
|
certs."standalone.com" = {
|
|
|
|
webroot = "/var/lib/acme/acme-challenges";
|
|
|
|
};
|
2019-08-29 15:32:59 +01:00
|
|
|
};
|
|
|
|
systemd.targets."acme-finished-standalone.com" = {};
|
|
|
|
systemd.services."acme-standalone.com" = {
|
|
|
|
wants = [ "acme-finished-standalone.com.target" ];
|
|
|
|
before = [ "acme-finished-standalone.com.target" ];
|
|
|
|
};
|
|
|
|
services.nginx.enable = true;
|
|
|
|
services.nginx.virtualHosts."standalone.com" = {
|
|
|
|
locations."/.well-known/acme-challenge".root = "/var/lib/acme/acme-challenges";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2017-07-27 12:24:17 +01:00
|
|
|
webserver = { config, pkgs, ... }: {
|
|
|
|
imports = [ commonConfig ];
|
|
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
|
|
|
|
|
|
networking.extraHosts = ''
|
2019-08-29 15:32:59 +01:00
|
|
|
${config.networking.primaryIPAddress} a.example.com
|
|
|
|
${config.networking.primaryIPAddress} b.example.com
|
2017-07-27 12:24:17 +01:00
|
|
|
'';
|
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
# A target remains active. Use this to probe the fact that
|
|
|
|
# a service fired eventhough it is not RemainAfterExit
|
|
|
|
systemd.targets."acme-finished-a.example.com" = {};
|
|
|
|
systemd.services."acme-a.example.com" = {
|
|
|
|
wants = [ "acme-finished-a.example.com.target" ];
|
|
|
|
before = [ "acme-finished-a.example.com.target" ];
|
|
|
|
};
|
|
|
|
|
2017-07-27 12:24:17 +01:00
|
|
|
services.nginx.enable = true;
|
2019-08-29 15:32:59 +01:00
|
|
|
|
|
|
|
services.nginx.virtualHosts."a.example.com" = {
|
2017-07-27 12:24:17 +01:00
|
|
|
enableACME = true;
|
|
|
|
forceSSL = true;
|
|
|
|
locations."/".root = pkgs.runCommand "docroot" {} ''
|
|
|
|
mkdir -p "$out"
|
|
|
|
echo hello world > "$out/index.html"
|
|
|
|
'';
|
|
|
|
};
|
2019-08-29 15:32:59 +01:00
|
|
|
|
2019-10-25 23:45:19 +01:00
|
|
|
security.acme.server = "https://acme-v02.api.letsencrypt.org/dir";
|
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
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" ];
|
|
|
|
before = [ "acme-finished-b.example.com.target" ];
|
|
|
|
};
|
|
|
|
services.nginx.virtualHosts."b.example.com" = {
|
|
|
|
enableACME = true;
|
|
|
|
forceSSL = true;
|
|
|
|
locations."/".root = pkgs.runCommand "docroot" {} ''
|
|
|
|
mkdir -p "$out"
|
|
|
|
echo hello world > "$out/index.html"
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
})
|
|
|
|
];
|
2017-07-27 12:24:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
client = commonConfig;
|
|
|
|
};
|
|
|
|
|
2019-10-18 18:13:04 +01:00
|
|
|
testScript = {nodes, ...}:
|
2019-08-29 15:32:59 +01:00
|
|
|
let
|
|
|
|
newServerSystem = nodes.webserver2.config.system.build.toplevel;
|
|
|
|
switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
|
|
|
|
in
|
|
|
|
# Note, waitForUnit does not work for oneshot services that do not have RemainAfterExit=true,
|
|
|
|
# this is because a oneshot goes from inactive => activating => inactive, and never
|
|
|
|
# reaches the active state. To work around this, we create some mock target units which
|
|
|
|
# get pulled in by the oneshot units. The target units linger after activation, and hence we
|
|
|
|
# can use them to probe that a oneshot fired. It is a bit ugly, but it is the best we can do
|
|
|
|
''
|
2019-10-18 18:13:04 +01:00
|
|
|
$client->start;
|
|
|
|
$letsencrypt->start;
|
|
|
|
$acmeStandalone->start;
|
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
$letsencrypt->waitForUnit("default.target");
|
2019-10-18 18:13:04 +01:00
|
|
|
$letsencrypt->waitForUnit("pebble.service");
|
2019-08-29 15:32:59 +01:00
|
|
|
|
|
|
|
subtest "can request certificate with HTTPS-01 challenge", sub {
|
|
|
|
$acmeStandalone->waitForUnit("default.target");
|
|
|
|
$acmeStandalone->succeed("systemctl start acme-standalone.com.service");
|
|
|
|
$acmeStandalone->waitForUnit("acme-finished-standalone.com.target");
|
|
|
|
};
|
|
|
|
|
2019-10-18 18:13:04 +01:00
|
|
|
$client->waitForUnit("default.target");
|
|
|
|
|
|
|
|
$client->succeed('curl https://acme-v02.api.letsencrypt.org:15000/roots/0 > /tmp/ca.crt');
|
|
|
|
$client->succeed('curl https://acme-v02.api.letsencrypt.org:15000/intermediate-keys/0 >> /tmp/ca.crt');
|
|
|
|
|
2019-08-29 15:32:59 +01:00
|
|
|
subtest "Can request certificate for nginx service", sub {
|
|
|
|
$webserver->waitForUnit("acme-finished-a.example.com.target");
|
2019-10-18 18:13:04 +01:00
|
|
|
$client->succeed('curl --cacert /tmp/ca.crt https://a.example.com/ | grep -qF "hello world"');
|
2019-08-29 15:32:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
subtest "Can add another certificate for nginx service", sub {
|
|
|
|
$webserver->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test");
|
|
|
|
$webserver->waitForUnit("acme-finished-b.example.com.target");
|
2019-10-18 18:13:04 +01:00
|
|
|
$client->succeed('curl --cacert /tmp/ca.crt https://b.example.com/ | grep -qF "hello world"');
|
2019-08-29 15:32:59 +01:00
|
|
|
};
|
|
|
|
'';
|
2017-07-27 12:24:17 +01:00
|
|
|
}
|