mirror of
https://github.com/KevinMidboe/playbooks-retailor.git
synced 2026-09-07 13:32:06 +00:00
207 lines
7.0 KiB
YAML
207 lines
7.0 KiB
YAML
---
|
|
# ---------------------------------------------------------------------------
|
|
# TLS certificate lifecycle via lego (ACME DNS-01 against Cloudflare).
|
|
#
|
|
# This file only READS the Cloudflare token from elk_cloudflare_token_env_file.
|
|
# It never receives the token as an Ansible variable - the token is expected
|
|
# to already exist on disk (root:root, 0600), placed there by cloud-init at
|
|
# provisioning time. See the role README for the IaC side of this.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
- name: Check if Cloudflare token file exists at legacy/staging path
|
|
ansible.builtin.stat:
|
|
path: /etc/retailor/cloudflare-token.env
|
|
register: elk_cf_token_src_stat
|
|
|
|
- name: Ensure folder exists
|
|
ansible.builtin.command:
|
|
cmd: mkdir -p "{{ elk_lego_path }}"
|
|
when: elk_cf_token_src_stat.stat.exists
|
|
changed_when: true
|
|
|
|
- name: Move Cloudflare token file into place if found at staging path
|
|
ansible.builtin.command:
|
|
cmd: cp /etc/retailor/cloudflare-token.env "{{ elk_cloudflare_token_env_file }}"
|
|
when: elk_cf_token_src_stat.stat.exists
|
|
changed_when: true
|
|
|
|
- name: Assert the Cloudflare token file exists at the expected location
|
|
ansible.builtin.stat:
|
|
path: "{{ elk_cloudflare_token_env_file }}"
|
|
register: elk_cf_token_stat
|
|
|
|
- name: Fail early with a clear message if the token file is missing
|
|
ansible.builtin.fail:
|
|
msg: >
|
|
{{ elk_cloudflare_token_env_file }} does not exist. This role expects
|
|
cloud-init (or equivalent provisioning) to have already written a
|
|
scoped Cloudflare API token here before Ansible runs, or a legacy
|
|
copy at /etc/retailor/cloudflare-token.env to be moved into place. See the
|
|
IaC notes in the role README.
|
|
when: not elk_cf_token_stat.stat.exists
|
|
|
|
- name: Ensure the token file has correct ownership and permissions
|
|
ansible.builtin.file:
|
|
path: "{{ elk_cloudflare_token_env_file }}"
|
|
mode: "0640"
|
|
owner: root
|
|
group: root
|
|
when: elk_cf_token_stat.stat.exists
|
|
|
|
- name: Re-stat token file after permission enforcement
|
|
ansible.builtin.stat:
|
|
path: "{{ elk_cloudflare_token_env_file }}"
|
|
register: elk_cf_token_stat
|
|
|
|
- name: Verify the token file is not world readable
|
|
ansible.builtin.assert:
|
|
that:
|
|
- elk_cf_token_stat.stat.mode == "0640"
|
|
fail_msg: >
|
|
{{ elk_cloudflare_token_env_file }} should be mode 0640
|
|
(currently {{ elk_cf_token_stat.stat.mode }}) since it holds a
|
|
Cloudflare API credential.
|
|
success_msg: "Cloudflare token file permissions verified."
|
|
|
|
- name: Create lego directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0700"
|
|
loop:
|
|
- "{{ elk_lego_path }}"
|
|
- "{{ elk_lego_path }}/accounts"
|
|
|
|
- name: Create lego certificates directory (traversable by container users)
|
|
ansible.builtin.file:
|
|
path: "{{ elk_lego_certs_dir }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Check installed lego version (if any)
|
|
ansible.builtin.command: "{{ elk_lego_install_dir }}/lego --version"
|
|
register: elk_lego_installed_version
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Download and install lego binary
|
|
when: >
|
|
elk_lego_installed_version.rc != 0 or
|
|
elk_lego_version not in (elk_lego_installed_version.stdout | default(''))
|
|
block:
|
|
- name: Download lego release tarball
|
|
ansible.builtin.get_url:
|
|
url: "https://github.com/go-acme/lego/releases/download/v{{ elk_lego_version }}/lego_v{{ elk_lego_version }}_linux_{{ elk_lego_arch }}.tar.gz"
|
|
dest: "/tmp/lego_{{ elk_lego_version }}.tar.gz"
|
|
mode: "0644"
|
|
|
|
- name: Extract lego binary
|
|
ansible.builtin.unarchive:
|
|
src: "/tmp/lego_{{ elk_lego_version }}.tar.gz"
|
|
dest: /tmp
|
|
remote_src: true
|
|
include:
|
|
- lego
|
|
|
|
- name: Install lego binary
|
|
ansible.builtin.copy:
|
|
src: /tmp/lego
|
|
dest: "{{ elk_lego_install_dir }}/lego"
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
remote_src: true
|
|
|
|
- name: Clean up downloaded artifacts
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: absent
|
|
loop:
|
|
- "/tmp/lego_{{ elk_lego_version }}.tar.gz"
|
|
- /tmp/lego
|
|
|
|
- name: Check whether a certificate already exists for this domain
|
|
ansible.builtin.stat:
|
|
path: "{{ elk_lego_certs_dir }}/{{ elk_tls_domain }}.crt"
|
|
register: elk_cert_stat
|
|
|
|
# First issuance uses `lego ... run`, which is what actually requests a new
|
|
# certificate; `renew` (used by the systemd timer below) refuses to run
|
|
# against a domain with no existing certificate.
|
|
- name: Issue the initial certificate (first run only)
|
|
ansible.builtin.shell: |
|
|
set -o pipefail
|
|
export $(cat "{{ elk_cloudflare_token_env_file}}" | xargs) && \
|
|
{{ elk_lego_install_dir }}/lego \
|
|
--accept-tos \
|
|
--email "{{ elk_tls_acme_email }}" \
|
|
--server "{{ elk_tls_acme_server }}" \
|
|
--domains "{{ elk_tls_domain }}" \
|
|
--dns cloudflare \
|
|
--dns.resolvers "1.1.1.1:53,8.8.8.8:53" \
|
|
--path "{{ elk_lego_path }}" \
|
|
run
|
|
args:
|
|
executable: /bin/bash
|
|
when: not elk_cert_stat.stat.exists
|
|
register: elk_lego_first_issue
|
|
notify: "restart elk stack"
|
|
|
|
- name: Ensure issued cert/key are readable by the containers (root-owned, group-readable)
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
loop:
|
|
- "{{ elk_lego_certs_dir }}/{{ elk_tls_domain }}.crt"
|
|
- "{{ elk_lego_certs_dir }}/{{ elk_tls_domain }}.issuer.crt"
|
|
when: elk_cert_stat.stat.exists or elk_lego_first_issue is not skipped
|
|
|
|
- name: Private key stays root-only-readable but group-readable for the container runtime
|
|
ansible.builtin.file:
|
|
path: "{{ elk_lego_certs_dir }}/{{ elk_tls_domain }}.key"
|
|
owner: root
|
|
group: root
|
|
mode: "0640"
|
|
when: elk_cert_stat.stat.exists or elk_lego_first_issue is not skipped
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ongoing renewal: systemd oneshot service + daily timer. lego's `renew`
|
|
# subcommand only actually re-issues (and only then fires --run-hook) when
|
|
# the certificate is within elk_tls_renew_days of expiring, so this is safe
|
|
# to run daily without causing unnecessary container restarts.
|
|
# ---------------------------------------------------------------------------
|
|
- name: Template lego renewal systemd service
|
|
ansible.builtin.template:
|
|
src: lego-renew.service.j2
|
|
dest: /etc/systemd/system/lego-renew.service
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
register: elk_lego_service_unit
|
|
|
|
- name: Template lego renewal systemd timer
|
|
ansible.builtin.template:
|
|
src: lego-renew.timer.j2
|
|
dest: /etc/systemd/system/lego-renew.timer
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
register: elk_lego_timer_unit
|
|
|
|
- name: Reload systemd if units changed
|
|
ansible.builtin.systemd:
|
|
daemon_reload: true
|
|
when: elk_lego_service_unit is changed or elk_lego_timer_unit is changed
|
|
|
|
- name: Enable and start the lego renewal timer
|
|
ansible.builtin.systemd:
|
|
name: lego-renew.timer
|
|
enabled: true
|
|
state: started
|