There is a bug, or misfeature, in acme.sh using dns manual mode where
it will not renew the certificate when new domains are added to an
existing certificate. It appears to generate the TXT record requests
correctly, but then when we renew the certificate it thinks it is not
time and skips it. This is filed upstream with [1] however we can
work around it, and generally be better anyway.
For each letsencrypt host, during certificate request we build up the
"acme_txt_required" key which is a list of TXT record tuples.
Currently we keep the challenge domain in the first entry, which is
not useful (all our hosts have the same challenge domain,
amce.opendev.org). Modify this to be the certificate key from the
host config. To be clear; when a host has
letsencrypt_certs:
hostname-cert-main:
hostname.opendev.org
altname.opendev.org
hostname-cert-secondary:
secondary.opendev.org
secondaryalt.opendev.org
acme_txt_required when renewing all certs will end up looking like:
[
(hostname-cert-main, <txt1>), (hostname-cert-main, <txt2>),
(hostname-cert-secondary, <txt3>), (hostname-cert-secondary, <txt3>>)
]
In the certificate creation path, we walk "acme_txt_required" and take
the unique 0-value entries; this gives us the list of keys in
"letsencrypt_certs" which were actually updated.
We can then force renewal for these certs, because we know they
changed in some way that requires reissuing them (within renewal time,
or new domains).
This isn't just a work-around, it is generically better too.
Previously if any cert on host required an update, we would try to
update them all. This would be a no-op; acme.sh would just skip doing
anything; but now we don't even have to call into the renewal if we
know nothing has changed.
[1] https://github.com/acmesh-official/acme.sh/issues/2763
Change-Id: I1e82c64217d46d7e1acc0111dff4db2f0062c42a
Request certificates from letsencrypt
The role requests certificates (or renews expiring certificates,
which is fundamentally the same thing) from letsencrypt for a host. This
requires the acme.sh tool and driver which should have been
installed by the letsencrypt-acme-sh-install role.
This role does not create the certificates. It will request the
certificates from letsencrypt and populate the authentication data into
the acme_txt_required variable. These values need to be
installed and activated on the DNS server by the
letsencrypt-install-txt-record role; the
letsencrypt-create-certs will then finish the certificate
provision process.
Role Variables
If set to True will use the letsencrypt staging environment, rather than make production requests. Useful during initial provisioning of hosts to avoid affecting production quotas.
A host wanting a certificate should define a dictionary variable
letsencyrpt_certs. Each key in this dictionary is a separate certificate to create (i.e. a host can create multiple separate certificates). Each key should have a list of hostnames valid for that certificate. The certificate will be named for the first entry.For example:
letsencrypt_certs: hostname-main-cert: - hostname01.opendev.org - hostname.opendev.org hostname-secondary-cert: - foo.opendev.orgwill ultimately result in two certificates being provisioned on the host in
/etc/letsencrypt-certs/hostname01.opendev.organd/etc/letsencrypt-certs/foo.opendev.org.Note the creation role
letsencrypt-create-certswill call a handlerletsencrypt updated {{ key }}(for example,letsencrypt updated hostname-main-cert) when that certificate is created or updated. Because Ansible errors if a handler is called with no listeners, you must define a listener for event.letsencrypt-create-certshashandlers/main.yamlwhere handlers can be defined. Since handlers reside in a global namespace, you should choose an appropriately unique name.Note that each entry will require a
CNAMEpointing the ACME challenge domain to the TXT record that will be created in the signing domain. For example above, the following records would need to be pre-created:_acme-challenge.hostname01.opendev.org. IN CNAME acme.opendev.org. _acme-challenge.hostname.opendev.org. IN CNAME acme.opendev.org. _acme-challenge.foo.opendev.org. IN CNAME acme.opendev.org.