THIS ROLE IS DEPRECATED! PLEASE USE THE COLLECTION VERSION felixfontein.acme (https://github.com/felixfontein/ansible-acme/) INSTEAD!
Allows to obtain certificates from Let's Encrypt with minimal interaction with the webserver. Most code is executed on the controller, and the account key is never send to the nodes.
The role can be installed via Ansible Galaxy:
ansible-galaxy install felixfontein.acme_certificate
For changes in this role, see the changelog.
This is an Ansible role which can use any CA supporting the ACME protocol, such as Let's Encrypt or Buypass, to issue TLS/SSL certificates for your server. This role requires Ansible 2.8.3 or newer and is based on the acme_certificate module coming with Ansible.
The main advantage of this approach over others is that almost no code is executed on your webserver: only when you use HTTP challenges, files need to be copied onto your webserver, and afterwards deleted from it. Everything else is executed on your local machine!
(This does not cover installing the certificates, you have to do that yourself in another role.)
Requires the Python cryptography library installed on the controller, available to the Python version used to execute the playbook. If cryptography
is not installed, a recent enough version of PyOpenSSL is currently supported as a fallback by the Ansible openssl_privatekey
and openssl_csr
modules.
The openssl
binary must also be available in the executable path on the controller. It is needed by the acme_certificate
module in case cryptography
is not installed, and it is used for certificate chain validation.
If DNS challenges are used, there can be other requirements depending on the DNS provider. For example, for Amazon's Route 53, the Ansible route53
module requires the Python boto
package.
You can create an account key using the openssl
binary as follows:
# RSA 4096 bit key
openssl genrsa 4096 -out keys/acme-account.key
# ECC 256 bit key (P-256)
openssl ecparam -name prime256v1 -genkey -out keys/acme-account.key
# ECC 384 bit key (P-384)
openssl ecparam -name secp384r1 -genkey -out keys/acme-account.key
With Ansible, you can use the openssl_privatekey
module as follows:
- name: Generate RSA 4096 key
openssl_privatekey:
path: keys/acme-account.key
type: RSA
size: 4096
- name: Generate ECC 256 bit key (P-256)
openssl_privatekey:
path: keys/acme-account.key
type: ECC
curve: secp256r1
- name: Generate ECC 384 bit key (P-384)
openssl_privatekey:
path: keys/acme-account.key
type: ECC
curve: secp384r1
Make sure you store the account key safely. As opposed to certificate private keys, there is no need to regenerate it frequently, and it makes recovation of certificates issued with it very simple.
Please note that from May 2020 on, all variables must be prefixed with acme_certificate_
. For some time, the module will still use the old (short) variable names if the longer ones are not defined. Please upgrade your role usage as soon as possible.
These are the main variables:
acme_certificate_acme_account
: Path to the private ACME account key. Must always be specified.acme_certificate_acme_email
: Your email address which shall be associated to the ACME account. Must always be specified.acme_certificate_algorithm
: The algorithm used for creating private keys. The default is"rsa"
; other choices are"p-256"
,"p-384"
or"p-521"
for the NIST elliptic curvesprime256v1
,secp384r1
andsecp521r1
, respectively.acme_certificate_key_length
: The bitlength to use for RSA private keys. The default is 4096.acme_certificate_key_name
: The basename for storing the keys and certificates. The default is the first domain specified, with*
replaced by_
.acme_certificate_keys_path
: Where the keys and certificates are stored. Default value is"keys/"
.acme_certificate_keys_old_path
: Where old keys and certificates should be copied to; used in caseacme_certificate_keys_old_store
is true. Default value is"keys/old/"
.acme_certificate_keys_old_store
: If set totrue
, will make copies of old keys and certificates. The copies will be stored in the directory specified byacme_certificate_keys_old_store
. Default value isfalse
.acme_certificate_keys_old_prepend_timestamp
: Whether copies of old keys and certificates should be prepended by the current date and time. Default value isfalse
.acme_certificate_ocsp_must_staple
: Whether a certificate with the OCSP Must Staple extension is requested. Default value isfalse
.acme_certificate_agreement
: The terms of service document the user agrees to. Default value ishttps://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
.acme_certificate_acme_directory
: The ACME directory to use. Default ishttps://acme-v02.api.letsencrypt.org/directory
, which is the current production ACME v2 endpoint of Let's Encrypt.acme_certificate_acme_version
: The ACME directory's version. Default is 2. Use 1 for ACME v1.acme_certificate_challenge
: The challenge type to use. Should behttp-01
for HTTP challenges (needs access to web server) ordns-01
for DNS challenges (needs access to DNS provider).acme_certificate_root_certificate
: The root certificate for the ACME directory. Default value ishttps://letsencrypt.org/certs/isrgrootx1.pem
for the root certificate of Let's Encrypt.acme_certificate_deactivate_authzs
: Whetherauthz
s (authorizations) should be deactivated afterwards. Default value istrue
. Set tofalse
to be able to re-useauthz
s.acme_certificate_modify_account
: Whether the ACME account should be created (if it doesn't exist) and the contact data (email address) should be updated. Default value istrue
. Set tofalse
if you want to use theacme_account
module to manage your ACME account (not done by this role).acme_certificate_privatekey_mode
: Which file mode to use for the private key file. Default value is"0600"
, which means read- and writeable by the owner, but not accessible by anyone else (except possiblyroot
).acme_certificate_select_chain
: (Only usable with Ansible 2.10+) Must be in the format described here. Allows to select the certificate chain to be used;acme_certificate_root_certificate
must be used in conjunction. This can be used for example with Let's Encrypt to select which root certificate to use. The following configuration makes sure that the IdenTrust cross-signed intermediate is used, which is more compatible for example for older Android versions than the new IRSG root:The following configuration selects the new IRSG X1 root:acme_certificate_root_certificate: https://letsencrypt.org/certs/trustid-x3-root.pem.txt acme_certificate_select_chain: - test_certificates: last issuer: CN: DST Root CA X3 O: Digital Signature Trust Co.
acme_certificate_root_certificate: https://letsencrypt.org/certs/isrgrootx1.pem acme_certificate_select_chain: - test_certificates: last issuer: CN: ISRG Root X1 O: Internet Security Research Group
For HTTP challenges, the following variables define how the challenges can be put onto the (remote) webserver:
acme_certificate_server_location
: Location where.well-known/acme-challenge/
will be served from. Default is/var/www/challenges
.acme_certificate_http_become
: Argument forbecome:
for thefile
andcopy
tasks. Default value isfalse
.acme_certificate_http_challenge_user
: The user the challenge files are owned by. Default value isroot
.acme_certificate_http_challenge_group
: The group the challenge files are owned by. Default value ishttp
.acme_certificate_http_challenge_folder_mode
: The mode to use for the challenge folder. Default value is0750
(octal).acme_certificate_http_challenge_file_mode
: The mode to use for the challenge files. Default value is0640
(octal).
The following subsection shows how to configure nginx for HTTP challenges. Configuring other webservers can be done in a similar way.
Assume that for one of your TLS/SSL protected domains, you use a HTTP-to-HTTPS redirect. Let's assume it looks like this:
server {
listen example.com:80;
server_name example.com *.example.com;
return 301 https://www.example.com$request_uri;
}
To allow the acme_certificate
role to put something at http://*.example.com/.well-known/acme-challenge/
, you can change this to:
server {
listen example.com:80;
server_name example.com *.example.com;
location /.well-known/acme-challenge/ {
alias /var/www/challenges/;
try_files $uri =404;
}
location / {
return 301 https://www.example.com$request_uri;
}
}
With this nginx config, all other URLs on *.example.com
and example.com
are still redirected, while everything in *.example.com/.well-known/acme-challenge/
is served from /var/www/challenges
. When adjusting the location of /var/www/challenges
, you must also change acme_certificate_server_location
.
You can even improve on this by redirecting all URLs in *.example.com/.well-known/acme-challenge/
which do not resolve to a valid file in /var/www/challenges
to your HTTPS server as well. One way to do this is:
server {
listen example.com:80;
server_name example.com *.example.com;
location /.well-known/acme-challenge/ {
alias /var/www/challenges/;
try_files $uri @forward_https;
}
location @forward_https {
return 301 https://www.example.com$request_uri;
}
location / {
return 301 https://www.example.com$request_uri;
}
}
With this config, if /var/www/challenges/
is empty, your HTTP server will behave as if the /.well-known/acme-challenge/
location isn't specified.
If DNS challenges are used, the following variables define how the challenges can be fulfilled:
-
acme_certificate_dns_provider
: must be one ofroute53
,hosttech
, andns1
. Each needs more information:- For
route53
(Amazon Route 53), the credentials must be passed asacme_certificate_aws_access_key
andacme_certificate_aws_secret_key
. - For
hosttech
(hosttech GmbH, requires external hosttech_dns_record module). - For
ns1
(ns1.com) the key for your API account must be passed asacme_certificate_ns1_secret_key
. Also it depends on external modulens1_record
. Assuming default directory structure and settings, you may need download 2 files into machine where playbook executed:
curl --create-dirs -L -o ~/.ansible/plugins/module_utils/ns1.py https://github.com/ns1/ns1-ansible-modules/raw/master/module_utils/ns1.py curl --create-dirs -L -o ~/.ansible/plugins/modules/ns1_record.py https://github.com/ns1/ns1-ansible-modules/raw/master/library/ns1_record.py
- For
Please note that the DNS challenge code is not perfect. The Route 53, Hosttech and NS1 functionality has been tested. One thing that is not complete yet is that the code tries to extract the DNS zone from the domain by taking the last two components separated by dots. This will fail for example for .co.uk
domains or other nested zones.
Support for more DNS providers can be added by adding tasks/dns-NAME-create.yml
and tasks/dns-NAME-cleanup.yml
files with similar content as in the existing files.
Note that this Ansible role expects the Let's Encrypt account key to be in PEM format and not in JWK format, which is used by the official Let's Encrypt client certbot. If you have created an account key with the official client and now want to use this key with this ansible role, you have to convert it. One tool which can do this is pem-jwk.
Let's assume you created TLS keys for www.example.com
. You have to copy the relevant files to your webserver. The ansible role created the following files:
keys/www.example.com.key
: this is the private key for the certificate. Ensure nobody can access it.keys/www.example.com.pem
: this is the certificate itself.keys/www.example.com-chain.pem
: this is the intermediate certificate(s) needed for a trust path.keys/www.example.com.cnf
: this is an OpenSSL configuration file used to create the Certificate Signing Request. You can safely delete it.keys/www.example.com.csr
: this is the Certificate Signing Request used to obtain the certificate. You can safely delete it.keys/www.example.com-fullchain.pem
: this is the certificate combined with the intermediate certificate(s).keys/www.example.com-rootchain.pem
: this is the intermediate certificate(s) combined with the root certificate. You might need this for OCSP stapling.keys/www.example.com-root.pem
: this is the root certificate of Let's Encrypt.
For configuring your webserver, you need the private key (keys/www.example.com.key
), and either the certificate with intermediate certificate(s) combined in one file (keys/www.example.com-fullchain.pem
), or the certificate and the intermediate certificate(s) as two separate files (keys/www.example.com.pem
and keys/www.example.com-chain.pem
). If you want to use OCSP stapling, you will also need keys/www.example.com-rootchain.pem
.
To get these files onto your web server, you could add tasks as follows:
- name: copy private keys
copy:
src: keys/{{ item }}
dest: /etc/ssl/private/
owner: root
group: root
mode: "0400"
with_items:
- www.example.com.key
notify: reload webserver
- name: copy certificates
copy:
src: keys/{{ item }}
dest: /etc/ssl/server-certs/
owner: root
group: root
mode: "0444"
with_items:
- www.example.com-rootchain.pem
- www.example.com-fullchain.pem
- www.example.com.pem
notify: reload webserver
The webserver configuration could look as follows (for nginx):
server {
listen www.example.com:443 ssl; # IPv4: listen to IP www.example.com points to
listen [::]:443 ssl; # IPv6: listen to localhost
server_name www.example.com;
# Allowing only TLS 1.0 and 1.2, with a very selective amount of ciphers.
# According to SSL Lab's SSL server test, this will block:
# - Android 2.3.7
# - IE 6 and 8 under Windows XP
# - Java 6, 7 and 8
# If that's not acceptable for you, choose other cipher lists. Look for
# example at https://wiki.mozilla.org/Security/Server_Side_TLS
ssl_protocols TLSv1.2 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers "-ALL !ADH !aNULL !EXP !EXPORT40 !EXPORT56 !RC4 !3DES !eNULL !NULL !DES !MD5 !LOW ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA";
# The certificate chain sent to the browser, as well as the private key.
# Make sure your private key is only accessible by the webserver during
# configuration loading (which by default is done with user root).
ssl_certificate /etc/ssl/server-certs/www.example.com-fullchain.pem;
ssl_certificate_key /etc/ssl/private/www.example.com.key;
# For OCSP stapling, we need a DNS resolver. Here only public Quad9 and
# Google DNS servers are specified; I would prepent them by your hoster's
# DNS servers. You can usually find their IPs in /etc/resolv.conf on your
# webserver.
resolver 9.9.9.9 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
# Enabling OCSP stapling. Nginx will take care of retrieving the OCSP data
# automatically. See https://wiki.mozilla.org/Security/Server_Side_TLS#OCSP_Stapling
# for details on OCSP stapling.
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/server-certs/www.example.com-rootchain.pem;
# Enables a SSL session cache. Adjust the numbers depending on your site's usage.
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 30m;
ssl_session_tickets off;
# You should only use HSTS with proper certificates; the ones from Let's Encrypt
# are fine for this, self-signed ones are not. See MozillaWiki for more details:
# https://wiki.mozilla.org/Security/Server_Side_TLS#HSTS:_HTTP_Strict_Transport_Security
add_header Strict-Transport-Security "max-age=3155760000;";
charset utf-8;
access_log /var/log/nginx/www.example.com.log combined;
error_log /var/log/nginx/www.example.com.log error;
location / {
root /var/www/www.example.com;
index index.html;
}
}
This role doesn't depend on other roles.
This role can be used as follows. Note that it obtains several certificates, and defines variables used for all certificates globally:
---
- name: getting certificates for webserver
hosts: webserver
vars:
acme_certificate_acme_account: 'keys/acme-account.key'
acme_certificate_acme_email: 'mail@example.com'
# For HTTP challenges:
acme_certificate_server_location: '/var/www/challenges/'
acme_certificate_http_challenge_user: root
acme_certificate_http_challenge_group: http
acme_certificate_http_challenge_folder_mode: "0750"
acme_certificate_http_challenge_file_mode: "0640"
# For DNS challenges with route53:
acme_certificate_dns_provider: route53
acme_certificate_aws_access_key: REPLACE_WITH_YOUR_ACCESS_KEY
acme_certificate_aws_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
# For DNS challenges with ns1:
# acme_certificate_dns_provider: ns1
# acme_certificate_ns1_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
roles:
- role: acme_certificate
acme_certificate_domains: ['example.com', 'www.example.com']
# Use DNS challenges:
acme_certificate_challenge: dns-01
# The certificate files will be stored at:
# keys/example.com.key (private key)
# keys/example.com.csr (certificate signing request)
# keys/example.com.pem (certificate)
# keys/example.com.cnf (OpenSSL config for CSR creation -- can be safely deleted)
# keys/example.com-chain.pem (intermediate certificate)
# keys/example.com-fullchain.pem (certificate with intermediate certificate)
# keys/example.com-root.pem (root certificate)
# keys/example.com-rootchain.pem (intermediate certificate with root certificate)
- role: acme_certificate
acme_certificate_domains: ['another.example.com']
acme_certificate_key_name: 'another.example.com-rsa'
acme_certificate_key_length: 4096
# Use HTTP challenges:
acme_certificate_challenge: http-01
# The certificate files will be stored at:
# keys/another.example.com-rsa.key (private key)
# keys/another.example.com-rsa.csr (certificate signing request)
# keys/another.example.com-rsa.pem (certificate)
# keys/another.example.com-rsa.cnf (OpenSSL config for CSR creation -- can be safely deleted)
# keys/another.example.com-rsa-chain.pem (intermediate certificate)
# keys/another.example.com-rsa-fullchain.pem (certificate with intermediate certificate)
# keys/another.example.com-rsa-root.pem (root certificate)
# keys/another.example.com-rsa-rootchain.pem (intermediate certificate with root certificate)
- role: acme_certificate
acme_certificate_domains: ['another.example.com']
acme_certificate_key_name: 'another.example.com-ecc'
acme_certificate_algorithm: 'p-256'
# Use HTTP challenges (default for challenge is http-01).
# The certificate files will be stored at:
# keys/another.example.com-ecc.key (private key)
# keys/another.example.com-ecc.csr (certificate signing request)
# keys/another.example.com-ecc.pem (certificate)
# keys/another.example.com-ecc.cnf (OpenSSL config for CSR creation -- can be safely deleted)
# keys/another.example.com-ecc-chain.pem (intermediate certificate)
# keys/another.example.com-ecc-fullchain.pem (certificate with intermediate certificate)
# keys/another.example.com-ecc-root.pem (root certificate)
# keys/another.example.com-ecc-rootchain.pem (intermediate certificate with root certificate)
The MIT License (MIT)
Copyright (c) 2018-2020 Felix Fontein
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The homepage for this role is https://github.com/felixfontein/acme-certificate/. Please use the issue tracker to report problems.