Merge pull request #116369 from m1cr0man/master

nixos/acme: Fix webroot issues
This commit is contained in:
Robert Hensing 2021-03-23 21:31:42 +01:00 committed by GitHub
commit e0e241c219
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 14 deletions

View file

@ -24,7 +24,7 @@ let
Type = "oneshot";
User = "acme";
Group = mkDefault "acme";
UMask = 0023;
UMask = 0022;
StateDirectoryMode = 750;
ProtectSystem = "full";
PrivateTmp = true;
@ -303,9 +303,15 @@ let
}
${optionalString (data.webroot != null) ''
# Ensure the webroot exists
mkdir -p '${data.webroot}/.well-known/acme-challenge'
chown 'acme:${data.group}' ${data.webroot}/{.well-known,.well-known/acme-challenge}
# Ensure the webroot exists. Fixing group is required in case configuration was changed between runs.
# Lego will fail if the webroot does not exist at all.
(
mkdir -p '${data.webroot}/.well-known/acme-challenge' \
&& chgrp '${data.group}' ${data.webroot}/.well-known/acme-challenge
) || (
echo 'Please ensure ${data.webroot}/.well-known/acme-challenge exists and is writable by acme:${data.group}' \
&& exit 1
)
''}
echo '${domainHash}' > domainhash.txt

View file

@ -253,7 +253,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
def check_connection(node, domain, retries=3):
assert retries >= 0
assert retries >= 0, f"Failed to connect to https://{domain}"
result = node.succeed(
"openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"
@ -262,12 +262,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
for line in result.lower().split("\n"):
if "verification" in line and "error" in line:
time.sleep(1)
time.sleep(3)
return check_connection(node, domain, retries - 1)
def check_connection_key_bits(node, domain, bits, retries=3):
assert retries >= 0
assert retries >= 0, f"Did not find expected number of bits ({bits}) in key"
result = node.succeed(
"openssl s_client -CAfile /tmp/ca.crt"
@ -277,12 +277,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
print("Key type:", result)
if bits not in result:
time.sleep(1)
time.sleep(3)
return check_connection_key_bits(node, domain, bits, retries - 1)
def check_stapling(node, domain, retries=3):
assert retries >= 0
assert retries >= 0, "OCSP Stapling check failed"
# Pebble doesn't provide a full OCSP responder, so just check the URL
result = node.succeed(
@ -293,10 +293,23 @@ in import ./make-test-python.nix ({ lib, ... }: {
print("OCSP Responder URL:", result)
if "${caDomain}:4002" not in result.lower():
time.sleep(1)
time.sleep(3)
return check_stapling(node, domain, retries - 1)
def download_ca_certs(node, retries=5):
assert retries >= 0, "Failed to connect to pebble to download root CA certs"
exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
exit_code_2, _ = node.execute(
"curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt"
)
if exit_code + exit_code_2 > 0:
time.sleep(3)
return download_ca_certs(node, retries - 1)
client.start()
dnsserver.start()
@ -313,8 +326,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
acme.wait_for_unit("network-online.target")
acme.wait_for_unit("pebble.service")
client.succeed("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
client.succeed("curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt")
download_ca_certs(client)
with subtest("Can request certificate with HTTPS-01 challenge"):
webserver.wait_for_unit("acme-finished-a.example.test.target")
@ -322,6 +334,21 @@ in import ./make-test-python.nix ({ lib, ... }: {
check_issuer(webserver, "a.example.test", "pebble")
check_connection(client, "a.example.test")
with subtest("Certificates and accounts have safe + valid permissions"):
group = "${nodes.webserver.config.security.acme.certs."a.example.test".group}"
webserver.succeed(
f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
)
webserver.succeed(
f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/.lego/a.example.test/**/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
)
webserver.succeed(
f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1"
)
webserver.succeed(
f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c \"%a %U %G\" {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0"
)
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")
@ -375,8 +402,15 @@ in import ./make-test-python.nix ({ lib, ... }: {
assert keyhash_old == keyhash_new
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")
try:
switch_to(webserver, "httpd-aliases")
webserver.wait_for_unit("acme-finished-c.example.test.target")
except Exception as err:
_, output = webserver.execute(
"cat /var/log/httpd/*.log && ls -al /var/lib/acme/acme-challenge"
)
print(output)
raise err
check_issuer(webserver, "c.example.test", "pebble")
check_connection(client, "c.example.test")
check_connection(client, "d.example.test")