From 3eaa55b81ecd21d84be8651c0e6ff375aafc1fba Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Fri, 18 Jul 2025 12:43:03 +0200 Subject: [PATCH 1/3] certbot_create_method 'dns-cloudflare' --- README.md | 13 +++++++++++-- defaults/main.yml | 5 +++++ tasks/create-cert-dns-cloudflare.yml | 15 +++++++++++++++ tasks/install-with-package.yml | 10 ++++++++++ tasks/main.yml | 19 +++++++++++++++++++ templates/dnscloudflare.ini.j2 | 1 + vars/default.yml | 3 +++ 7 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tasks/create-cert-dns-cloudflare.yml create mode 100644 templates/dnscloudflare.ini.j2 diff --git a/README.md b/README.md index 40d147a..5fe186b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,11 @@ By default, this role configures a cron job to run under the provided user accou ### Automatic Certificate Generation -Currently the `standalone` and `webroot` method are supported for generating new certificates using this role. +Current methods supported for generating new certificates using this role: + + - `standalone` + - `webroot` + - `dns-cloudflare` **For a complete example**: see the fully functional test playbook in [molecule/default/playbook-standalone-nginx-aws.yml](molecule/default/playbook-standalone-nginx-aws.yml). @@ -36,7 +40,7 @@ Set `certbot_create_if_missing` to `yes` or `True` to let this role generate cer certbot_create_method: standalone -Set the method used for generating certs with the `certbot_create_method` variable — current allowed values are: `standalone` or `webroot`. +Set the method used for generating certs with the `certbot_create_method` variable — current allowed values are: `standalone`, `webroot` or 'dns-cloudflare'. certbot_testmode: false @@ -86,6 +90,11 @@ This install method is currently experimental and may or may not work across all When using the `webroot` creation method, a `webroot` item has to be provided for every `certbot_certs` item, specifying which directory to use for the authentication. Also, make sure your webserver correctly delivers contents from this directory. +#### dns-cloudflare Certificate Generation + +When using the `dns-cloudflare` creation method, set `certbot_dns_cloudflare_api_token` with your Cloudflare API token. +The process will generate a `dns-01` challenge (*DNS01*) by creating, and subsequently removing, TXT records using the Cloudflare API. See [certbot-dns-cloudflare documentation](https://certbot-dns-cloudflare.readthedocs.io/en/stable/) for details. + ### Source Installation from Git You can install Certbot from it's Git source repository if desired with `certbot_install_method: source`. This might be useful in several cases, but especially when older distributions don't have Certbot packages available (e.g. CentOS < 7, Ubuntu < 16.10 and Debian < 8). diff --git a/defaults/main.yml b/defaults/main.yml index 3f29db0..662a8c7 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -40,6 +40,9 @@ certbot_create_command: >- {{ '--webroot-path ' if certbot_create_method == 'webroot' else '' }} {{ cert_item.webroot | default(certbot_webroot) if certbot_create_method == 'webroot' else '' }} {{ certbot_create_extra_args }} + {{ '--dns-cloudflare-credentials /etc/letsencrypt/dnscloudflare.ini' + if certbot_create_method == 'dns-cloudflare' + else '' }} --cert-name {{ cert_item_name }} -d {{ cert_item.domains | join(',') }} {{ '--expand' if certbot_expand else '' }} @@ -58,6 +61,8 @@ certbot_create_standalone_stop_services: # - apache # - varnish +certbot_dns_cloudflare_api_token: fakeone + # Available options: 'package', 'snap', 'source'. certbot_install_method: 'package' diff --git a/tasks/create-cert-dns-cloudflare.yml b/tasks/create-cert-dns-cloudflare.yml new file mode 100644 index 0000000..3585822 --- /dev/null +++ b/tasks/create-cert-dns-cloudflare.yml @@ -0,0 +1,15 @@ +--- +- name: Determine certificate name + set_fact: + cert_item_name: "{{ cert_item.name | default(cert_item.domains | first | replace('*.', '')) }}" + +- name: Check if certificate already exists. + stat: + path: /etc/letsencrypt/live/{{ cert_item_name }}/cert.pem + register: letsencrypt_cert + become: true + +- name: Generate new certificate if one doesn't exist. + command: "{{ certbot_create_command }}" + when: not letsencrypt_cert.stat.exists + become: true diff --git a/tasks/install-with-package.yml b/tasks/install-with-package.yml index aec074c..cf5851e 100644 --- a/tasks/install-with-package.yml +++ b/tasks/install-with-package.yml @@ -4,6 +4,16 @@ name: "{{ certbot_package }}" state: present +- name: Install Certbot dependencies. + package: + name: "{{ cert_item }}" + state: present + when: + - certbot_create_method in certbot_create_packages + loop: "{{ certbot_create_packages[certbot_create_method] }}" + loop_control: + loop_var: cert_item + - name: Set Certbot script variable. set_fact: certbot_script: "{{ certbot_package }}" diff --git a/tasks/main.yml b/tasks/main.yml index 894143c..219e92e 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -13,6 +13,17 @@ - import_tasks: install-from-source.yml when: certbot_install_method == 'source' +- name: Cloudflare API token + ansible.builtin.template: + src: dnscloudflare.ini.j2 + dest: "/etc/letsencrypt/dnscloudflare.ini" + owner: root + group: root + mode: '0600' + when: + - certbot_create_method == 'dns-cloudflare' + become: true + - include_tasks: create-cert-standalone.yml with_items: "{{ certbot_certs }}" when: @@ -29,5 +40,13 @@ loop_control: loop_var: cert_item +- include_tasks: create-cert-dns-cloudflare.yml + with_items: "{{ certbot_certs }}" + when: + - certbot_create_if_missing + - certbot_create_method == 'dns-cloudflare' + loop_control: + loop_var: cert_item + - import_tasks: renew-cron.yml when: certbot_auto_renew diff --git a/templates/dnscloudflare.ini.j2 b/templates/dnscloudflare.ini.j2 new file mode 100644 index 0000000..9d87495 --- /dev/null +++ b/templates/dnscloudflare.ini.j2 @@ -0,0 +1 @@ +dns_cloudflare_api_token = {{ certbot_dns_cloudflare_api_token }} diff --git a/vars/default.yml b/vars/default.yml index d88f2dc..e394f90 100644 --- a/vars/default.yml +++ b/vars/default.yml @@ -1,2 +1,5 @@ --- certbot_package: certbot +certbot_create_packages: + 'dns-cloudflare': + - python3-certbot-dns-cloudflare From 97e3fdc6ec1145d1d2e56ebde056471b0cdf50bb Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Wed, 1 Oct 2025 01:29:06 +0200 Subject: [PATCH 2/3] certbot_create_method 'nginx' --- README.md | 5 +++++ tasks/create-cert-nginx.yml | 15 +++++++++++++++ tasks/main.yml | 8 ++++++++ vars/default.yml | 2 ++ 4 files changed, 30 insertions(+) create mode 100644 tasks/create-cert-nginx.yml diff --git a/README.md b/README.md index 5fe186b..6f69404 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Current methods supported for generating new certificates using this role: - `standalone` - `webroot` + - `nginx` - `dns-cloudflare` **For a complete example**: see the fully functional test playbook in [molecule/default/playbook-standalone-nginx-aws.yml](molecule/default/playbook-standalone-nginx-aws.yml). @@ -90,6 +91,10 @@ This install method is currently experimental and may or may not work across all When using the `webroot` creation method, a `webroot` item has to be provided for every `certbot_certs` item, specifying which directory to use for the authentication. Also, make sure your webserver correctly delivers contents from this directory. +### nginx Certificate Generation + +When using the `nginx` creation method, `nginx` package will be installed as a dependency of `python3-certbot-nginx`. + #### dns-cloudflare Certificate Generation When using the `dns-cloudflare` creation method, set `certbot_dns_cloudflare_api_token` with your Cloudflare API token. diff --git a/tasks/create-cert-nginx.yml b/tasks/create-cert-nginx.yml new file mode 100644 index 0000000..3585822 --- /dev/null +++ b/tasks/create-cert-nginx.yml @@ -0,0 +1,15 @@ +--- +- name: Determine certificate name + set_fact: + cert_item_name: "{{ cert_item.name | default(cert_item.domains | first | replace('*.', '')) }}" + +- name: Check if certificate already exists. + stat: + path: /etc/letsencrypt/live/{{ cert_item_name }}/cert.pem + register: letsencrypt_cert + become: true + +- name: Generate new certificate if one doesn't exist. + command: "{{ certbot_create_command }}" + when: not letsencrypt_cert.stat.exists + become: true diff --git a/tasks/main.yml b/tasks/main.yml index 219e92e..b705132 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -40,6 +40,14 @@ loop_control: loop_var: cert_item +- include_tasks: create-cert-nginx.yml + with_items: "{{ certbot_certs }}" + when: + - certbot_create_if_missing + - certbot_create_method == 'nginx' + loop_control: + loop_var: cert_item + - include_tasks: create-cert-dns-cloudflare.yml with_items: "{{ certbot_certs }}" when: diff --git a/vars/default.yml b/vars/default.yml index e394f90..b5a7c5f 100644 --- a/vars/default.yml +++ b/vars/default.yml @@ -1,5 +1,7 @@ --- certbot_package: certbot certbot_create_packages: + nginx: + - python3-certbot-nginx 'dns-cloudflare': - python3-certbot-dns-cloudflare From c5967d6b75e99b28849d77508323549d07db26de Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Tue, 24 Mar 2026 12:08:28 +0100 Subject: [PATCH 3/3] pre/post services for nginx --- defaults/main.yml | 4 +-- tasks/create-cert-nginx.yml | 52 ++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index 662a8c7..89528d7 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -47,10 +47,10 @@ certbot_create_command: >- -d {{ cert_item.domains | join(',') }} {{ '--expand' if certbot_expand else '' }} {{ '--pre-hook /etc/letsencrypt/renewal-hooks/pre/stop_services' - if certbot_create_standalone_stop_services and certbot_create_method == 'standalone' + if certbot_create_standalone_stop_services and certbot_create_method in ['standalone', 'nginx'] else '' }} {{ '--post-hook /etc/letsencrypt/renewal-hooks/post/start_services' - if certbot_create_standalone_stop_services and certbot_create_method == 'standalone' + if certbot_create_standalone_stop_services and certbot_create_method in ['standalone', 'nginx'] else '' }} {{ "--deploy-hook '" ~ cert_item.deploy_hook ~ "'" if 'deploy_hook' in cert_item diff --git a/tasks/create-cert-nginx.yml b/tasks/create-cert-nginx.yml index 3585822..594e6c8 100644 --- a/tasks/create-cert-nginx.yml +++ b/tasks/create-cert-nginx.yml @@ -9,7 +9,57 @@ register: letsencrypt_cert become: true +- name: Ensure pre and post hook folders exist. + file: + path: /etc/letsencrypt/renewal-hooks/{{ item }} + state: directory + mode: 0755 + owner: root + group: root + with_items: + - pre + - post + +- name: Create pre hook to stop services. + template: + src: stop_services.j2 + dest: /etc/letsencrypt/renewal-hooks/pre/stop_services + owner: root + group: root + mode: 0750 + when: + - certbot_create_standalone_stop_services is defined + - certbot_create_standalone_stop_services is truthy + +- name: Create post hook to start services. + template: + src: start_services.j2 + dest: /etc/letsencrypt/renewal-hooks/post/start_services + owner: root + group: root + mode: 0750 + when: + - certbot_create_standalone_stop_services is defined + - certbot_create_standalone_stop_services is truthy + +- name: Check if domains have changed + block: + - name: Register certificate domains + shell: "{{ certbot_script }} certificates --cert-name {{ cert_item_name }} | grep Domains | cut -d':' -f2" + changed_when: false + register: letsencrypt_cert_domains_dirty + + - name: Cleanup domain list + set_fact: + letsencrypt_cert_domains: "{{ letsencrypt_cert_domains_dirty.stdout | trim | split(' ') | map('trim') | select('!=', '') | list | sort }}" + + - name: Determine if domains have changed + set_fact: + letsencrypt_cert_domains_changed: "{{ letsencrypt_cert_domains != (cert_item.domains | map('trim') | select('!=', '') | list | sort) }}" + + when: letsencrypt_cert.stat.exists + - name: Generate new certificate if one doesn't exist. command: "{{ certbot_create_command }}" - when: not letsencrypt_cert.stat.exists + when: not letsencrypt_cert.stat.exists or letsencrypt_cert_domains_changed | default(false) become: true