[sf-lug] fun with letsencrypt.org & scripts :-)

Michael Paoli Michael.Paoli at cal.berkeley.edu
Wed Aug 17 02:50:54 PDT 2016


So ... fun with letsencrypt.org & scripts :-)

So, I have this little reminder on my calendar:
2016-08-19 2016-08-20T01:30:00+0000 BALUG/SF-LUG letsencrypt.org SSL  
cert expires for host(s):  
www.balug.org,www.sf-lug.org,www.ipv6.balug.org,www.ipv6.sf-lug.org,balug.org,sf-lug.org,archive.balug.org,www.archive.balug.org,beta.balug.org,www.beta.balug.org,ipv6.balug.org,new.balug.org,lists.new.balug.org,www.new.balug.org,secure.balug.org,www.secure.balug.org,test.balug.org,php.test.balug.org,www.php.test.balug.org,www.test.balug.org,wiki.balug.org,www.wiki.balug.org,ipv6.sf-lug.org,sf-lug.com,ipv6.sf-lug.com,www.ipv6.sf-lug.com,www.sf-lug.com,sf-lug.info,ipv6.sf-lug.info,www.ipv6.sf-lug.info,www.sf-lug.info

So, about time to renew!!!

For various reasons, I've not turned the letsencrypt software loose to
run and automatically do the renewals for me - at least not yet on the
production hosts I support.  Why?  Well, most notably, for
production/stable distribution I'm running, the letsencrypt software
isn't (yet) in that release.  So ... I run the letsencrypt software,
under an unprivileged user ID, separately on a non-production host, and
using the "manual" mode of the letsencrypt software.  But I've got some
nice wrapper scripts I use.  Could probably use those to then be driven
by expect(1) or the like (e.g. Expect(3pm)) - but haven't taken it that
far ... yet.

What's an example run look like presently?

About like this - I run the top-level wrapper script:
$ Getcerts  
www.balug.org,www.sf-lug.org,www.ipv6.balug.org,www.ipv6.sf-lug.org,balug.org,sf-lug.org,archive.balug.org,www.archive.balug.org,beta.balug.org,www.beta.balug.org,ipv6.balug.org,new.balug.org,lists.new.balug.org,www.new.balug.org,secure.balug.org,www.secure.balug.org,test.balug.org,php.test.balug.org,www.php.test.balug.org,www.test.balug.org,wiki.balug.org,www.wiki.balug.org,ipv6.sf-lug.org,sf-lug.com,ipv6.sf-lug.com,www.ipv6.sf-lug.com,www.sf-lug.com,sf-lug.info,ipv6.sf-lug.info,www.ipv6.sf-lug.info,www.sf-lug.info

That then has the manual mode of the letsencrypt software issuing me
some challenges, e.g.:

Make sure your web server displays the following content at
http://www.balug.org/.well-known/acme-challenge/_Rb7f4gCXVOaQ0hqB3pReFlrB5_RlGcPDGW48iEei08 before  
continuing:

_Rb7f4gCXVOaQ0hqB3pReFlrB5_RlGcPDGW48iEei08.uBv74F95SBTHmImLwOI3zhA3qsAKdz1R87ZIF9sMkM4

On the web server - from the appropriate directory just above the
DocumentRoot directories for the sites, I then do:
# (set --  
http://www.balug.org/.well-known/acme-challenge/_Rb7f4gCXVOaQ0hqB3pReFlrB5_RlGcPDGW48iEei08 _Rb7f4gCXVOaQ0hqB3pReFlrB5_RlGcPDGW48iEei08.uBv74F95SBTHmImLwOI3zhA3qsAKdz1R87ZIF9sMkM4; umask 022 && path=$(echo "$1" | sed -e 's/^http:\/\///') && dpath=$(dirname "$path") && fpath=$(basename "$path") && mkdir -p "$dpath" && echo "$2" > "$path" && wget -q -O - http://"$path" | cmp - <(echo "$2") && echo  
OK)
OK
For some sites, I need to run an extra command to sync out to (all of)
the production site(s), e.g.:
$ www.balug.org_rsync
Then I repeat the earlier command above, to confirm the challenge
response is successfully in place (if I wanted to be more thorough, I
could adapt and write something that would check and ensure it was
present on all sites).

Once that check is passed, to the letsencrypt software prompt:
Press ENTER to continue
I press ENTER, and it does its validation checks.
I then just repeat that for each challenge - doing a simple but careful
copy-paste of the challenge data into the one-liner script I use (with
command line editing), repeating and stepping through each until it's
all been completed.  It's nice now that letsencrypt now also validates
sites that are IPv6 only - it didn't some few months or a bit more ago.
It does still, however, throw a spurious error in such cases, but it
continues on successfully from there anyway.

Once it's completed, it outputs something like:
...
IMPORTANT NOTES:
  - Congratulations! Your certificate and chain have been saved at
    /home/test/0005_chain.pem
...
I then do some sanity checking of the cert:
$ sed -ne '/^-----BEGIN CERTIFICATE-----$/,/^-----END  
CERTIFICATE-----$/p;/^-----END CERTIFICATE-----$/q'  
/home/test/0005_chain.pem | openssl x509 -text -noout 2>&1
...
         Validity
...
             Not After : Nov 15 06:30:00 2016 GMT
...
         Subject: CN=www.balug.org
...
             X509v3 Subject Alternative Name:
                 DNS:archive.balug.org, DNS:balug.org,  
DNS:beta.balug.org, DNS:ipv6.balug.org, DNS:ipv6.sf-lug.com,  
DNS:ipv6.sf-lug.info, DNS:ipv6.sf-lug.org, DNS:lists.new.balug.org,  
DNS:new.balug.org, DNS:php.test.balug.org, DNS:secure.balug.org,  
DNS:sf-lug.com, DNS:sf-lug.info, DNS:sf-lug.org, DNS:test.balug.org,  
DNS:wiki.balug.org, DNS:www.archive.balug.org, DNS:www.balug.org,  
DNS:www.beta.balug.org, DNS:www.ipv6.balug.org,  
DNS:www.ipv6.sf-lug.com, DNS:www.ipv6.sf-lug.info,  
DNS:www.ipv6.sf-lug.org, DNS:www.new.balug.org,  
DNS:www.php.test.balug.org, DNS:www.secure.balug.org,  
DNS:www.sf-lug.com, DNS:www.sf-lug.info, DNS:www.sf-lug.org,  
DNS:www.test.balug.org, DNS:www.wiki.balug.org
...

After that, I copy the new key, cert, and chains to the web server,
following the letsencrypt convention on locations (so I can eventually
cut it over to a fully automatic operation), update the (symbolic)
links, reload Apache, and the new certs are then fully operational.

So, what's the Getcerts program look like?  Well, that's the high(er)
level ... it does also have a lower level (more like mid-level) program
which it in turn also uses.  That and some letsencrypt stuff configured
for the ID that I use to run that software.

Anyway, those programs look like:
... oh, and one minor caution - some of the code comments aren't quite
caught up to reality yet:

$ expand -t 4 < Getcerts
#!/bin/bash

# Take arguments(s) to be sets of cert names,
# each set comma (,) separated set of one or more cert names.
# For each set, generate cert, if set has multiple comma (,) separated
# names, use first for CN, and Subject Alternative Name (SAN) for all in
# the set

umask 077 || exit
LC_ALL=C export LC_ALL
rc=0

[ "$#" -ge 1 ] || {
     1>&2 echo "usage $0: certset [ certset ... ]"
     exit 1
}

for certset
do
     case "$certset" in
         ''|,*|*,|*,,*)
             1>&2 echo "$0: bad certset: null/empty elements not allowed"
             rc=1
             continue
         ;;
     esac
     CN=$(
         set -- $(echo "$certset" | tr ',' ' ')
         echo "$1"
     )
     SAN=$(
         SAN=
         set -- $(echo "$certset" | tr ',' ' ')
         if [ "$#" -ge 2 ]; then
             while [ "$#" -ge 1 ]
             do
                 SAN="$SAN${SAN:+,}DNS:$1"
                 shift
             done
             echo 'subjectAltName='"$SAN"
         else
             :
         fi
     )
     keyfile=$(
         i=$(
             cd "$HOME"/etc/letsencrypt/keys &&
             ls -d [0-9][0-9][0-9][0-9]_key-letsencrypt.pem |
             sed -ne '$s/^\([0-9]\{4\}\)_.*$/\1/p'
         )
         case x"$i" in
             x[0-9][0-9][0-9][0-9])
                 i=$(expr 1 + "$i")
                 i=$(printf '%04d' "$i")
             ;;
             x)
                 i=0000
             ;;
             *)
                 1>&2 echo "$0: failed to determine index for key file"
                 exit 1
             ;;
         esac
         echo "$HOME/etc/letsencrypt/keys/$i"_key-letsencrypt.pem
     )

     csrfile=$(
         i=$(
             cd "$HOME"/etc/letsencrypt/csr &&
             ls -d [0-9][0-9][0-9][0-9]_csr-letsencrypt.pem |
             sed -ne '$s/^\([0-9]\{4\}\)_.*$/\1/p'
         )
         case x"$i" in
             x[0-9][0-9][0-9][0-9])
                 i=$(expr 1 + "$i")
                 i=$(printf '%04d' "$i")
             ;;
             x)
                 i=0000
             ;;
             *)
                 1>&2 echo "$0: failed to determine index for csr file"
                 exit 1
             ;;
         esac
         echo "$HOME/etc/letsencrypt/csr/$i"_csr-letsencrypt.pem
     )

     set -o noclobber
     >"$keyfile" || {
         set +o noclobber
         rc=1
         continue
     }
     set +o noclobber

     set -o noclobber
     >"$csrfile" || {
         set +o noclobber
         rc=1
         continue
     }
     set +o noclobber

     echo "CN=$CN"
     if [ -n "$SAN" ]; then
         echo "SAN=$SAN" ]
     else
         :
     fi
     echo "keyfile=$keyfile"

     tmpcsrdirfile=$(mktemp) &&
     openssl req \
         -new -newkey rsa:4096 -sha256 -nodes \
         -keyout "$keyfile" -out "$tmpcsrdirfile" -outform der \
         -subj "/CN=$CN" \
         ${SAN:+-reqexts SAN } \
         -config <(
             cat /etc/ssl/openssl.cnf
             if [ -n "$SAN" ]; then
                 echo
                 echo '[SAN]'
                 echo "$SAN"
             else
                 :
             fi
         ) &&
     openssl req -inform DER -in "$tmpcsrdirfile" -outform PEM -out \
         "$csrfile" &&
     myletsencrypt --csr "$csrfile"
     rm "$tmpcsrdirfile"
done
exit "$rc"
$



$ expand -t 4 < myletsencrypt
#!/bin/sh
letsencrypt certonly \
     --agree-tos \
     --config "$HOME"/etc/letsencrypt/cli.ini \
     --config-dir "$HOME"/etc/letsencrypt \
     --duplicate \
     --email Michael.Paoli at cal.berkeley.edu \
     --logs-dir "$HOME"/var/log/letsencrypt \
     --manual \
     --manual-public-ip-logging-ok \
     --rsa-key-size 4096 \
     --text \
     --work-dir "$HOME"/var/lib/letsencrypt ${1+"$@"}

     # [ --account ACCOUNT_ID ]
     # --webroot
     # --webroot-path WEBROOT_PATH --domain domain ...

# note, last -d (domain) given will be CN, additional preceding that
# will be added to Subject Alternative Name (SAN)
$

So ... I make another update note on my calendar:
2016-11-14 2016-11-15T06:30:00+0000 BALUG/SF-LUG letsencrypt.org SSL  
cert expires for host(s):  
www.balug.org,www.sf-lug.org,www.ipv6.balug.org,www.ipv6.sf-lug.org,balug.org,sf-lug.org,archive.balug.org,www.archive.balug.org,beta.balug.org,www.beta.balug.org,ipv6.balug.org,new.balug.org,lists.new.balug.org,www.new.balug.org,secure.balug.org,www.secure.balug.org,test.balug.org,php.test.balug.org,www.php.test.balug.org,www.test.balug.org,wiki.balug.org,www.wiki.balug.org,ipv6.sf-lug.org,sf-lug.com,ipv6.sf-lug.com,www.ipv6.sf-lug.com,www.sf-lug.com,sf-lug.info,ipv6.sf-lug.info,www.ipv6.sf-lug.info,www.sf-lug.info - note also sf-lug.info domain will be moot by then: sf-lug.info domain expires  
2016-09-28T20:01:47Z





More information about the sf-lug mailing list