Files
ouranos/ansible/haproxy/deploy.yml
Robert Helewka 343b0e13d6 fix(certbot): harden renewal hook and fix permission errors
The renewal deploy-hook ran as the certbot user but lacked permissions to
write the combined PEM to /etc/haproxy/certs and to reload HAProxy,
causing silent failures that left a stale certificate in production until
expiry.

- Add certbot user to the haproxy group so it can write the combined PEM
- Grant certbot NOPASSWD sudo for `systemctl reload haproxy` only
- Make the Prometheus textfile directory group-owned by certbot (0775)
  so cert-metrics.sh can atomically update ssl_cert.prom
- Refactor renewal-hook.sh to always refresh cert metrics on exit via a
  trap, ensuring expiry alerts fire when the hook itself is broken
- Replace `set -e` with explicit error handling and structured logging
2026-06-17 09:58:46 -04:00

88 lines
3.4 KiB
YAML

---
# -----------------------------------------------------------------------------
# HAProxy Deployment Playbook
# -----------------------------------------------------------------------------
# Installs HAProxy and creates the directory structure required by downstream
# playbooks. This playbook must run BEFORE certbot/deploy.yml so that the
# /etc/haproxy/certs directory exists with the correct haproxy group ownership
# when certbot writes the combined PEM file.
#
# Dependency chain:
# haproxy/deploy.yml ← this playbook (package + dirs)
# certbot/deploy.yml ← writes cert to /etc/haproxy/certs/
# haproxy/configure.yml ← templates haproxy.cfg and starts the service
#
# Hosts: horkos (public reverse proxy), bootes (internal HAProxy)
# -----------------------------------------------------------------------------
- name: Deploy HAProxy (package and directory structure)
hosts: all
become: true
tags: [haproxy, service, deploy]
tasks:
- name: Check if host has haproxy service
ansible.builtin.set_fact:
has_haproxy_service: "{{ 'haproxy' in services | default([]) }}"
- name: Skip hosts without haproxy service
ansible.builtin.meta: end_host
when: not has_haproxy_service
# -------------------------------------------------------------------------
# Install HAProxy
# -------------------------------------------------------------------------
- name: Ensure HAProxy is installed
ansible.builtin.apt:
name: haproxy
state: present
update_cache: true
# -------------------------------------------------------------------------
# User / Group
# HAProxy's apt package creates the haproxy user/group, but we also need
# the certbot group to exist so that /etc/haproxy/certs can be group-owned
# by haproxy and written by certbot.
# -------------------------------------------------------------------------
- name: Ensure haproxy group exists
ansible.builtin.group:
name: "{{ haproxy_group | default('haproxy') }}"
system: true
- name: Ensure haproxy user exists
ansible.builtin.user:
name: "{{ haproxy_user | default('haproxy') }}"
group: "{{ haproxy_group | default('haproxy') }}"
system: true
shell: /usr/sbin/nologin
home: /nonexistent
create_home: false
# -------------------------------------------------------------------------
# Directory Structure
# /etc/haproxy/certs must exist with haproxy group ownership before certbot
# runs so that the renewal hook can write the combined PEM file there.
# -------------------------------------------------------------------------
- name: Ensure /etc/haproxy directory exists
ansible.builtin.file:
path: /etc/haproxy
owner: root
group: "{{ haproxy_group | default('haproxy') }}"
state: directory
mode: '0755'
# Mode 0770: the certbot renewal deploy-hook (running as the certbot user,
# a member of the haproxy group) must be able to create the temporary PEM
# file here. With 0750 the hook fails with "Permission denied" and HAProxy
# keeps serving a stale cert until it expires.
- name: Ensure /etc/haproxy/certs directory exists
ansible.builtin.file:
path: /etc/haproxy/certs
owner: "{{ haproxy_user | default('haproxy') }}"
group: "{{ haproxy_group | default('haproxy') }}"
state: directory
mode: '0770'