Change:
Describe the bug The subtests for apache httpd in fail: : Removing these subtests makes the test pass: To Reproduce Steps to reproduce the behavior: Expected behavior Test success. Screenshots Additional context Notify maintainers Metadata Please run and paste the result. Maintainer information:
The cause is that the permissions on the configured webroot directory are wrong when switching from Nginx to httpd. Taking from a debug statement: This subsequently causes 403 errors in httpd with the following error in the log: There are 3 solutions, sorted by my own preference to implement: Change the UMask to 0022 (currently 0023) so that any group can read the folder. Fix the permissions in the acme module when renewals are run. Fix the permissions in the test suite. Keeping in mind, I feel that changing the UMask would be the best option that would "Just work" without affecting the potentially sensitive group permissions on the webroot. Also - it's a web directory which will effectively be readable by anyone (AKA anyone that visits the page) anyway. I'm not in favour of the last option as I feel fixing it for all users is important, and I would rather have this thread + take the flak for somewhat open permissions than break people's setups entirely.
That seems to align with the purpose of the challenge directory. If you change the umask, make sure it doesn't affect other files and directories.
I've some tests for the resulting file's permissions. Also I would like to highlight the work did a while back in to determine where/what permissions lego applies to files. 0023 was a bit stingy on my part from the beginning. It should've stayed as 0022 all along ;P

Discussion:
Type = "oneshot"; User = "acme"; Group = mkDefault "acme"; <del> UMask = 0023; </del> <ins> UMask = 0022; </ins> StateDirectoryMode = 750; ProtectSystem = "full"; PrivateTmp = true;

set -euxo pipefail ${optionalString (data.webroot != null) '' <del> # Ensure the webroot exists mkdir -p '${data.webroot}/.well-known/acme-challenge' chown 'acme:${data.group}' ${data.webroot}/{.well-known,.well-known/acme-challenge} </del> <ins> # 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 ) </ins> ''} echo '${domainHash}' > domainhash.txt

def check_connection(node, domain, retries=3): <del> assert retries >= 0 </del> <ins> assert retries >= 0, f"Failed to connect to https://{domain}" </ins> result = node.succeed( "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"

for line in result.lower().split("n"): if "verification" in line and "error" in line: <del> time.sleep(1) </del> <ins> time.sleep(3) </ins> return check_connection(node, domain, retries - 1) def check_connection_key_bits(node, domain, bits, retries=3): <del> assert retries >= 0 </del> <ins> assert retries >= 0, f"Did not find expected number of bits ({bits}) in key" </ins> result = node.succeed( "openssl s_client -CAfile /tmp/ca.crt"

print("Key type:", result) if bits not in result: <del> time.sleep(1) </del> <ins> time.sleep(3) </ins> return check_connection_key_bits(node, domain, bits, retries - 1) def check_stapling(node, domain, retries=3): <del> assert retries >= 0 </del> <ins> assert retries >= 0, "OCSP Stapling check failed" </ins> # Pebble doesn't provide a full OCSP responder, so just check the URL result = node.succeed(

print("OCSP Responder URL:", result) if "${caDomain}:4002" not in result.lower(): <del> time.sleep(1) </del> <ins> time.sleep(3) </ins> return check_stapling(node, domain, retries - 1) <ins> 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) </ins> client.start() dnsserver.start()

acme.wait_for_unit("network-online.target") acme.wait_for_unit("pebble.service") <del> client.succeed("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") client.succeed("curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt") </del> <ins> download_ca_certs(client) </ins> with subtest("Can request certificate with HTTPS-01 challenge"): webserver.wait_for_unit("acme-finished-a.example.test.target")

check_issuer(webserver, "a.example.test", "pebble") check_connection(client, "a.example.test") <ins> 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" ) </ins> 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")

assert keyhash_old == keyhash_new with subtest("Can request certificates for vhost + aliases (apache-httpd)"): <del> switch_to(webserver, "httpd-aliases") webserver.wait_for_unit("acme-finished-c.example.test.target") </del> <ins> 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 </ins> check_issuer(webserver, "c.example.test", "pebble") check_connection(client, "c.example.test") check_connection(client, "d.example.test")