-
-
Notifications
You must be signed in to change notification settings - Fork 15k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
acme-dns: init at 0.8 #83474
acme-dns: init at 0.8 #83474
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -314,21 +314,44 @@ in | |
renewOpts = escapeShellArgs (globalOpts ++ | ||
[ "renew" "--days" (toString cfg.validMinDays) ] ++ | ||
certOpts ++ data.extraLegoRenewFlags); | ||
|
||
acmeDnsDeps = optional (data.dnsProvider == "acme-dns") | ||
"acme-dns-${cert}.service"; | ||
|
||
commonServiceConfig = { | ||
Type = "oneshot"; | ||
User = data.user; | ||
Group = data.group; | ||
PrivateTmp = true; | ||
StateDirectory = "acme/.lego/${cert} acme/.lego/accounts ${lpath}"; | ||
StateDirectoryMode = if data.allowKeysForGroup then "750" else "700"; | ||
WorkingDirectory = spath; | ||
# Only try loading the credentialsFile if the dns challenge is enabled | ||
EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null; | ||
}; | ||
|
||
acmeService = { | ||
description = "Renew ACME Certificate for ${cert}"; | ||
after = [ "network.target" "network-online.target" ]; | ||
|
||
after = [ "network.target" "network-online.target" ] | ||
++ acmeDnsDeps; | ||
wants = [ "network-online.target" ]; | ||
# We use `requires` to avoid lego running and falling | ||
# back to its own acme-dns registration logic if ours | ||
# fails; see acmeDnsService for rationale. | ||
requires = acmeDnsDeps; | ||
wantedBy = mkIf (!config.boot.isContainer) [ "multi-user.target" ]; | ||
serviceConfig = { | ||
Type = "oneshot"; | ||
User = data.user; | ||
Group = data.group; | ||
PrivateTmp = true; | ||
StateDirectory = "acme/.lego/${cert} acme/.lego/accounts ${lpath}"; | ||
StateDirectoryMode = if data.allowKeysForGroup then "750" else "700"; | ||
WorkingDirectory = spath; | ||
# Only try loading the credentialsFile if the dns challenge is enabled | ||
EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null; | ||
|
||
# acme-dns requires CNAME support for _acme-challenge | ||
# records. This setting only affects the behaviour of | ||
# DNS-01 challenge propagation checks when a CNAME | ||
# record is present; see: | ||
# | ||
# * https://go-acme.github.io/lego/dns/#experimental-features | ||
# * https://github.com/go-acme/lego/blob/v3.5.0/challenge/dns01/dns_challenge.go#L179-L185 | ||
environment.LEGO_EXPERIMENTAL_CNAME_SUPPORT = "true"; | ||
|
||
serviceConfig = commonServiceConfig // { | ||
ExecStart = pkgs.writeScript "acme-start" '' | ||
#!${pkgs.runtimeShell} -e | ||
test -L ${spath}/accounts -o -d ${spath}/accounts || ln -s ../accounts ${spath}/accounts | ||
|
@@ -364,8 +387,63 @@ in | |
in | ||
"+${script}"; | ||
}; | ||
}; | ||
|
||
# For certificates using the acme-dns dnsProvider, we | ||
# handle registration and CNAME checking ourselves | ||
# rather than letting lego do it, as it only attempts | ||
# registration upon renewal, leading to unpredictable | ||
# timing of the manual interventions required to add | ||
# the CNAME records. | ||
acmeDnsService = { | ||
description = "Ensure acme-dns Credentials for ${cert}"; | ||
|
||
wants = [ "network-online.target" ]; | ||
after = [ "network-online.target" ]; | ||
|
||
serviceConfig = commonServiceConfig; | ||
|
||
# TODO: is openssl needed here? (needs testing with HTTPS | ||
# acme-dns API) | ||
path = [ pkgs.curl pkgs.openssl pkgs.dnsutils pkgs.jq ]; | ||
script = '' | ||
set -uo pipefail | ||
|
||
if ! [ -e "$ACME_DNS_STORAGE_PATH" ]; then | ||
# We use --retry because the acme-dns server might | ||
# not be up when the service starts (especially if | ||
# it's local). | ||
response=$(curl --fail --silent --show-error \ | ||
--request POST "$ACME_DNS_API_BASE/register" \ | ||
--max-time 30 --retry 5 --retry-connrefused \ | ||
| jq ${escapeShellArg "{${builtins.toJSON cert}: .}"}) | ||
# Write the response. We do this separately to the | ||
# request to ensure that $ACME_DNS_STORAGE_PATH | ||
# doesn't get written to if curl or jq fail. | ||
echo "$response" > "$ACME_DNS_STORAGE_PATH" | ||
fi | ||
|
||
src='_acme-challenge.${cert}.' | ||
if ! target=$(jq --exit-status --raw-output \ | ||
'.${builtins.toJSON cert}.fulldomain' \ | ||
"$ACME_DNS_STORAGE_PATH"); then | ||
echo "$ACME_DNS_STORAGE_PATH has invalid format." | ||
echo "Try removing it and then running:" | ||
echo ' systemctl restart acme-${cert}.service' | ||
exit 1 | ||
fi | ||
|
||
if ! dig +short CNAME "$src" | grep -qF "$target"; then | ||
echo "Required CNAME record for $src not found." | ||
echo "Please add the following DNS record:" | ||
echo " $src CNAME $target." | ||
echo "and then run:" | ||
echo ' systemctl restart acme-${cert}.service' | ||
exit 1 | ||
fi | ||
Comment on lines
+410
to
+443
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the registration logic moved to the lego binary or some helper binary installed if with I'm a bit afraid of the more flaky parts here to break in funny ways, and assume more people would benefit from being able to check the DNS configuration w.r.t. acme-dns. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lego's goacmedns dependency isn't packaged separately, but if it was then we could use Unfortunately lego handles this really badly natively; it doesn't even bother registering until renew time (so you have to scramble to set up CNAME records at unpredictable intervals on initial migration to acme-dns certificates) and doesn't check the CNAME records at all (instead, it just prints the record unconditionally on initial registration and then fails mysteriously if you don't set it up). It would be nice if we could delegate some of this to lego, but given that we're already having to architect our own solution to forcing renewals on configuration changes, etc., I think some of this complexity is unavoidable to ensure a reasonable user experience unless lego changes its interface/operating model. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened go-acme/lego#1119, go-acme/lego#1120. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add comments to these issues into the comment at the beginning of the block. |
||
''; | ||
}; | ||
|
||
selfsignedService = { | ||
description = "Create preliminary self-signed certificate for ${cert}"; | ||
path = [ pkgs.openssl ]; | ||
|
@@ -416,6 +494,8 @@ in | |
}; | ||
in ( | ||
[ { name = "acme-${cert}"; value = acmeService; } ] | ||
++ optional (data.dnsProvider == "acme-dns") | ||
{ name = "acme-dns-${cert}"; value = acmeDnsService; } | ||
++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; } | ||
); | ||
servicesAttr = listToAttrs services; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dependency shouldn't be added if acme-dns isn't running on the same machine.