Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion readme-vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ cap_add_param_vars:
opt_param_usage_include_env: true
opt_param_env_vars:
- {env_var: "SUBDOMAINS", env_value: "www,", desc: "Subdomains you'd like the cert to cover (comma separated, no spaces) ie. `www,ftp,cloud`. For a wildcard cert, set this *exactly* to `wildcard` (wildcard cert is available via `dns` validation only)"}
- {env_var: "CERTPROVIDER", env_value: "", desc: "Optionally define the cert provider. Set to `zerossl` for ZeroSSL certs (requires existing [ZeroSSL account](https://app.zerossl.com/signup) and the e-mail address entered in `EMAIL` env var). Otherwise defaults to Let's Encrypt."}
- {env_var: "CERTPROVIDER", env_value: "", desc: "Optionally define the cert provider. Set to `zerossl` for ZeroSSL certs (requires existing [ZeroSSL account](https://app.zerossl.com/signup) and the e-mail address entered in `EMAIL` env var). Set to `custom` to use a custom/internal ACME server (e.g. [step-ca](https://smallstep.com/docs/step-ca/)), which requires `CERTPROVIDERURL` (see below). Otherwise defaults to Let's Encrypt."}
- {env_var: "CERTPROVIDERURL", env_value: "", desc: "Only used when `CERTPROVIDER=custom`. The ACME directory URL of your custom ACME server, ie. `https://ca.example.com/acme/acme/directory`."}
- {env_var: "ACMECABUNDLE", env_value: "", desc: "Only used when `CERTPROVIDER=custom`. Trust an internal CA so SWAG can connect to your ACME server over TLS. Either a path to a CA bundle PEM file mounted into the container, or the base64-encoded contents of that PEM."}
- {env_var: "EAB_KID", env_value: "", desc: "Only used when `CERTPROVIDER=custom`. External Account Binding key identifier, if your custom ACME server requires EAB. Must be set together with `EAB_HMAC_KEY`."}
- {env_var: "EAB_HMAC_KEY", env_value: "", desc: "Only used when `CERTPROVIDER=custom`. External Account Binding HMAC key, if your custom ACME server requires EAB. Must be set together with `EAB_KID`."}
- {env_var: "DNSPLUGIN", env_value: "cloudflare", desc: "Required if `VALIDATION` is set to `dns`. Options are `acmedns`, `aliyun`, `azure`, `bunny`, `cloudflare`, `cpanel`, `desec`, `digitalocean`, `directadmin`, `dnsimple`, `dnsmadeeasy`, `dnspod`, `do`, `domeneshop`, `dreamhost`, `duckdns`, `dynu`, `freedns`, `gandi`, `gehirn`, `glesys`, `godaddy`, `google`, `he`, `hetzner`, `hetzner-cloud`, `infomaniak`, `inwx`, `ionos`, `linode`, `loopia`, `luadns`, `namecheap`, `netcup`, `njalla`, `nsone`, `ovh`, `porkbun`, `rfc2136`, `route53`, `sakuracloud`, `standalone`, `transip`, and `vultr`. Also need to enter the credentials into the corresponding ini (or json for some plugins) file under `/config/dns-conf`."}
- {env_var: "PROPAGATION", env_value: "", desc: "Optionally override (in seconds) the default propagation time for the dns plugins."}
- {env_var: "EMAIL", env_value: "", desc: "Optional e-mail address used for cert expiration notifications (Required for ZeroSSL)."}
Expand Down Expand Up @@ -219,6 +223,7 @@ init_diagram: |
"swag:latest" <- Base Images
# changelog
changelogs:
- {date: "19.06.26:", desc: "Add support for custom/internal ACME servers via `CERTPROVIDER=custom` with `CERTPROVIDERURL`, optional `ACMECABUNDLE` for internal CA trust, and optional `EAB_KID`/`EAB_HMAC_KEY` for External Account Binding."}
- {date: "01.06.26:", desc: "Remove obsolete old cert check logic."}
- {date: "23.01.26:", desc: "Reorder init to fix proxy conf version checks."}
- {date: "21.12.25:", desc: "Add support for hetzner-cloud dns validation."}
Expand Down
5 changes: 5 additions & 0 deletions root/app/le-renew.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@ echo "<------------------------------------------------->"
echo
echo "<------------------------------------------------->"
echo "cronjob running on $(date)"
# Trust the custom/internal CA (CERTPROVIDER=custom) when renewing, since
# REQUESTS_CA_BUNDLE is an env var and is not persisted in cli.ini.
if [[ -f /config/cabundle.pem ]]; then
export REQUESTS_CA_BUNDLE="/config/cabundle.pem"
fi
echo "Running certbot renew"
certbot renew --non-interactive --config-dir /config/etc/letsencrypt --logs-dir /config/log/letsencrypt --work-dir /tmp/letsencrypt --config /config/etc/letsencrypt/cli.ini
77 changes: 71 additions & 6 deletions root/etc/s6-overlay/s6-rc.d/init-certbot-config/run
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ EXTRA_DOMAINS=${EXTRA_DOMAINS}\\n\
ONLY_SUBDOMAINS=${ONLY_SUBDOMAINS}\\n\
VALIDATION=${VALIDATION}\\n\
CERTPROVIDER=${CERTPROVIDER}\\n\
CERTPROVIDERURL=${CERTPROVIDERURL}\\n\
DNSPLUGIN=${DNSPLUGIN}\\n\
EMAIL=${EMAIL}\\n\
STAGING=${STAGING}\\n"
Expand All @@ -23,6 +24,12 @@ for i in "${SANED_VARS[@]}"; do
export echo "${i}"="$(echo "${!i}" | tr '[:upper:]' '[:lower:]')"
done

# Custom ACME vars are case-sensitive (URL paths, base64 EAB keys / CA bundle),
# so only strip surrounding quotes; do not lowercase them.
for i in CERTPROVIDERURL ACMECABUNDLE EAB_KID EAB_HMAC_KEY; do
export "${i}=${!i//\"/}"
done

# Check for and install requested DNS plugins
if grep -q "universal-package-install" <<< "${DOCKER_MODS}" && grep -q "certbot-dns" <<< "${INSTALL_PIP_PACKAGES}"; then
echo "**** Installing requested dns plugins ****"
Expand Down Expand Up @@ -80,7 +87,7 @@ if [[ -f "/config/donoteditthisfile.conf" ]]; then
mv /config/donoteditthisfile.conf /config/.donoteditthisfile.conf
fi
if [[ ! -f "/config/.donoteditthisfile.conf" ]]; then
echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf
echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGCERTPROVIDERURL=\"${CERTPROVIDERURL}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf
echo "Created .donoteditthisfile.conf"
fi

Expand Down Expand Up @@ -186,15 +193,23 @@ if [[ ! "${URL}" = "${ORIGURL}" ]] ||
[[ ! "${DNSPLUGIN}" = "${ORIGDNSPLUGIN}" ]] ||
[[ ! "${PROPAGATION}" = "${ORIGPROPAGATION}" ]] ||
[[ ! "${STAGING}" = "${ORIGSTAGING}" ]] ||
[[ ! "${CERTPROVIDER}" = "${ORIGCERTPROVIDER}" ]]; then
[[ ! "${CERTPROVIDER}" = "${ORIGCERTPROVIDER}" ]] ||
[[ ! "${CERTPROVIDERURL}" = "${ORIGCERTPROVIDERURL}" ]]; then
echo "Different validation parameters entered than what was used before. Revoking and deleting existing certificate, and an updated one will be created"
if [[ "${ORIGCERTPROVIDER}" = "zerossl" ]]; then
if [[ "${ORIGCERTPROVIDER}" = "custom" ]]; then
REV_ACMESERVER=("${ORIGCERTPROVIDERURL}")
elif [[ "${ORIGCERTPROVIDER}" = "zerossl" ]]; then
REV_ACMESERVER=("https://acme.zerossl.com/v2/DV90")
elif [[ "${ORIGSTAGING}" = "true" ]]; then
REV_ACMESERVER=("https://acme-staging-v02.api.letsencrypt.org/directory")
else
REV_ACMESERVER=("https://acme-v02.api.letsencrypt.org/directory")
fi
# if the previous provider was a custom internal CA, trust its bundle so the
# revocation request can verify TLS
if [[ "${ORIGCERTPROVIDER}" = "custom" ]] && [[ -f /config/cabundle.pem ]]; then
export REQUESTS_CA_BUNDLE="/config/cabundle.pem"
fi
if [[ -f /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem ]]; then
certbot revoke --config-dir /config/etc/letsencrypt --logs-dir /config/log/letsencrypt --work-dir /tmp/letsencrypt --config /config/etc/letsencrypt/cli.ini --non-interactive --cert-path /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem --key-path /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/privkey.pem --server "${REV_ACMESERVER[@]}" || true
else
Expand All @@ -204,13 +219,34 @@ if [[ ! "${URL}" = "${ORIGURL}" ]] ||
fi

# saving new variables
echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf
echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGCERTPROVIDERURL=\"${CERTPROVIDERURL}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf

# if zerossl is selected or staging is set to true, use the relevant server
if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ "${STAGING}" = "true" ]]; then
echo "ZeroSSL does not support staging mode, ignoring STAGING variable"
fi
if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then
if [[ "${CERTPROVIDER}" = "custom" ]]; then
if [[ -z "${CERTPROVIDERURL}" ]]; then
echo "CERTPROVIDER is set to custom, but CERTPROVIDERURL is not set. Please set CERTPROVIDERURL to the ACME directory URL of your custom ACME server (e.g. https://ca.example.com/acme/acme/directory). Sleeping."
sleep infinity
fi
echo "Using custom ACME server as the cert provider: ${CERTPROVIDERURL}"
if [[ "${STAGING}" = "true" ]]; then
echo "STAGING has no effect with a custom ACME server, ignoring STAGING variable"
fi
ACMESERVER="${CERTPROVIDERURL}"
# Trust a custom/internal CA so certbot can reach the ACME server over TLS.
# ACMECABUNDLE may be a path to a mounted PEM file or a base64-encoded PEM.
if [[ -n "${ACMECABUNDLE}" ]]; then
if [[ -f "${ACMECABUNDLE}" ]]; then
cp "${ACMECABUNDLE}" /config/cabundle.pem
else
echo "${ACMECABUNDLE}" | base64 -d - >/config/cabundle.pem
fi
lsiown abc:abc /config/cabundle.pem
echo "Custom CA bundle written to /config/cabundle.pem"
fi
elif [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then
echo "ZeroSSL is selected as the cert provider, registering cert with ${EMAIL}"
ACMESERVER="https://acme.zerossl.com/v2/DV90"
elif [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -z "${EMAIL}" ]]; then
Expand All @@ -227,6 +263,20 @@ fi

set_ini_value "server" "${ACMESERVER}" /config/etc/letsencrypt/cli.ini

# Trust the custom CA for this issuance run; clean up custom remnants when not
# using a custom provider so a previous internal CA / EAB is not reused.
if [[ "${CERTPROVIDER}" = "custom" ]] && [[ -f /config/cabundle.pem ]]; then
export REQUESTS_CA_BUNDLE="/config/cabundle.pem"
elif [[ "${CERTPROVIDER}" != "custom" ]]; then
unset REQUESTS_CA_BUNDLE
rm -f /config/cabundle.pem
if [[ "${ORIGCERTPROVIDER}" = "custom" ]]; then
# drop EAB creds left over from a custom provider so Let's Encrypt /
# ZeroSSL re-register cleanly
sed -i -e "/^eab-kid\b/d" -e "/^eab-hmac-key\b/d" /config/etc/letsencrypt/cli.ini
fi
fi

# figuring out domain only vs domain & subdomains vs subdomains only
DOMAINS_ARRAY=()
if [[ -z "${SUBDOMAINS}" ]] || [[ "${ONLY_SUBDOMAINS}" != true ]]; then
Expand Down Expand Up @@ -321,7 +371,22 @@ fi

# generating certs if necessary
if [[ ! -f "/config/keys/letsencrypt/fullchain.pem" ]]; then
if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then
if [[ "${CERTPROVIDER}" = "custom" ]]; then
# Optional External Account Binding for custom ACME servers (e.g. step-ca
# ACME provisioners configured to require EAB). Only consumed at account
# registration, so it has no effect on later renewals.
if [[ -n "${EAB_KID}" ]] && [[ -n "${EAB_HMAC_KEY}" ]]; then
echo "Registering with custom ACME server using provided EAB credentials"
set_ini_value "eab-kid" "${EAB_KID}" /config/etc/letsencrypt/cli.ini
set_ini_value "eab-hmac-key" "${EAB_HMAC_KEY}" /config/etc/letsencrypt/cli.ini
elif [[ -n "${EAB_KID}" ]] || [[ -n "${EAB_HMAC_KEY}" ]]; then
echo "Only one of EAB_KID / EAB_HMAC_KEY is set. Both are required to use External Account Binding. Sleeping."
sleep infinity
else
# remove any EAB values left over from a previous provider
sed -i -e "/^eab-kid\b/d" -e "/^eab-hmac-key\b/d" /config/etc/letsencrypt/cli.ini
fi
elif [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then
echo "Retrieving EAB from ZeroSSL"
EAB_CREDS=$(curl -s https://api.zerossl.com/acme/eab-credentials-email --data "email=${EMAIL}")
ZEROSSL_EAB_KID=$(echo "${EAB_CREDS}" | jq .eab_kid)
Expand Down