Certificates with Ansible, Letsencrypt and Cloudflare

Changelog / Warnings

Please note that this article is more than 5 years old. The information provided may very well be outdated

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

 1---
 2- hosts: localhost
 3  vars:
 4    connection: local
 5    cloudflare_account_email: ''
 6    cloudflare_account_api_token: ''
 7    domain_name: ''
 8
 9  tasks:
10  - name: Create folder to store everything
11    file:
12      path: '~/.ssh/letsencrypt'
13      state: directory
14      mode: 0700
15
16  - name: Create Letsencrypt Account private key
17    openssl_privatekey:
18      path: '~/.ssh/letsencrypt/letsencrypt.pem'
19  
20  - name: Create Domain Private Key
21    openssl_privatekey:
22      path: '~/.ssh/letsencrypt/{{ domain_name }}.pem'
23
24  - name: Create Certificate Signing Request
25    openssl_csr:
26      path: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
27      privatekey_path: '~/.ssh/letsencrypt/{{ domain_name }}.pem'
28      common_name: '{{ domain_name }}'
29      subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}"
30    with_dict:
31      dns_server:
32      - '{{ domain_name }}'
33      - '*.{{ domain_name }}'
34
35  - name: Create ACME Challenge
36    acme_certificate:
37      account_key_src: '~/.ssh/letsencrypt/letsencrypt.pem'
38      acme_directory: 'https://acme-staging-v02.api.letsencrypt.org/directory'
39      acme_version: 2
40      challenge: dns-01
41      csr: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
42      fullchain_dest: '~/.ssh/letsencrypt/{{ domain_name }}.fullchain.pem'
43      terms_agreed: yes
44    register: le_challenge
45
46  - name: Update DNS record
47    cloudflare_dns:
48      account_api_token: '{{ cloudflare_account_api_token }}'
49      account_email: '{{ cloudflare_account_email }}'
50      zone: '{{ domain_name }}'
51      record: "{{ item['value']['dns-01']['resource'] }}"
52      type: TXT
53      value: "{{ item['value']['dns-01']['resource_value'] }}"
54    when: le_challenge is changed
55    with_dict: "{{ le_challenge['challenge_data'] }}"
56    ignore_errors: yes
57
58  - name: Wait a bit to let DNS update
59    wait_for:
60      timeout: 30
61    when: le_challenge is changed
62
63  - name: Validate ACME Challenge
64    acme_certificate:
65      account_key_src: '~/.ssh/letsencrypt/letsencrypt.pem'
66      acme_directory: 'https://acme-staging-v02.api.letsencrypt.org/directory'
67      acme_version: 2
68      challenge: dns-01
69      csr: '~/.ssh/letsencrypt/{{ domain_name }}.csr'
70      data: '{{ le_challenge }}'
71      fullchain_dest: '~/.ssh/letsencrypt/{{ domain_name }}.fullchain.pem'
72      terms_agreed: yes
73    when: le_challenge is changed
74
75  - name: Remove TXT record after validating
76    cloudflare_dns:
77      account_api_token: '{{ cloudflare_account_api_token }}'
78      account_email: '{{ cloudflare_account_email }}'
79      zone: '{{ domain_name }}'
80      record: "{{ item['value']['dns-01']['resource'] }}"
81      type: TXT
82      value: "{{ item['value']['dns-01']['resource_value'] }}"
83      state: absent
84    when: le_challenge is changed
85    with_dict: "{{ le_challenge['challenge_data'] }}"
86    ignore_errors: yes
87