The wrapper getcerts.sh
is an all-in-one solution to create private keys,
generate a signing request (csr) and obtain certificates from Let's Encrypt
for multiple domains. And, of course, subject alternative names are supported.
Furthermore, the script can install (new) certificates and restart the web server.
New certificates can be requested and installed without any downtime. The only downtime needed, is to activate the (new) certificates.
The wrapper getcerts.sh
is based upon the excellent work done by Daniel Roesler and Jon Lundi:
An option, --staging
, to use Let's Encrypt's staging directory has been added
to acme-tiny.py
.
The package getcerts.sh
expects the following directory structure:
ACMEHOME
|-- bin
| |-- acme-tiny.py
| |-- conv.py
| `-- getcerts
|-- certs
| |-- <domain>.crt
| |-- <domain>.csr
| `-- lets-encrypt-x1-cross-signed-0001.crt
|-- doxygen
| |-- Doxyfile
| `-- doxygen-bash.sed
|-- etc
| |-- domain.txt
| |-- <domain>-san.txt
| `-- openssl.cnf
|-- keys
| |-- <domain>.key
| `-- letsencrypt-account.key
`-- lib
|-- certificate.sh
|-- csr.sh
`-- misc.sh
Notes:
- domain.txt contains all top level domains to create keys, signing requests and certificates for.
- a file named <domain>.* has to be named after the top level domain being processed.
- file <domain>-san.txt has to contain all subject alternative names, one per line, for the top level domain being processed.
- all *.csr, *.crt and *.key files are in PEM format.
- file letsencrypt-account.key contains the account key obtained from Let's Encrypt.
- doxygen contains the necessary information to create HTML documentation.
It is advised to create a single purpose user to install getcerts.sh
into,
for example acme
.
# create user 'acme'
sudo useradd -c 'ACME User' -m -d /home/acme acme
# if needed, set a strong password
sudo passwd acme
ACMEHOME
in this document refers to /home/acme
.
To install getcerts.sh
, download a release or clone from Github, extract
the contents in a directory and configure getcerts.sh
.
After installing getcerts.sh
, the following files, all relative to
ACMEHOME
, need to be configured:
-
etc/domain.txt
This file contains all the top level domains, each on a separate line, to get certificates for. Example: example.com. -
etc/<domain>-san.txt
This file contains all the subject alternative names for a single domain. The <domain>-part has to be replaced with the TLD. Example: example.com-san.txt. -
keys/letsencrypt-account.key
Let's Encrypt account key. -
/var/www/acme/.well-known/acme-challenge
Your web server has to be configured to provide a challenge directory for each and every website for which a certificate will be requested. -
etc/openssl.conf
In this file, at least the *_default items have to be adjusted:emailAddress_default = cert@example.com
0.organizationName_default = Example Organisation
localityName_default = City
stateOrProvinceName_default = Province/state
countryName_default = XX
Please leave
commonName_default
as is as it references a variable, using${ENV::CN}
, which will be set bygetcerts.sh
.
If a previous Let's Encrypt account key is present, after using certbot
, this
has to be converted into a PEM format. Perform these steps:
# Copy your private key to your working directory
cp /etc/letsencrypt/accounts/acme-v0[12].api.letsencrypt.org/directory/<id>/private_key.json private_key.json
# convert private key to asn1
python2 bin/conv.py private_key.json > private_key.asn1
# Create a DER encoded private key
openssl asn1parse -noout -out private_key.der -genconf private_key.asn1
# Convert to PEM
openssl rsa -in private_key.der -inform der -out keys/letsencrypt-account.key
# remove temporary information
rm private_key.asn1 private_key.der private_key.json
If a single purpose user, e.g. acme
, has been created, it needs a sudo entry
to be able to install the new certificates and to restart the web server.
Create a file in /etc/sudoers.d
, for example /etc/sudoers/acme
, with the
following (or similar) content:
acme ALL=(ALL) NOPASSWD: /home/acme/bin/getcerts.sh
And yes, this could be a security problem if certificate installation and web server restart is delegated to a non-sysadmin user or third party!
It's left as an exercise to the reader to provide a more secure solution.
In getcerts.sh
, the default values in function init() can be adjusted.
function init
{
local doConfigVerify=${1}
local ACMEHOME="${2:-/home/acme}"
# limit $PATH to get some basic security
export PATH=/usr/bin:/bin
# set up some basic settings
export MYID=$(id -u)
export BINDIR=${ACMEHOME}/bin
export LIBDIR=${ACMEHOME}/lib
export CERTDIR=${ACMEHOME}/certs
export KEYDIR=${ACMEHOME}/keys
export CHAINDIR=${ACMEHOME}/certs
export CONFIGDIR=${ACMEHOME}/etc
export ACMEKEY=${ACMEHOME}/keys/letsencrypt-account.key
export LOGDIR=${ACMEHOME}/log
# where are the 'official' certificates installed?
export SSLCERTDIR=/etc/pki/tls/certs
# where to put the challenge
export ACMEDIR=/var/www/acme/.well-known/acme-challenge/
# contact email
export EMAIL="mailto:certs@example.com"
# do some basic configuration checks
[[ ${doConfigVerify} -eq 1 ]] && {
verifyConfig "init" ||
usage 30 "configuration verification was unsuccessful, run ${PROGNAME} -C|--config for more information"
}
}
The variables should be self explanatory.
During the verification phase, getcerts.sh
places a token in the challenge
directory to verify if it has access to the web servers where certificates
are requested for. In order to be able to this, all virtual servers need to
have an entry to point to the challenge directory.
In a proxying web server, in this case apache on a Red Hat platform, this can be configured as follows:
<VirtualHost *:443>
ServerName www.example.com
Alias "/.well-known/" "/var/www/acme/.well-known/"
<Directory "/var/www/acme/.well-known/">
Require all granted
</Directory>
#
# rest of SSL configuration goes here...
#
# proxy forward
ProxyRequests Off
ProxyPreserveHost On
<proxy *>
AddDefaultCharSet off
Order deny,allow
Allow from all
</proxy>
# forward rules
<Location />
ProxyPass http://wwwbe.example.com:80/
ProxyPassReverse http://wwwbe.example.com:80/
</Location>
# do not proxy /.well-known/acme-challenge
<Location /.well-known/acme-challenge>
ProxyPass "!"
</Location>
</VirtualHost>
Of course, cron can be set up to run this command once per month. Put this in the crontab for user acme:
* * 1 * * getcerts.sh --auto-generate && sudo getcerts.sh --install-certificates
getcerts.sh -k
This will create a private 4096 bits RSA key which is stored in `keys' directory.
getcerts.sh -c
This will create a signing request (CSR) for all configured domains, which are stored
in certs
directory. The configured subject alternative names are set up in this
signing request.
getcerts.sh -g
This will request a new certificate from Let's Encrypt for each configured domain. These are in fact a chain which will be split up into separate certificates.
getcerts.sh -i
This will save the currently installed certificates and store the newly created
certificates into the configured system wide location. If all this was successful,
the web server will be restarted. Due to this, it might be essential to run this step
as root
.
Running getcerts.sh --help
as a regular user shows this information:
getcerts.sh: usage: getcerts.sh [-h|-u]
or: getcerts.sh [-C]
or: getcerts.sh [-D <tld>] [-F] [-H <home>] [-I] [-m <days>] [-S] [-v] -L|-V|-a|-c|-d|-f|-g|-i|-k|-l|-s]
options:
-C,--config - do some basic configuration verification
-D,--domain <tld> - top level domain to use, default: edgar-matzinger.nl
-F,--force - force the request of a new certificate
-H,--home <home> - home to use, default: /home/acme
-I,--info - show informatiopn about installed certificates
-L,--list-csr - list certificate signing request
-S,--staging - use Let's Encrypt staging directory
-V,--verify-csr - verify the certificate signing request
-a,--auto-generate - automagically create a new CSR and request new certificate
-c,--create-csr - create new certificate signing request
-d,--list-domains - list domains
-f,--force - force the request of a new certificate
-g,--get-certificates - get new certificates
-h,--help - show this information
-i,--install-certificates - install (new) certificates
-k,--create-key - create a domain key
-l,--list-certificates - list current certificates (default)
-m,--min-days-left <days> - get a new certificate if validity current one has less than <days> left, default: 30
-s,--list-sans - list subject alternative names
-u,--usage - show some brief usage information
-v,--verbose - be more verbose
--version - show version
options can be specified in any order
Running getcerts.sh --help
as 'root' shows this information:
getcerts.sh: usage: getcerts.sh [-h|-u]
or: getcerts.sh [-C]
or: getcerts.sh [-H <home>] [-i] [-v]
options:
-C,--config - do some basic configuration verification
-H,--home <home> - home to use, default: /home/acme
-h,--help - show this information
-i,--install-certificates - install (new) certificates
-u,--usage - show some brief usage information
-v,--verbose - be more verbose
--version - show version
options can be specified in any order
The easiest way to get new certificates from Let's Encrypt is to run:
getcerts.sh --auto-generate && sudo getcerts.sh --install-certificates