The example use Cloudflare for DNS, but any provider with an ansible module works.
To use the example, add your own email, api token and domain name to variables. To receive a certificate with an actual trusted root, change ACME Directory to https://acme-v02.api.letsencrypt.org/directory
---
- hosts: localhost
vars:
connection: local
cloudflare_account_email: ''
cloudflare_account_api_token: ''
domain_name: ''
tasks:
- name: Create folder to store everything
file:
path: '~/.ssh/letsencrypt'
state: directory
mode: 0700
- name: Create Letsencrypt Account private key
openssl_privatekey:
path: '~/.ssh/letsencrypt/letsencrypt.pem'
- name: Create Domain Private Key
openssl_privatekey:
path: '~/.ssh/letsencrypt/{{ domain_name }}.pem'
- name: Create Certificate Signing Request
openssl_csr:
path: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
privatekey_path: '~/.ssh/letsencrypt/{{ domain_name }}.pem'
common_name: '{{ domain_name }}'
subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}"
with_dict:
dns_server:
- '{{ domain_name }}'
- '*.{{ domain_name }}'
- name: Create ACME Challenge
acme_certificate:
account_key_src: '~/.ssh/letsencrypt/letsencrypt.pem'
acme_directory: 'https://acme-staging-v02.api.letsencrypt.org/directory'
acme_version: 2
challenge: dns-01
csr: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
fullchain_dest: '~/.ssh/letsencrypt/{{ domain_name }}.fullchain.pem'
terms_agreed: yes
register: le_challenge
- name: Update DNS record
cloudflare_dns:
account_api_token: '{{ cloudflare_account_api_token }}'
account_email: '{{ cloudflare_account_email }}'
zone: '{{ domain_name }}'
record: "{{ item['value']['dns-01']['resource'] }}"
type: TXT
value: "{{ item['value']['dns-01']['resource_value'] }}"
when: le_challenge is changed
with_dict: "{{ le_challenge['challenge_data'] }}"
ignore_errors: yes
- name: Wait a bit to let DNS update
wait_for:
timeout: 30
when: le_challenge is changed
- name: Validate ACME Challenge
acme_certificate:
account_key_src: '~/.ssh/letsencrypt/letsencrypt.pem'
acme_directory: 'https://acme-staging-v02.api.letsencrypt.org/directory'
acme_version: 2
challenge: dns-01
csr: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
data: '{{ le_challenge }}'
fullchain_dest: '~/.ssh/letsencrypt/{{ domain_name }}.fullchain.pem'
terms_agreed: yes
when: le_challenge is changed
- name: Remove TXT record after validating
cloudflare_dns:
account_api_token: '{{ cloudflare_account_api_token }}'
account_email: '{{ cloudflare_account_email }}'
zone: '{{ domain_name }}'
record: "{{ item['value']['dns-01']['resource'] }}"
type: TXT
value: "{{ item['value']['dns-01']['resource_value'] }}"
state: absent
when: le_challenge is changed
with_dict: "{{ le_challenge['challenge_data'] }}"
ignore_errors: yes