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 = {{setup_dns_cloudflare_email}} dns_cloudflare_api_key = {{setup_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: "{{ item.src }}" dest: "{{ item.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: "[email protected]" # 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: "{{ certbot_email }}" domains: - "*.{{ansible_nodename}}.{{base_domain}}" setup_dns_cloudflare_email: "[email protected]" # 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: "{{ansible_nodename}}.{{base_domain}}" extra_parameters: | ErrorLog ${APACHE_LOG_DIR}/error.log LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon apache_vhosts_ssl: - servername: "{{ansible_nodename}}.{{base_domain}}" certificate_file: "/etc/letsencrypt/live/{{ansible_nodename}}.{{base_domain}}/fullchain.pem" certificate_key_file: "/etc/letsencrypt/live/{{ansible_nodename}}.{{base_domain}}/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"