diff options
Diffstat (limited to 'docker/auto-certbot')
-rw-r--r-- | docker/auto-certbot/Dockerfile | 20 | ||||
-rwxr-xr-x | docker/auto-certbot/daemon.bash | 107 | ||||
-rwxr-xr-x | docker/auto-certbot/get-cert-domains.py | 38 |
3 files changed, 165 insertions, 0 deletions
diff --git a/docker/auto-certbot/Dockerfile b/docker/auto-certbot/Dockerfile new file mode 100644 index 0000000..eeb6475 --- /dev/null +++ b/docker/auto-certbot/Dockerfile @@ -0,0 +1,20 @@ +FROM certbot/certbot:latest + +ARG CRUPEST_AUTO_CERTBOT_ADDITIONAL_PACKAGES="" +RUN apk add --no-cache tini coreutils bash ${CRUPEST_AUTO_CERTBOT_ADDITIONAL_PACKAGES} && python -m pip install cryptography + + +ARG CRUPEST_DOMAIN +ARG CRUPEST_ADDITIONAL_DOMAIN_LIST="" +ARG CRUPEST_EMAIL +ARG CRUPEST_AUTO_CERTBOT_POST_HOOK="" +# install bash +ENV CRUPEST_DOMAIN=${CRUPEST_DOMAIN} +ENV CRUPEST_ADDITIONAL_DOMAIN_LIST=${CRUPEST_ADDITIONAL_DOMAIN_LIST} +ENV CRUPEST_EMAIL=${CRUPEST_EMAIL} +ENV CRUPEST_AUTO_CERTBOT_POST_HOOK=${CRUPEST_AUTO_CERTBOT_POST_HOOK} +COPY daemon.bash /daemon.bash +COPY get-cert-domains.py /get-cert-domains.py +VOLUME ["/var/www/certbot", "/etc/letsencrypt", "/var/lib/letsencrypt"] +ENTRYPOINT ["tini", "--"] +CMD [ "/daemon.bash" ] diff --git a/docker/auto-certbot/daemon.bash b/docker/auto-certbot/daemon.bash new file mode 100755 index 0000000..d79387e --- /dev/null +++ b/docker/auto-certbot/daemon.bash @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +set -e + +# Check I'm root. +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root" 1>&2 + exit 1 +fi + +# Check certbot version. +certbot --version + +# Check domain +if [[ -z "$CRUPEST_DOMAIN" ]]; then + echo "CRUPEST_DOMAIN can't be empty!" 1>&2 + exit 1 +fi + +# Check email +if [[ -z "$CRUPEST_EMAIL" ]]; then + echo "CRUPEST_EMAIL can't be empty!" 1>&2 + exit 2 +fi + +# Check CRUPEST_CERT_PATH, default to /etc/letsencrypt/live/$CRUPEST_DOMAIN/fullchain.pem +if [ -z "$CRUPEST_CERT_PATH" ]; then + CRUPEST_CERT_PATH="/etc/letsencrypt/live/$CRUPEST_DOMAIN/fullchain.pem" +fi + +# Check CRUPEST_CERT_PATH exists. +if [ ! -f "$CRUPEST_CERT_PATH" ]; then + echo "Cert file does not exist. You may want to generate it manually with aio script." 1>&2 + exit 3 +fi + +echo "Root domain:" "$CRUPEST_DOMAIN" +echo "Email:" "$CRUPEST_EMAIL" +echo "Cert path: ${CRUPEST_CERT_PATH}" + +# Check CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is defined. +if [ -z "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND" ]; then + echo "CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is not defined or empty. Will use the default one." +else + printf "CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is defined as:\n%s\n" "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND" +fi + +domains_str="$(/get-cert-domains.py "${CRUPEST_CERT_PATH}")" + +printf "Domain list:\n%s\n" "$domains_str" + +mapfile -t domains <<< "$domains_str" + +for domain in "${domains[@]}"; do + domain_options=("${domain_options[@]}" -d "$domain") +done + +options=(-n --agree-tos -m "$CRUPEST_EMAIL" --webroot -w /var/www/certbot "${domain_options[@]}") +if [ -n "$CRUPEST_AUTO_CERTBOT_POST_HOOK" ]; then + printf "You have defined a post hook:\n%s\n" "$CRUPEST_AUTO_CERTBOT_POST_HOOK" + options=("${options[@]}" --post-hook "$CRUPEST_AUTO_CERTBOT_POST_HOOK") +fi + +# Use test server to test. +certbot certonly --force-renewal --test-cert --dry-run "${options[@]}" + +function check_and_renew_cert { + expire_info=$(openssl x509 -enddate -noout -in "$CRUPEST_CERT_PATH") + + # Get ssl certificate expire date. + expire_date=$(echo "$expire_info" | cut -d= -f2) + + echo "SSL certificate expire date: $expire_date" + + # Convert expire date to UNIX timestamp. + expire_timestamp="$(date -d "$expire_date" +%s)" + + # Minus expire timestamp with 30 days in UNIX timestamp. + renew_timestamp="$((expire_timestamp - 2592000))" + echo "Renew SSL certificate at: $(date -d @$renew_timestamp)" + + # Get rest time til renew. + rest_time_in_second="$((renew_timestamp - $(date +%s)))" + rest_time_in_day=$((rest_time_in_second / 86400)) + echo "Rest time til renew: $rest_time_in_second seconds, aka, about $rest_time_in_day days" + + # Do we have rest time? + if [ $rest_time_in_second -gt 0 ]; then + # Sleep 1 hour. + echo "I'm going to sleep for 1 day to check again." + sleep 1d + else + # No, renew now. + echo "Renewing now..." + + if [ -n "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND" ]; then + $CRUPEST_AUTO_CERTBOT_RENEW_COMMAND + else + certbot certonly "${options[@]}" + fi + fi +} + +# Run check_and_renew_cert in infinate loop. +while true; do + check_and_renew_cert +done diff --git a/docker/auto-certbot/get-cert-domains.py b/docker/auto-certbot/get-cert-domains.py new file mode 100755 index 0000000..9bd28c8 --- /dev/null +++ b/docker/auto-certbot/get-cert-domains.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import sys +import os +from os.path import * +from cryptography.x509 import * +from cryptography.x509.oid import ExtensionOID + +# Check only one argument +if len(sys.argv) != 2: + print("You should only specify one argument, aka, the path of cert.", + file=sys.stderr) + exit(1) + +cert_path = sys.argv[1] + +if not exists(cert_path): + print("Cert file does not exist.", file=sys.stderr) + exit(2) + +if not isfile(cert_path): + print("Cert path is not a file.") + exit(3) + +if not 'CRUPEST_DOMAIN' in os.environ: + print("Please set CRUPEST_DOMAIN environment variable to root domain.", file=sys.stderr) + exit(4) + +root_domain = os.environ['CRUPEST_DOMAIN'] + +with open(cert_path, 'rb') as f: + cert = load_pem_x509_certificate(f.read()) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME) + domains: list = ext.value.get_values_for_type(DNSName) + domains.remove(root_domain) + domains = [root_domain, *domains] + print('\n'.join(domains)) |