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,
|
_retry,
|
||||||
derive_all,
|
derive_all,
|
||||||
provision_gitea,
|
provision_gitea,
|
||||||
provision_postgres,
|
|
||||||
provision_woodpecker,
|
provision_woodpecker,
|
||||||
write_env,
|
write_env,
|
||||||
)
|
)
|
||||||
@@ -145,41 +144,21 @@ def deploy(
|
|||||||
k, _, v = line.partition("=")
|
k, _, v = line.partition("=")
|
||||||
current_env[k.strip()] = v.strip()
|
current_env[k.strip()] = v.strip()
|
||||||
|
|
||||||
# Preserve BOOTSTRAP-tier credentials from current .env.
|
# Preserve ALL current credentials from .env.
|
||||||
# These were set during initial bootstrap and must NOT be rotated
|
# Credential rotation is dangerous — changing a password without
|
||||||
# (RustFS stores IAM on disk; changing env vars crashes it).
|
# also restarting the service that reads it causes auth failures.
|
||||||
from api.auth.manifest import CREDENTIALS, Provisioner, Tier
|
# We only overwrite values that were successfully rotated in Phase 1.
|
||||||
|
for var in current_env:
|
||||||
bootstrap_vars = {
|
if var in values:
|
||||||
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:
|
|
||||||
values[var] = current_env[var]
|
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)
|
# ── Phase 1: Rotate Gitea token ─────────────────────────────
|
||||||
if current_env.get("GITEA_TOKEN"):
|
# Only rotate the Gitea API token — it's stateless and doesn't
|
||||||
values["GITEA_TOKEN"] = current_env["GITEA_TOKEN"]
|
# require a service restart. DB passwords are NOT rotated because
|
||||||
log.info("Using current GITEA_TOKEN from .env")
|
# services read them from env vars at startup and we can't safely
|
||||||
|
# restart infrastructure services from CI.
|
||||||
# ── Phase 1: Rotate backends FIRST ────────────────────────
|
result.postgres = True # preserved from .env, no rotation needed
|
||||||
# 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
|
|
||||||
try:
|
try:
|
||||||
token = _retry(lambda: provision_gitea(values), "gitea")
|
token = _retry(lambda: provision_gitea(values), "gitea")
|
||||||
values["GITEA_TOKEN"] = token
|
values["GITEA_TOKEN"] = token
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class TestDeploy:
|
|||||||
env.write_text("OLD_KEY=keep\n")
|
env.write_text("OLD_KEY=keep\n")
|
||||||
|
|
||||||
with (
|
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_gitea", return_value="new-tok"),
|
||||||
patch("api.auth.deploy.provision_woodpecker"),
|
patch("api.auth.deploy.provision_woodpecker"),
|
||||||
patch("api.auth.deploy.subprocess.run"),
|
patch("api.auth.deploy.subprocess.run"),
|
||||||
@@ -131,7 +131,7 @@ class TestDeploy:
|
|||||||
env.write_text("SAFE=original\n")
|
env.write_text("SAFE=original\n")
|
||||||
|
|
||||||
with (
|
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_gitea", return_value="tok"),
|
||||||
patch("api.auth.deploy.provision_woodpecker"),
|
patch("api.auth.deploy.provision_woodpecker"),
|
||||||
patch("api.auth.deploy.subprocess.run"),
|
patch("api.auth.deploy.subprocess.run"),
|
||||||
@@ -148,16 +148,15 @@ class TestDeploy:
|
|||||||
content = env.read_text()
|
content = env.read_text()
|
||||||
assert "SAFE=original" in content
|
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 = tmp_path / ".env"
|
||||||
env.write_text("")
|
env.write_text("GITEA_TOKEN=old\n")
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"api.auth.deploy.provision_postgres",
|
"api.auth.deploy.provision_gitea",
|
||||||
side_effect=Exception("pg down"),
|
side_effect=Exception("gitea down"),
|
||||||
),
|
),
|
||||||
patch("api.auth.deploy.provision_gitea", return_value="tok"),
|
|
||||||
patch("api.auth.deploy.provision_woodpecker"),
|
patch("api.auth.deploy.provision_woodpecker"),
|
||||||
patch("api.auth.deploy.subprocess.run"),
|
patch("api.auth.deploy.subprocess.run"),
|
||||||
patch("api.auth.deploy.verify_all", return_value=[]),
|
patch("api.auth.deploy.verify_all", return_value=[]),
|
||||||
@@ -165,6 +164,6 @@ class TestDeploy:
|
|||||||
):
|
):
|
||||||
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
|
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
|
||||||
|
|
||||||
assert not result.postgres
|
assert not result.gitea
|
||||||
assert result.gitea
|
assert result.postgres # preserved, not rotated
|
||||||
assert result.env_written
|
assert result.env_written
|
||||||
|
|||||||
Reference in New Issue
Block a user