Creating a Let’s Encrypt Wildcard Cert with Ansible
The other day I needed to add a wildcard cert to one of our staging servers. Using Jeff Geerling’s Ansible Role - Certbot (for Let’s Encrypt) for single domains provides an out of the box experience. Since we need a wildcard cert before installing Apache or Nginx we need to use a DNS plugin, there Is no web server to validate against. The plugins are not installed by default so we will need to run the Certbot role without any domains the install the plugin and run Certbot again with domains.
EDIT: You can get this role on Ansible Galaxy
ansible-galaxy install michaelpporter.certbot_cloudflare
The Setup Role
We will use the setup role to install the DNS plugin in between steps. Certbot uses ini files for settings. We will need two template files. For this demo I am using CloudFlare, the technique should work for the other supported DNS hosts.
Letsencrypt cli Template
roles/setup/files/templates/letsencrypt_cli.ini.j2
# Let's Encrypt site-wide configuration
dns-cloudflare-credentials = /etc/letsencrypt/dnscloudflare.ini
# Use the ACME v2 staging URI for testing things
#server = <https://acme-staging-v02.api.letsencrypt.org/directory>
# Production ACME v2 API endpoint
server = <https://acme-v02.api.letsencrypt.org/directory>
CloudFlare Template
roles/setup/files/templates/confcloudflare.ini.j2
# Cloudflare API credentials used by Certbot
dns_cloudflare_email =
dns_cloudflare_api_key =
roles/setup/tasks/main.yml
- name: Install certbot-dns-cloudflare
shell: "cd /opt/certbot/certbot-dns-cloudflare && python setup.py install"
when: "'demoweb' in group_names"
- name: Create certbot settings folder
file:
path: /etc/letsencrypt
state: directory
owner: root
group: root
mode: 0700
when: "'demoweb' in group_names"
- name: Create Certbot ini files
template:
src: ""
dest: ""
owner: root
group: root
mode: 0600
with_items:
- { src: 'files/templates/confcloudflare.ini.j2', dest: '/etc/letsencrypt/dnscloudflare.ini' }
- { src: 'files/templates/letsencrypt_cli.ini.j2', dest: '/etc/letsencrypt/cli.ini' }
when: "'demoweb' in group_names"
roles/setup/detaults/main.yml
setup_dns_cloudflare_email: ''
setup_dns_cloudflare_api_key: ''
The Playbook
Below is a sample playbook. It does not include php or mySQL needed for a full LAMP server.
main.yml
- hosts: demo
remote_user: sudousername # Change this to your remote user
become: true
pre_tasks:
- name: Install python for Ansible
raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
changed_when: False
- name: Set timezone
timezone:
name: America/Chicago
vars_files:
- vars/main-vars.yml
roles:
- geerlingguy.pip
- { role: geerlingguy.certbot, certbot_certs: [] } # Just install certbot
- setup # Install DNS plugin
- geerlingguy.certbot # Create the cert for our site
- geerlingguy.apache # Install Apache with Dynamic Vhosts
vars/vars-main.yml
# Cerbot
certbot_create_if_missing: yes
certbot_install_from_source: yes # includes the plugin folders to aid install
certbot_email: "valid@example.com" # Your admin email address
certbot_create_method: standalone
certbot_create_standalone_stop_services:
- apache
# - nginx
In my tests you have to use certbot
not certbot-auto
with the dns plugins
certbot_create_command: "certbot certonly --noninteractive --dns-cloudflare --agree-tos --email \{\{ cert_item.email | default(certbot_admin_email) }} -d \{\{ cert_item.domains | join(',') }}"
If you have 2 servers web01 and web02 this will setup the wilcard cert per server
## \*.web01.example.com
## \*.web02.example.com
certbot_certs:
- email: ""
domains:
- "*.."
setup_dns_cloudflare_email: "valid@example.com" # Your CloudFlare email
## To protect your API Key encrypt it with (tun it in terminal):
## ansible-vault encrypt_string 'cloudflareapikey' --name 'setup_dns_cloudflare_api_key'
setup_dns_cloudflare_api_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
62636164366637336237386437373030326162316236663365613930626438663737666337366230
3536333562666666366338613666386532383237643335360a613131386630393863343135306433
37323264393462363261313436363265663065343834373861373864393732653236376138636232
6163343664353030380a346166626631373366386163373935613033386633336664633037346366
61326135646639353462353530393832346564373665323564353864626364363232
# Apache
base_domain: "michaelpporter.com" # your domain
apache_remove_default_vhost: true
apache_global_vhost_settings: |
DirectoryIndex index.php index.html index.shtml
apache_vhosts:
- servername: "."
extra_parameters: |
ErrorLog ${APACHE_LOG_DIR}/error.log
LogFormat "%V %h %l %u %t "%r" %s %b" vcommon
apache_vhosts_ssl:
- servername: "."
certificate_file: "/etc/letsencrypt/live/./fullchain.pem"
certificate_key_file: "/etc/letsencrypt/live/./privkey.pem"
extra_parameters: |
ErrorLog ${APACHE_LOG_DIR}/error.log
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
# PHP
## Adjust to your needs
php_version: "7.1"
php_install_recommends: no
php_memory_limit: "256M"
php_display_errors: "On"
php_display_startup_errors: "On"
php_realpath_cache_size: "1024K"
php_opcache_enabled_in_ini: true
php_opcache_memory_consumption: "192"
php_opcache_max_accelerated_files: 4096
php_max_input_vars: "4000"
php_upload_max_filesize: "64M"
php_max_file_uploads: "20"
php_post_max_size: "32M"
php_date_timezone: "America/Chicago"