From 99c309240caf99518db1dc0f53e08c42f5888e94 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 28 Jun 2024 20:34:40 -0500 Subject: [PATCH] r/postgresql-cert: ACME certificates using certbot This role can be used to get a server certificate for PostgreSQL from an ACME CA using `certbot`. It fetches the initial certificate and copies it to the PostgreSQL configuration directory. It also sets up a post-renewal hook script that copies updated certificates and reload the server. --- roles/postgresql-cert/handlers/main.yml | 8 ++ roles/postgresql-cert/tasks/main.yml | 114 ++++++++++++++++++ .../templates/certbot-renew.timer.j2 | 3 + .../templates/deploy-hook.sh.j2 | 30 +++++ 4 files changed, 155 insertions(+) create mode 100644 roles/postgresql-cert/handlers/main.yml create mode 100644 roles/postgresql-cert/tasks/main.yml create mode 100644 roles/postgresql-cert/templates/certbot-renew.timer.j2 create mode 100644 roles/postgresql-cert/templates/deploy-hook.sh.j2 diff --git a/roles/postgresql-cert/handlers/main.yml b/roles/postgresql-cert/handlers/main.yml new file mode 100644 index 0000000..0cd6ed8 --- /dev/null +++ b/roles/postgresql-cert/handlers/main.yml @@ -0,0 +1,8 @@ +- name: reload systemd + systemd: + daemon_reload: true + +- name: restart certbot-renew timer + systemd: + name: certbot-renew.timer + state: restarted diff --git a/roles/postgresql-cert/tasks/main.yml b/roles/postgresql-cert/tasks/main.yml new file mode 100644 index 0000000..c84bf2f --- /dev/null +++ b/roles/postgresql-cert/tasks/main.yml @@ -0,0 +1,114 @@ +- name: ensure required packages are installed + package: + name: + - certbot + - postgresql-server # to get postgres user account + state: present + tags: + - install + +- name: ensure http port is allowed in firewall (for acme challenge) + firewalld: + service: http + state: enabled + permanent: true + immediate: true + when: host_uses_firewalld|d(true) + tags: + - firewalld + +- name: ensure postgresql server certificate exists + command: + certbot certonly -n + --standalone + -d {{ postgresql_cert_domain }} + --server {{ postgresql_cert_acme_server }} + --agree-tos + --email {{ postgresql_cert_acme_email }} + args: + creates: /etc/letsencrypt/live/{{ postgresql_cert_domain }}/fullchain.pem + tags: + - cert + +- name: ensure certbot deploy renewal hook script is installed + template: + src: deploy-hook.sh.j2 + dest: /etc/letsencrypt/renewal-hooks/deploy/postgresql.sh + owner: root + group: root + mode: u=rwx,go=rx + tags: + - deploy-hook + +- name: ensure certbot renewal period is configured for postgresql cert + lineinfile: + line: renew_before_expiry = 8 hours + regexp: '^#?\s*renew_before_expiry\s*=' + path: /etc/letsencrypt/renewal/{{ postgresql_cert_domain }}.conf + state: present + tags: + - config + +- name: ensure certbot-renew timer unit drop-in directory exists + file: + path: /etc/systemd/system/certbot-renew.timer.d + owner: root + group: root + mode: u=rwx,go=rx + state: directory + tags: + - systemd +- name: ensure certbot-renew timer schedule is configured + template: + src: certbot-renew.timer.j2 + dest: /etc/systemd/system/certbot-renew.timer.d/schedule.conf + owner: root + group: root + mode: u=rw,go=r + notify: + - reload systemd + - restart certbot-renew timer + tags: + - systemd + +- name: ensure certbot-renew timer is enabled + systemd: + name: certbot-renew.timer + enabled: true + tags: + - service +- name: flush handlers + meta: flush_handlers +- name: ensure certbot-renew timer is running + systemd: + name: certbot-renew.timer + state: started + tags: + - service + +- name: ensure postgresql config directory exists + file: + path: /etc/postgresql + state: directory +- name: ensure initial copy of postgresql certificate is in place + copy: + src: /etc/letsencrypt/live/{{ postgresql_cert_domain }}/fullchain.pem + dest: /etc/postgresql/server.cer + remote_src: true + owner: root + group: root + mode: u=rw,go=r + force: false + tags: + - cert +- name: ensure initial copy of postgresql private key is in place + copy: + src: /etc/letsencrypt/live/{{ postgresql_cert_domain }}/privkey.pem + dest: /etc/postgresql/server.key + remote_src: true + owner: root + group: postgres + mode: u=rw,g=r,o= + force: false + tags: + - cert diff --git a/roles/postgresql-cert/templates/certbot-renew.timer.j2 b/roles/postgresql-cert/templates/certbot-renew.timer.j2 new file mode 100644 index 0000000..11ea320 --- /dev/null +++ b/roles/postgresql-cert/templates/certbot-renew.timer.j2 @@ -0,0 +1,3 @@ +[Timer] +RandomizedDelaySec=15m +OnCalendar=hourly diff --git a/roles/postgresql-cert/templates/deploy-hook.sh.j2 b/roles/postgresql-cert/templates/deploy-hook.sh.j2 new file mode 100644 index 0000000..a1b9352 --- /dev/null +++ b/roles/postgresql-cert/templates/deploy-hook.sh.j2 @@ -0,0 +1,30 @@ +#!/bin/sh +# vim: set sw=4 ts=4 sts=4 et : + +POSTGRESQL_DOMAIN="{{ postgresql_cert_domain }}" + +set -- ${FAILED_DOMAINS} +for domain; do + case ${domain} in + ${POSTGRESQL_DOMAIN}) + printf 'Certificate renewal failed for %s, not reloading server\n' \ + "${domain}" >&2 + exit 1 + ;; + esac +done + +set -- ${RENEWED_DOMAINS} +for domain; do + case ${domain} in + ${POSTGRESQL_DOMAIN}) + install -o root -g root -m u=rw,go=r \ + /etc/letsencrypt/live/${POSTGRESQL_DOMAIN}/fullchain.pem \ + /etc/postgresql/server.cer + install -o root -g postgres -m u=rw,g=r,o= \ + /etc/letsencrypt/live/${POSTGRESQL_DOMAIN}/privkey.pem \ + /etc/postgresql/server.key + systemctl reload postgresql + ;; + esac +done