fix provision: use CURRENT superuser password from .env for auth
Some checks are pending
ci/woodpecker/push/deploy Pipeline is running
ci/woodpecker/push/infra-ci Pipeline was successful
coverage 99% coverage
ci/woodpecker/push/ci Pipeline was successful

The derived POSTGRES_PASSWORD differs from the actual password in
postgres (set during initial bootstrap). provision_postgres now
accepts superuser_pw parameter; deploy.py reads the CURRENT
password from .env to authenticate, then rotates roles to the
NEW derived passwords.

Also reads all current .env values before deriving so the deploy
module has access to the actual running credentials.
This commit is contained in:
kert
2026-03-23 10:24:54 -04:00
parent d0c33848ac
commit 9022effa57
2 changed files with 29 additions and 13 deletions

View File

@@ -134,21 +134,30 @@ def deploy(
values = derive_all(root_key, commit_sha) values = derive_all(root_key, commit_sha)
log.info("Derived %d credentials for commit %s", len(values), commit_sha[:8]) log.info("Derived %d credentials for commit %s", len(values), commit_sha[:8])
# Read current GITEA_TOKEN from .env (the one that's actually valid) # Read current secrets from .env — these are the passwords that
# The derived token is just a placeholder — real tokens come from the API # backends actually have. The derived values are what we WANT them
# to be AFTER rotation. We need the current ones to authenticate.
current_env: dict[str, str] = {}
if env_path.exists(): if env_path.exists():
for line in env_path.read_text().splitlines(): for line in env_path.read_text().splitlines():
if line.startswith("GITEA_TOKEN="): line = line.strip()
current_token = line.partition("=")[2].strip() if line and not line.startswith("#") and "=" in line:
if current_token: k, _, v = line.partition("=")
values["GITEA_TOKEN"] = current_token current_env[k.strip()] = v.strip()
log.info("Using current GITEA_TOKEN from .env for rotation")
break # Use current GITEA_TOKEN (derived one is just a hash placeholder)
if current_env.get("GITEA_TOKEN"):
values["GITEA_TOKEN"] = current_env["GITEA_TOKEN"]
log.info("Using current GITEA_TOKEN from .env")
# ── Phase 1: Rotate backends FIRST ──────────────────────── # ── Phase 1: Rotate backends FIRST ────────────────────────
# PostgreSQL # PostgreSQL — authenticate with CURRENT superuser password,
# then ALTER ROLE to the NEW derived passwords
pg_auth_pw = current_env.get(
"POSTGRES_PASSWORD", values.get("POSTGRES_PASSWORD", "")
)
try: try:
_retry(lambda: provision_postgres(values), "postgres") _retry(lambda: provision_postgres(values, superuser_pw=pg_auth_pw), "postgres")
result.postgres = True result.postgres = True
log.info("Phase 1: PostgreSQL passwords rotated") log.info("Phase 1: PostgreSQL passwords rotated")
except Exception as e: except Exception as e:

View File

@@ -105,13 +105,20 @@ def _retry(fn, label: str, retries: int = MAX_RETRIES):
# ── PostgreSQL provisioning ───────────────────────────────────── # ── PostgreSQL provisioning ─────────────────────────────────────
def provision_postgres(values: dict[str, str], *, container: str = "postgres") -> None: def provision_postgres(
values: dict[str, str],
*,
container: str = "postgres",
superuser_pw: str | None = None,
) -> None:
"""Rotate passwords for all managed PostgreSQL roles. """Rotate passwords for all managed PostgreSQL roles.
Uses psql -v variable binding so passwords never appear in SQL text. Uses psql -v variable binding so passwords never appear in SQL text.
Authenticates as superuser using POSTGRES_PASSWORD from the values dict. Pass superuser_pw to authenticate with the CURRENT password when
rotating to new derived passwords.
""" """
superuser_pw = values.get("POSTGRES_PASSWORD", "") if superuser_pw is None:
superuser_pw = values.get("POSTGRES_PASSWORD", "")
for role, env_var in POSTGRES_ROLES.items(): for role, env_var in POSTGRES_ROLES.items():
pw = values[env_var] pw = values[env_var]
subprocess.run( subprocess.run(