diff --git a/roles/svc-db-postgres/files/set_postgres_superuser_password.sh b/roles/svc-db-postgres/files/set_postgres_superuser_password.sh new file mode 100644 index 0000000000..305f7fcab4 --- /dev/null +++ b/roles/svc-db-postgres/files/set_postgres_superuser_password.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: +# tmp.sh + +container="${1:-}" +new_pw="${2:-}" + +if [[ -z "$container" || -z "$new_pw" ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +# Wait until PostgreSQL is ready (socket) +for i in $(seq 1 60); do + if docker exec "$container" bash -lc 'pg_isready -U postgres >/dev/null 2>&1'; then + break + fi + sleep 1 +done +docker exec "$container" bash -lc 'pg_isready -U postgres' + +# Locate pg_hba.conf +hba_file="$(docker exec "$container" bash -lc "psql -U postgres -d postgres -Atc 'SHOW hba_file;'")" +if [[ -z "$hba_file" ]]; then + echo "[FAIL] Could not determine hba_file" >&2 + exit 1 +fi + +backup="${hba_file}.bak_ansible_pwcheck_$$" + +# Backup hba +docker exec "$container" bash -lc "cp -a \"$hba_file\" \"$backup\"" + +restore_hba() { + # Restore on exit (best effort) + docker exec "$container" bash -lc "if [ -f \"$backup\" ]; then cp -a \"$backup\" \"$hba_file\" && rm -f \"$backup\"; fi" >/dev/null 2>&1 || true + docker exec "$container" bash -lc "psql -U postgres -d postgres -Atc 'SELECT pg_reload_conf();' >/dev/null 2>&1" || true +} +trap restore_hba EXIT + +# Force password auth for local TCP (127.0.0.1) +# Prepend a strict rule so it matches first. +docker exec "$container" bash -lc "{ + echo 'host all all 127.0.0.1/32 scram-sha-256' + cat \"$backup\" +} > \"$hba_file\"" + +# Reload config +docker exec "$container" bash -lc "psql -U postgres -d postgres -Atc 'SELECT pg_reload_conf();' >/dev/null" + +# Helper: test password auth explicitly over TCP +auth_test() { + docker exec -e PGPASSWORD="$new_pw" "$container" bash -lc \ + 'psql -h 127.0.0.1 -p 5432 -U postgres -d postgres -Atc "SELECT 1" >/dev/null 2>&1' +} + +pre_ok=0 +if auth_test; then + pre_ok=1 +fi + +# Set password (socket, no auth dependency) +docker exec -e NEW_POSTGRES_PASSWORD="$new_pw" "$container" bash -lc ' + psql -U postgres -d postgres -v ON_ERROR_STOP=1 -v password="$NEW_POSTGRES_PASSWORD" <<'"'"'SQL'"'"' +ALTER USER postgres WITH PASSWORD :'"'"'password'"'"'; +SQL +' + +post_ok=0 +if auth_test; then + post_ok=1 +fi + +if [[ "$pre_ok" -eq 0 && "$post_ok" -eq 1 ]]; then + echo "[CHANGED] New password did not work before, and works now." + exit 0 +fi + +if [[ "$pre_ok" -eq 1 && "$post_ok" -eq 1 ]]; then + echo "[UNCHANGED] New password already worked before." + exit 0 +fi + +echo "[FAIL] Password auth test failed even after ALTER USER." >&2 +exit 1 diff --git a/roles/svc-db-postgres/scripts/postgres_set_superuser_password.py b/roles/svc-db-postgres/scripts/postgres_set_superuser_password.py deleted file mode 100644 index 50c745a4ec..0000000000 --- a/roles/svc-db-postgres/scripts/postgres_set_superuser_password.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -""" -Set the PostgreSQL superuser ('postgres') password inside a Docker container. - -Exit codes: - 0 = no change (password already valid) - 2 = changed (password was updated) - 1 = error -""" - -from __future__ import annotations - -import argparse -import subprocess -import sys -from typing import List - - -def run(cmd: List[str], env: dict | None = None, input_text: str | None = None): - return subprocess.run( - cmd, - input=input_text, - text=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env, - check=False, - ) - - -def password_works(container: str, user: str, db: str, password: str) -> bool: - cmd = [ - "docker", - "exec", - "-e", - f"PGPASSWORD={password}", - container, - "psql", - "-U", - user, - "-d", - db, - "-c", - "SELECT 1", - ] - return run(cmd).returncode == 0 - - -def set_password(container: str, user: str, db: str, password: str) -> None: - sql = "ALTER USER postgres WITH PASSWORD :'new_password';\n" - - cmd = [ - "docker", - "exec", - "-i", - container, - "psql", - "-U", - user, - "-d", - db, - "-v", - "ON_ERROR_STOP=1", - "-v", - f"new_password={password}", - ] - - proc = run(cmd, input_text=sql) - if proc.returncode != 0: - raise RuntimeError(proc.stderr.strip()) - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Ensure postgres superuser password inside a Docker container." - ) - parser.add_argument("--container", required=True) - parser.add_argument("--user", default="postgres") - parser.add_argument("--db", default="postgres") - parser.add_argument("--password", required=True) - args = parser.parse_args() - - try: - if password_works(args.container, args.user, args.db, args.password): - print("postgres password already valid") - return 0 - - set_password(args.container, args.user, args.db, args.password) - - print("postgres password updated") - return 2 - - except Exception as exc: - print(f"ERROR: {exc}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/roles/svc-db-postgres/tasks/01_core.yml b/roles/svc-db-postgres/tasks/01_core.yml index a3f62fdc56..28e87ca7d7 100644 --- a/roles/svc-db-postgres/tasks/01_core.yml +++ b/roles/svc-db-postgres/tasks/01_core.yml @@ -20,17 +20,26 @@ package_name: 'psycopg2-binary' global_install: true -- name: "POSTGRES | Ensure postgres superuser password" - ansible.builtin.script: - cmd: > - scripts/postgres_set_superuser_password.py - --container {{ POSTGRES_CONTAINER }} - --user postgres - --db postgres - --password {{ POSTGRES_PASSWORD }} - register: pg_set_pwd +- name: "POSTGRES | Wait until postgres is ready (inside container)" + ansible.builtin.shell: > + docker exec {{ POSTGRES_CONTAINER }} pg_isready -U postgres + register: postgres_ready + until: postgres_ready.rc == 0 + retries: 30 + delay: 2 + changed_when: false + +- name: "POSTGRES | Ensure postgres superuser password matches inventory" + ansible.builtin.script: > + set_postgres_superuser_password.sh + {{ POSTGRES_CONTAINER | quote }} + {{ POSTGRES_PASSWORD | quote }} + register: postgres_pw_result no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - changed_when: pg_set_pwd.rc == 2 - failed_when: pg_set_pwd.rc not in [0, 2] + changed_when: > + postgres_pw_result.stdout is search('\\[CHANGED\\]') + failed_when: > + postgres_pw_result.rc != 0 or + postgres_pw_result.stdout is search('\\[FAIL\\]') - include_tasks: utils/once/flag.yml