mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
feat(svc-db-postgres): robust superuser password rotation via script with explicit auth verification
- replace Python helper with Bash script executed via ansible.script - wait for postgres readiness inside container (pg_isready) - explicitly verify password auth via temporary pg_hba override - distinguish CHANGED / UNCHANGED / ALREADY OK states - map script result deterministically to Ansible changed/failed logic - remove legacy postgres_set_superuser_password.py https://chatgpt.com/share/69754a74-9d18-800f-89a6-304b083fbe50
This commit is contained in:
3 files changed
+107
-110
No files matched your search
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Usage:
|
||||
# tmp.sh <container_name> <new_postgres_password>
|
||||
|
||||
container="${1:-}"
|
||||
new_pw="${2:-}"
|
||||
|
||||
if [[ -z "$container" || -z "$new_pw" ]]; then
|
||||
echo "Usage: $0 <container_name> <new_postgres_password>" >&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
|
||||
@@ -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())
|
||||
@@ -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
|
||||
Reference in new issue
Block a user