2

I am currently trying to set up a hook for certificate deployment. I have successfully retrieved the certificate and found out that you have to force renewal when testing, so I've now hit the rate limit (grumble grumble). So far I haven't been able to figure out why CERTBOT_DOMAIN appears not to be set when the hook is called.

Renewal was a success and Certbot prints out that it is running the hook: Running post-hook command: /etc/letsencrypt/renewal-hooks/post/cert-upload.sh

Here is the script:

#!/bin/bash
echo "DOMAIN IS $CERTBOT_DOMAIN"

It prints out DOMAIN IS

The docs seem to indicate this is the right variable name; what am I missing here?

3

I never got the hooks to work. Instead I run the certbot renewal and then test if the latest certs are currently installed by downloading the cert from the live site and comparing it to the version locally stored by certbot.

At first I was annoyed by this, but it turns out that this is a superior approach anyway.

  • In some cases the cert can renew, but the deployment can fail due to a temporary error such as a failed network connection. When the you rely on the certbot hooks, the deployment won't run again automatically until next time the cert is renewed. If you test every day if the latest certs are installed (whether or not anything actually got renewed) a failed deployment is automatically retried the next day.
  • It decouples the deployment from the renewal. I've never had the problem of hitting the rate limit when testing. I can run my deployment scripts independently of running certbot. At most, I would run certbot twice and save two valid certificates so I can swap them to test the script that checks that the latest cert is live.
  • If you need to deploy the same certificate to multiple servers, it makes it easy to add an additional server. You add a check for the domain or subdomain of that server and re-run the deployment. It won't re-run certbot, and it will only deploy the certificate to the server that needs it. In other words, this check makes your deployment script flexible and idempotent.

I have a script I named lets-encrypt-installed-test.sh that does that job. It can be used with one or two arguments:

# Compare cert downloaded from https://example.com/
# with /etc/letsencrypt/live/example.com/cert.pem
if ! lets-encrypt-installed-test.sh --quiet example.com
then
  # Need to install cert on example.com
fi

# Compare cert downloaded from https://sub.example.com/
# with /etc/letsencrypt/live/example.com/cert.pem
if ! lets-encrypt-installed-test.sh --quiet sub.example.com example.com
then
  # Need to install cert on sub.example.com
fi

The two argument version is most useful when you deploying one wildcard or subject-alternative-name (SAN) certificate to multiple domains or subdomains.

lets-encrypt-installed-test.sh:

#!/bin/bash

set -e

quiet=0
domain=
ledomain=

for var in "$@"
do
    case "$var" in
        -q)
            quiet=1
            ;;
        --quiet)
            quiet=1
            ;;
        -*)
            echo "Unexpected argument $var" >&2
            exit 1
            ;;
        *.*)
            if [ "z$domain" == "z" ]
            then
                domain="$var"
                ledomain="$var"
            else
                ledomain="$var"
            fi
            ;;
        *)
            echo "Expected argument $var" >&2
            exit 1
            ;;
    esac
done

if [ "z$domain" == "z" ]
then
    echo "Expected domain as parameter to $0" >&2
    exit 1
fi

lefile="/etc/letsencrypt/live/$ledomain/cert.pem"

if [ ! -e $lefile ]
then
    echo "Lets Encrypt file does not exist: $lefile" >&2
    exit 1
fi

leprint=`openssl x509 -in $lefile -fingerprint -noout`

case "$leprint" in
    *Fingerprint*)
        ;;
    *)
        echo "No fingerprint from $lefile" >&2
        exit 1
        ;;
esac

liveprint=`echo | openssl s_client -showcerts -connect "$domain":443 -servername "$domain" 2>&1 | openssl x509 -fingerprint | grep -i fingerprint`

case "$liveprint" in
    *Fingerprint*)
        ;;
    *)
        echo "No fingerprint from SSL cert of https://$domain/" >&2
        exit 1
        ;;
esac

if [ "$leprint" != "$liveprint" ]
then
    if [ "$quiet" == "0" ]
    then
        echo "Fingerprints for local and remote SSL certificates differ:" >&2
        echo "$lefile: $leprint" >&2
        echo "https://$domain/: $liveprint" >&2
    fi
    exit 1
fi

exit 0

5
  • You're totally right that this is a better approach. Thank you! – jack Feb 20 at 1:29
  • For anybody trying to adapt this in the future: openssl kept crashing until I removed the quotes around $domain, and the 'echo' in front of the openssl invocation is important since otherwise openssl won't exit – jack Feb 20 at 20:25
  • I have sh provided by /bin/bash on my system. My guess is that quoting part of an argument like "$domain":443 is a bashism and other shells like dash don't support that. I've edited this answer to specify that bash should be used. – Stephen Ostermiller Feb 21 at 11:46
  • Pretty sure I'm using bash. Does the system ever ignore the #! directive? Ubuntu 18.04 if that matters. – jack Feb 21 at 15:10
  • 1
    Ubuntu now uses dash instead of bash by default. If you specify #!/bin/bash then bash will be used, but if you just use #!/bin/sh the default shell will be used. Before my edit, I hadn't specified bash. I use Ubuntu, but I have so many scripts that I've written from when the default sh wasbash that I now have to change the default to bash on my machine. Coding cross shell compatible scripts is just about as hard as coding cross browser compatible websites. – Stephen Ostermiller Feb 21 at 17:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.