PostgreSQL : servir le cert step-ca (TLS vérifiable), non-bloquant

Zéro-confiance flux PG, côté serveur : cert-sync (owned postgres, motif .path
comme Dovecot/Postfix) + ssl_cert/key/ca_file pointant sur le cert step-ca au
lieu du snakeoil. Garde-fou : ssl non activé si le cert n'est pas en place.
Reload robuste (instance postgresql@NN-main, pas le wrapper).

Prouvé sur data-sql-01 : cert servi = Set-OPS Internal CA (vérif root_ca ok),
keycloak/forgejo toujours 200 (TLS reste optionnel, rien de cassé).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-04 18:31:30 -04:00
parent b6952759f5
commit 5aa7fcafdb
6 changed files with 133 additions and 0 deletions

View file

@ -36,3 +36,12 @@ serveur_postgresql_comptes: []
serveur_postgresql_bases: []
# - nom: keycloak
# proprietaire: keycloak
# --- TLS via le certificat d'hote step_ca (client_pki requis sur le noeud) ---
# Cote serveur : PostgreSQL sert le cert step_ca (verifiable contre root_ca), au lieu
# du snakeoil auto-signe. Non-bloquant : le TLS reste optionnel tant que pg_hba ne le force pas.
serveur_postgresql_tls_actif: false
serveur_postgresql_tls_source_cert: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.crt"
serveur_postgresql_tls_source_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.key"
serveur_postgresql_tls_source_ca: "/etc/step/certs/root_ca.crt"
serveur_postgresql_tls_dir: "/etc/postgresql/tls"

View file

@ -37,6 +37,79 @@
group: postgres
mode: "0755"
# --- TLS : synchroniser le cert step_ca AVANT d'activer ssl (ordre critique) ---
- name: TLS — assurer le repertoire des certificats (postgres)
ansible.builtin.file:
path: "{{ serveur_postgresql_tls_dir }}"
state: directory
owner: postgres
group: postgres
mode: "0700"
when: serveur_postgresql_tls_actif | bool
- name: TLS — deployer le script de synchronisation du certificat
ansible.builtin.template:
src: setops-postgresql-cert-sync.sh.j2
dest: /usr/local/sbin/setops-postgresql-cert-sync
owner: root
group: root
mode: "0755"
when: serveur_postgresql_tls_actif | bool
- name: TLS — deployer les unites de synchronisation (service + path)
ansible.builtin.template:
src: "{{ item.s }}"
dest: "{{ item.d }}"
owner: root
group: root
mode: "0644"
loop:
- { s: "setops-postgresql-cert-sync.service.j2", d: "/etc/systemd/system/setops-postgresql-cert-sync.service" }
- { s: "setops-postgresql-cert-sync.path.j2", d: "/etc/systemd/system/setops-postgresql-cert-sync.path" }
loop_control:
label: "{{ item.d | basename }}"
when: serveur_postgresql_tls_actif | bool
- name: TLS — activer la surveillance du certificat (path)
ansible.builtin.systemd:
name: setops-postgresql-cert-sync.path
enabled: true
state: started
daemon_reload: true
when:
- serveur_postgresql_tls_actif | bool
- not ansible_check_mode
- name: TLS — synchroniser le certificat maintenant
ansible.builtin.command: /usr/local/sbin/setops-postgresql-cert-sync
changed_when: false
when:
- serveur_postgresql_tls_actif | bool
- not ansible_check_mode
- name: TLS — verifier la presence du certificat synchronise
ansible.builtin.stat:
path: "{{ serveur_postgresql_tls_dir }}/{{ item }}"
register: serveur_postgresql_tls_stat
loop:
- server.crt
- server.key
when:
- serveur_postgresql_tls_actif | bool
- not ansible_check_mode
- name: TLS — garde-fou, refuser d'activer ssl sans certificat en place
ansible.builtin.assert:
that:
- item.stat.exists
fail_msg: "Certificat PG absent ({{ item.item }}) — ssl non active pour ne rien casser."
loop: "{{ serveur_postgresql_tls_stat.results | default([]) }}"
loop_control:
label: "{{ item.item }}"
when:
- serveur_postgresql_tls_actif | bool
- not ansible_check_mode
- name: Deployer la configuration Set-OPS
ansible.builtin.template:
src: 99-setops.conf.j2

View file

@ -2,6 +2,13 @@
listen_addresses = '{{ serveur_postgresql_ecoute | join(",") }}'
port = {{ serveur_postgresql_port }}
password_encryption = '{{ serveur_postgresql_methode_auth }}'
{% if serveur_postgresql_tls_actif | default(false) %}
# TLS via le certificat d'hote step_ca (synchronise par setops-postgresql-cert-sync).
ssl = on
ssl_cert_file = '{{ serveur_postgresql_tls_dir }}/server.crt'
ssl_key_file = '{{ serveur_postgresql_tls_dir }}/server.key'
ssl_ca_file = '{{ serveur_postgresql_tls_dir }}/root.crt'
{% endif %}
{% for cle, valeur in serveur_postgresql_parametres.items() %}
{{ cle }} = '{{ valeur }}'
{% endfor %}

View file

@ -0,0 +1,12 @@
# Géré par Set-OPS (rôle serveur_postgresql). Ne pas éditer à la main.
# Surveille le certificat step_ca : au renouvellement (client_pki), resynchronise
# vers PostgreSQL et le recharge.
[Unit]
Description=Set-OPS — surveille le certificat step_ca de PostgreSQL
[Path]
PathChanged={{ serveur_postgresql_tls_source_cert }}
Unit=setops-postgresql-cert-sync.service
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,7 @@
# Géré par Set-OPS (rôle serveur_postgresql). Ne pas éditer à la main.
[Unit]
Description=Set-OPS — synchronise le certificat step_ca vers PostgreSQL
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setops-postgresql-cert-sync

View file

@ -0,0 +1,25 @@
#!/bin/sh
# Géré par Set-OPS (rôle serveur_postgresql). Ne pas éditer à la main.
# Synchronise le certificat step_ca (déposé par client_pki, root:root) vers un
# emplacement lisible par postgres, puis recharge. PostgreSQL lit en tant que
# 'postgres' et REFUSE une clé accessible au groupe/monde → owner postgres, 0600.
# Idempotent ; appelé au déploiement ET au renouvellement (unité .path).
set -eu
SRC_CERT="{{ serveur_postgresql_tls_source_cert }}"
SRC_KEY="{{ serveur_postgresql_tls_source_cle }}"
SRC_CA="{{ serveur_postgresql_tls_source_ca }}"
DST="{{ serveur_postgresql_tls_dir }}"
[ -f "$SRC_CERT" ] && [ -f "$SRC_KEY" ] || exit 0
install -o postgres -g postgres -m 0644 "$SRC_CERT" "$DST/server.crt"
install -o postgres -g postgres -m 0600 "$SRC_KEY" "$DST/server.key"
[ -f "$SRC_CA" ] && install -o postgres -g postgres -m 0644 "$SRC_CA" "$DST/root.crt"
# Reload (SIGHUP) : PostgreSQL recharge ses fichiers TLS sans coupure.
# ATTENTION : 'postgresql' est un wrapper Debian (reload = no-op) ; il faut recharger
# la ou les INSTANCES de cluster (postgresql@NN-main), sinon le nouveau cert n'est pas pris.
for u in $(systemctl list-units --plain --no-legend 'postgresql@*' 2>/dev/null | awk '{print $1}'); do
systemctl reload "$u" 2>/dev/null || true
done