Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions roles/relay/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ relay_access_log: false
# ── Certbot ───────────────────────────────────────────────────────────────────
relay_certbot_email: "" # Set in secrets.yml: relay_certbot_email: "admin@example.com"

# Cert acquisition method for the apex domain.
# - webroot: HTTP-01 challenge served from {{ relay_webroot }}/.well-known/acme-challenge.
# Requires apex DNS to point at this host. Breaks for second-IP/round-robin DNS
# (LE may pick the wrong server and 404 the challenge).
# - dns-cloudflare: DNS-01 challenge via Cloudflare API. Requires
# relay_cloudflare_api_token in vault (Zone:DNS:Edit scope on the apex zone).
# Works regardless of where DNS resolves — required for multi-IP RU setups.
relay_certbot_method: webroot
relay_certbot_dns_propagation_seconds: 30

# ── Stub site ─────────────────────────────────────────────────────────────────
relay_stub_title: "Welcome"
relay_stub_description: "Personal website"
Expand Down
8 changes: 8 additions & 0 deletions roles/relay/defaults/secrets.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ relay_sub_my: "my.your-ru-domain.com" # Subdomain for Raven relay (A record
relay_upstream_host: "1.2.3.4" # EU server IP address
relay_certbot_email: "admin@example.com" # Email for Let's Encrypt notifications

# Cloudflare API token, ONLY required when relay_certbot_method=dns-cloudflare.
# Create at CF dashboard → My Profile → API Tokens → Create Token (Custom):
# Permissions: Zone → DNS → Edit
# Zone Resources: Include → Specific zone → <your apex>
# Token is used by certbot-dns-cloudflare snap plugin for DNS-01 challenge.
# Strongly recommended for multi-IP RU setups where webroot HTTP-01 breaks.
# relay_cloudflare_api_token: "..."

# Optional: Cloudflare Origin CA cert for {{ relay_sub_my }} (CF-proxied subdomain).
# When set, nginx serves this cert instead of LE for the my.* server block.
# Issued from CF dashboard → SSL/TLS → Origin Server with SAN: *.<domain> + <domain>.
Expand Down
46 changes: 43 additions & 3 deletions roles/relay/tasks/certbot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,38 @@
# and uses CF Origin CA cert (deployed in nginx_ssl.yml when relay_cf_origin_cert
# is set), so it doesn't need a publicly-trusted cert and stays out of CT logs
# (closes M6).
- name: Relay | Validate dns-cloudflare prerequisites
ansible.builtin.assert:
that:
- relay_cloudflare_api_token is defined
- relay_cloudflare_api_token | length > 0
fail_msg: >-
relay_certbot_method=dns-cloudflare requires relay_cloudflare_api_token
in vault (Zone:DNS:Edit on apex zone). See defaults/secrets.yml.example.
when: relay_certbot_method == 'dns-cloudflare'

- name: Relay | Deploy Cloudflare credentials file (DNS-01 only)
ansible.builtin.copy:
content: "dns_cloudflare_api_token = {{ relay_cloudflare_api_token }}\n"
dest: /etc/letsencrypt/cloudflare.ini
owner: root
group: root
mode: "0600"
when: relay_certbot_method == 'dns-cloudflare'
no_log: true

- name: Relay | Check if certificate already covers apex only
# certbot ≤2.x prints "Domains:", certbot ≥3.x prints "Identifiers:" — accept both.
ansible.builtin.shell:
cmd: |
certbot certificates --cert-name {{ relay_domain }} 2>/dev/null \
| grep -E 'Domains:' | awk -F': ' '{print $2}' | xargs -n1 | sort | tr '\n' ' '
| grep -E '^[[:space:]]*(Domains|Identifiers):' \
| awk -F': ' '{print $2}' | xargs -n1 | sort | tr '\n' ' '
register: relay_cert_domains
changed_when: false
failed_when: false

- name: Relay | Obtain or update Let's Encrypt certificate (apex only)
- name: Relay | Obtain or update Let's Encrypt certificate (webroot)
ansible.builtin.command:
cmd: >
certbot certonly --webroot
Expand All @@ -22,7 +44,25 @@
--cert-name {{ relay_domain }}
--email {{ relay_certbot_email }}
-d {{ relay_domain }}
when: relay_cert_domains.stdout | default('') | trim != relay_domain
when:
- relay_certbot_method == 'webroot'
- relay_cert_domains.stdout | default('') | trim != relay_domain
notify: Reload nginx

- name: Relay | Obtain or update Let's Encrypt certificate (DNS-01 via Cloudflare)
ansible.builtin.command:
cmd: >
certbot certonly --dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
--dns-cloudflare-propagation-seconds {{ relay_certbot_dns_propagation_seconds }}
--non-interactive
--agree-tos
--cert-name {{ relay_domain }}
--email {{ relay_certbot_email }}
-d {{ relay_domain }}
when:
- relay_certbot_method == 'dns-cloudflare'
- relay_cert_domains.stdout | default('') | trim != relay_domain
notify: Reload nginx

- name: Relay | Ensure certbot renewal timer is enabled
Expand Down
20 changes: 20 additions & 0 deletions roles/relay/tasks/install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,23 @@
name: nginx
enabled: true
state: started

# certbot-dns-cloudflare plugin. Snap requires explicit trust before installing
# any plugin that runs as root; the trust flag is idempotent so we just always
# set it when method=dns-cloudflare. Plugin install is gated by `creates`, and
# `snap connect` is idempotent on its own.
- name: Relay | Allow certbot snap plugins with root access
ansible.builtin.command: snap set certbot trust-plugin-with-root=ok
changed_when: false
when: relay_certbot_method == 'dns-cloudflare'

- name: Relay | Install certbot-dns-cloudflare snap plugin
ansible.builtin.command:
cmd: snap install certbot-dns-cloudflare
creates: /snap/certbot-dns-cloudflare
when: relay_certbot_method == 'dns-cloudflare'

- name: Relay | Connect certbot:plugin to certbot-dns-cloudflare
ansible.builtin.command: snap connect certbot:plugin certbot-dns-cloudflare
changed_when: false
when: relay_certbot_method == 'dns-cloudflare'
Loading