fix deploy: preserve all credentials, only rotate Gitea token
Stop rotating PostgreSQL passwords — services read them from env vars at startup and CI can't safely restart infrastructure. Preserve ALL current .env values; only create a fresh Gitea API token (stateless, no restart needed). This prevents the cascade of auth failures that was killing the stack on every deploy.
This commit is contained in:
@@ -29,7 +29,6 @@ from api.auth.provision import (
|
||||
_retry,
|
||||
derive_all,
|
||||
provision_gitea,
|
||||
provision_postgres,
|
||||
provision_woodpecker,
|
||||
write_env,
|
||||
)
|
||||
@@ -145,41 +144,21 @@ def deploy(
|
||||
k, _, v = line.partition("=")
|
||||
current_env[k.strip()] = v.strip()
|
||||
|
||||
# Preserve BOOTSTRAP-tier credentials from current .env.
|
||||
# These were set during initial bootstrap and must NOT be rotated
|
||||
# (RustFS stores IAM on disk; changing env vars crashes it).
|
||||
from api.auth.manifest import CREDENTIALS, Provisioner, Tier
|
||||
|
||||
bootstrap_vars = {
|
||||
c.env_var
|
||||
for c in CREDENTIALS
|
||||
if c.tier is Tier.BOOTSTRAP and c.provisioner is not Provisioner.SKIP
|
||||
}
|
||||
for var in bootstrap_vars:
|
||||
if var in current_env:
|
||||
# Preserve ALL current credentials from .env.
|
||||
# Credential rotation is dangerous — changing a password without
|
||||
# also restarting the service that reads it causes auth failures.
|
||||
# We only overwrite values that were successfully rotated in Phase 1.
|
||||
for var in current_env:
|
||||
if var in values:
|
||||
values[var] = current_env[var]
|
||||
log.info("Preserved %d bootstrap-tier credentials from .env", len(bootstrap_vars))
|
||||
log.info("Preserved %d credentials from current .env", len(current_env))
|
||||
|
||||
# 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 ────────────────────────
|
||||
# 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:
|
||||
_retry(lambda: provision_postgres(values, superuser_pw=pg_auth_pw), "postgres")
|
||||
result.postgres = True
|
||||
log.info("Phase 1: PostgreSQL passwords rotated")
|
||||
except Exception as e:
|
||||
result.errors.append(("postgres", e))
|
||||
log.error("Phase 1: PostgreSQL rotation failed: %s", e)
|
||||
|
||||
# Gitea
|
||||
# ── Phase 1: Rotate Gitea token ─────────────────────────────
|
||||
# Only rotate the Gitea API token — it's stateless and doesn't
|
||||
# require a service restart. DB passwords are NOT rotated because
|
||||
# services read them from env vars at startup and we can't safely
|
||||
# restart infrastructure services from CI.
|
||||
result.postgres = True # preserved from .env, no rotation needed
|
||||
try:
|
||||
token = _retry(lambda: provision_gitea(values), "gitea")
|
||||
values["GITEA_TOKEN"] = token
|
||||
|
||||
@@ -108,7 +108,7 @@ class TestDeploy:
|
||||
env.write_text("OLD_KEY=keep\n")
|
||||
|
||||
with (
|
||||
patch("api.auth.deploy.provision_postgres"),
|
||||
# provision_postgres no longer called
|
||||
patch("api.auth.deploy.provision_gitea", return_value="new-tok"),
|
||||
patch("api.auth.deploy.provision_woodpecker"),
|
||||
patch("api.auth.deploy.subprocess.run"),
|
||||
@@ -131,7 +131,7 @@ class TestDeploy:
|
||||
env.write_text("SAFE=original\n")
|
||||
|
||||
with (
|
||||
patch("api.auth.deploy.provision_postgres"),
|
||||
# provision_postgres no longer called
|
||||
patch("api.auth.deploy.provision_gitea", return_value="tok"),
|
||||
patch("api.auth.deploy.provision_woodpecker"),
|
||||
patch("api.auth.deploy.subprocess.run"),
|
||||
@@ -148,16 +148,15 @@ class TestDeploy:
|
||||
content = env.read_text()
|
||||
assert "SAFE=original" in content
|
||||
|
||||
def test_partial_backend_failure_still_deploys(self, tmp_path: Path):
|
||||
def test_gitea_failure_still_deploys(self, tmp_path: Path):
|
||||
env = tmp_path / ".env"
|
||||
env.write_text("")
|
||||
env.write_text("GITEA_TOKEN=old\n")
|
||||
|
||||
with (
|
||||
patch(
|
||||
"api.auth.deploy.provision_postgres",
|
||||
side_effect=Exception("pg down"),
|
||||
"api.auth.deploy.provision_gitea",
|
||||
side_effect=Exception("gitea down"),
|
||||
),
|
||||
patch("api.auth.deploy.provision_gitea", return_value="tok"),
|
||||
patch("api.auth.deploy.provision_woodpecker"),
|
||||
patch("api.auth.deploy.subprocess.run"),
|
||||
patch("api.auth.deploy.verify_all", return_value=[]),
|
||||
@@ -165,6 +164,6 @@ class TestDeploy:
|
||||
):
|
||||
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
|
||||
|
||||
assert not result.postgres
|
||||
assert result.gitea
|
||||
assert not result.gitea
|
||||
assert result.postgres # preserved, not rotated
|
||||
assert result.env_written
|
||||
|
||||
Reference in New Issue
Block a user