diff --git a/ansible/plays/haproxy.yml b/ansible/plays/haproxy.yml index 4578401..9d769c1 100644 --- a/ansible/plays/haproxy.yml +++ b/ansible/plays/haproxy.yml @@ -3,5 +3,5 @@ hosts: haproxy roles: - # - role: roles/certbot + - role: roles/certbot - role: roles/haproxy diff --git a/ansible/roles/certbot/defaults/main.yml b/ansible/roles/certbot/defaults/main.yml index 9485767..f4c9dd1 100644 --- a/ansible/roles/certbot/defaults/main.yml +++ b/ansible/roles/certbot/defaults/main.yml @@ -1,3 +1,13 @@ +certbot_email: kevin.midboe+ha.project@gmail.com +certbot_secrets_dir: /root/.secrets/certbot +combined_certs_dir: /etc/haproxy/certs +combined_cert_prefix: "ssl-" + +# Set true while testing to avoid LE rate limits +certbot_use_staging: false +le_renewal_window_seconds: 2592000 +certbot_throttle: 1 + certbot_packages: - certbot - python3-certbot-dns-cloudflare diff --git a/ansible/roles/certbot/tasks/issue_certs.yml b/ansible/roles/certbot/tasks/issue_certs.yml new file mode 100644 index 0000000..be9effc --- /dev/null +++ b/ansible/roles/certbot/tasks/issue_certs.yml @@ -0,0 +1,81 @@ +--- +- name: Read Cloudflare secrets directory from environment (invalid by default) + ansible.builtin.set_fact: + cloudflare_api_key: >- + {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_KEY') + | default('__CLOUDFLARE_API_KEY_NOT_SET__', true) }} + no_log: true + +- name: Fail if CLOUDFLARE_API_KEY is not set + ansible.builtin.assert: + that: + - cloudflare_api_key != '__CLOUDFLARE_API_KEY_NOT_SET__' + fail_msg: > + CLOUDFLARE_API_KEY environment variable is required + +- name: Validate dns_cloudflare_api_token looks sane + ansible.builtin.assert: + that: + - cloudflare_api_key is regex('[A-Za-z0-9]$') + fail_msg: > + must contain a valid + CLOUDFLARE_API_KEY = + no_log: false + +- name: Ensure certbot secrets directory exists + ansible.builtin.file: + path: "{{ certbot_secrets_dir }}" + state: directory + owner: root + group: root + mode: "0700" + +- name: Write Cloudflare credential file + ansible.builtin.template: + src: cloudflare.ini.j2 + dest: "{{ certbot_secrets_dir }}/certbot-cloudflare.ini" + owner: root + group: root + mode: "0600" + no_log: true + +- name: Ensure combined cert output directory exists + ansible.builtin.file: + path: "{{ combined_certs_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +# Request/renew: certbot is already idempotent-ish. We guard with `creates` to avoid +# re-issuing on first provision runs; renewals happen via cron/systemd timer (recommended). +- name: Obtain certificate via certbot dns-cloudflare (first issuance) + ansible.builtin.command: > + certbot certonly + --agree-tos + --non-interactive + --email {{ certbot_email }} + --dns-cloudflare + --dns-cloudflare-credentials {{ certbot_secrets_dir }}/certbot-cloudflare.ini + -d {{ item }} + {% if certbot_use_staging %}--staging{% endif %} + args: + creates: "/etc/letsencrypt/live/{{ item }}/fullchain.pem" + loop: "{{ certbot_cloudflare_domains | default([]) }}" + register: certbot_issue + changed_when: certbot_issue.rc == 0 + failed_when: certbot_issue.rc != 0 + async: 0 + +# Combine cert+key for Traefik/HAProxy-style PEM bundle +- name: Combine fullchain + privkey into single PEM bundle + ansible.builtin.shell: | + set -euo pipefail + cat \ + /etc/letsencrypt/live/{{ item }}/fullchain.pem \ + /etc/letsencrypt/live/{{ item }}/privkey.pem \ + > {{ combined_certs_dir }}/{{ combined_cert_prefix }}{{ item }}.pem + chmod 0600 {{ combined_certs_dir }}/{{ combined_cert_prefix }}{{ item }}.pem + args: + executable: /bin/bash + loop: "{{ certbot_cloudflare_domains | default([]) }}" diff --git a/ansible/roles/certbot/tasks/main.yml b/ansible/roles/certbot/tasks/main.yml index 73b32a1..5d22184 100644 --- a/ansible/roles/certbot/tasks/main.yml +++ b/ansible/roles/certbot/tasks/main.yml @@ -1,3 +1,4 @@ --- - import_tasks: install.yml - import_tasks: secrets.yml +# - import_tasks: issue_certs.yml diff --git a/ansible/roles/certbot/templates/cloudflare.ini.j2 b/ansible/roles/certbot/templates/cloudflare.ini.j2 index 82d9d88..ba0e1ba 100644 --- a/ansible/roles/certbot/templates/cloudflare.ini.j2 +++ b/ansible/roles/certbot/templates/cloudflare.ini.j2 @@ -1 +1,2 @@ -dns_cloudflare_api_token = {{ certbot_cloudflare_api_token }} +# Managed by ansible +dns_cloudflare_api_token = {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_KEY') }}