feat(forge): resolve an optional forge read credential (STATE-WP-0084-T02/T03)
Nine repositories are invisible to derivation because central may not read them. This adds the consuming half of the credential lane MASON-WP-0003 built. The cluster has no agent injector and no secrets-store CSI driver, so the pod authenticates to OpenBao with a projected ServiceAccount token (audience `openbao`, not the API server) and reads the KV path itself. `forgeRead.*` carries coordinates only; no credential is a chart value, an image layer, or a Kubernetes Secret. The credential reaches git through GIT_CONFIG_* setting http.extraHeader, not through `-c` and not through userinfo in the clone URL — both of those put the token in the process listing. It is redacted from ForgeDeriveError, which is logged, stored in reset outcomes, and returned over the API. Absent stays a supported state: with no credential, or with OpenBao unreachable, resolution returns None and public derivation runs unchanged. Raising would turn "nine repositories are unreadable" into "the pass failed", which is what T01 exists to prevent. Chart default is disabled. 717 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
parent
ab6438235e
commit
470ece82ed
8 changed files with 1334 additions and 7 deletions
|
|
@ -12,6 +12,8 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from api.services import forge_credential as fc
|
||||
|
||||
from api.services import forge_projection as fp
|
||||
|
||||
|
||||
|
|
@ -532,3 +534,153 @@ class TestUnreadableIsNotMissing:
|
|||
d = outcome.to_dict()
|
||||
assert d["unreadable_count"] == 1 and d["errored"] == 1
|
||||
assert d["repositories"] == 3
|
||||
|
||||
|
||||
class TestForgeCredential:
|
||||
"""Optional forge read credential (STATE-WP-0084-T03).
|
||||
|
||||
Absent is a valid state: a hub with no credential must still derive every
|
||||
public repository. The credential must never reach argv, a log, or an
|
||||
exception — the places a secret leaks without anyone deciding to leak it.
|
||||
"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_cache(self):
|
||||
"""The resolved credential is cached for 5 minutes in production.
|
||||
|
||||
That cache is deliberate — a fleet reset of 121 repositories must not
|
||||
authenticate to OpenBao 121 times — so tests clear it rather than
|
||||
disable it, and exercise the same code path production uses.
|
||||
"""
|
||||
fc.reset_cache()
|
||||
yield
|
||||
fc.reset_cache()
|
||||
|
||||
def test_absent_credential_is_none_not_empty_string(self, monkeypatch):
|
||||
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
||||
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
||||
assert fc.forge_read_token() is None
|
||||
|
||||
def test_a_file_is_preferred_over_the_environment(self, tmp_path, monkeypatch):
|
||||
"""Kubernetes rotates a mounted file without a redeploy."""
|
||||
f = tmp_path / "token"
|
||||
f.write_text("from-file\n", encoding="utf-8")
|
||||
monkeypatch.setenv(fc.TOKEN_ENV, "from-env")
|
||||
monkeypatch.setenv(fc.TOKEN_FILE_ENV, str(f))
|
||||
assert fc.forge_read_token() == "from-file"
|
||||
|
||||
def test_an_unreadable_token_file_does_not_fall_back_silently(self, monkeypatch):
|
||||
"""Falling back to a stale env value would hide a broken mount."""
|
||||
monkeypatch.setenv(fc.TOKEN_FILE_ENV, "/nonexistent/token")
|
||||
monkeypatch.setenv(fc.TOKEN_ENV, "from-env")
|
||||
assert fc.forge_read_token() is None
|
||||
|
||||
def test_openbao_is_the_last_resort_not_the_first(self, tmp_path, monkeypatch):
|
||||
"""A file or env value must not trigger a network call."""
|
||||
f = tmp_path / "token"
|
||||
f.write_text("local", encoding="utf-8")
|
||||
monkeypatch.setenv(fc.TOKEN_FILE_ENV, str(f))
|
||||
monkeypatch.setattr(
|
||||
fc, "_from_openbao", lambda: pytest.fail("OpenBao consulted unnecessarily")
|
||||
)
|
||||
assert fc.forge_read_token() == "local"
|
||||
|
||||
def test_openbao_failure_resolves_to_absent_not_an_exception(self, monkeypatch):
|
||||
"""A hub that cannot reach OpenBao must still derive public repos.
|
||||
|
||||
Raising here would turn "nine repositories are unreadable" into "the
|
||||
whole derivation pass failed" — the outcome STATE-WP-0084-T01 exists to
|
||||
prevent.
|
||||
"""
|
||||
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
||||
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
||||
monkeypatch.setenv(fc.OPENBAO_ADDR_ENV, "https://openbao.invalid")
|
||||
monkeypatch.setenv(fc.SECRET_PATH_ENV, "kv/data/forge")
|
||||
monkeypatch.setenv(fc.OPENBAO_ROLE_ENV, "state-hub-forge-derivation")
|
||||
monkeypatch.setenv(fc.OPENBAO_JWT_PATH_ENV, "/nonexistent/sa-token")
|
||||
assert fc.forge_read_token() is None
|
||||
|
||||
def test_openbao_unwraps_kv_v2(self, tmp_path, monkeypatch):
|
||||
jwt = tmp_path / "sa"
|
||||
jwt.write_text("jwt-value", encoding="utf-8")
|
||||
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
||||
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
||||
monkeypatch.setenv(fc.OPENBAO_ADDR_ENV, "https://openbao.test")
|
||||
monkeypatch.setenv(fc.SECRET_PATH_ENV, "kv/data/forge")
|
||||
monkeypatch.setenv(fc.OPENBAO_ROLE_ENV, "state-hub-forge-derivation")
|
||||
monkeypatch.setenv(fc.OPENBAO_JWT_PATH_ENV, str(jwt))
|
||||
|
||||
class R:
|
||||
def __init__(self, payload):
|
||||
self._p = payload
|
||||
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
def json(self):
|
||||
return self._p
|
||||
|
||||
class C:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
return False
|
||||
|
||||
def post(self, url, json):
|
||||
assert json["jwt"] == "jwt-value"
|
||||
assert json["role"] == "state-hub-forge-derivation"
|
||||
return R({"auth": {"client_token": "bao-token"}})
|
||||
|
||||
def get(self, url, headers):
|
||||
assert headers["X-Vault-Token"] == "bao-token"
|
||||
return R({"data": {"data": {"token": "forge-secret"}}})
|
||||
|
||||
monkeypatch.setattr(fc.httpx, "Client", lambda **kw: C())
|
||||
assert fc.forge_read_token() == "forge-secret"
|
||||
|
||||
def test_credential_never_appears_in_argv(self, monkeypatch):
|
||||
"""`-c http.extraHeader=` would put the token in every ps listing."""
|
||||
seen = {}
|
||||
|
||||
class P:
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
def fake_run(cmd, **kw):
|
||||
seen["cmd"] = cmd
|
||||
seen["env"] = kw.get("env") or {}
|
||||
return P()
|
||||
|
||||
monkeypatch.setattr(fp.subprocess, "run", fake_run)
|
||||
fp._run_git("clone", "url", "dir", token="s3cret")
|
||||
assert not any("s3cret" in part for part in seen["cmd"])
|
||||
assert seen["env"]["GIT_CONFIG_COUNT"] == "1"
|
||||
assert "s3cret" not in seen["env"]["GIT_CONFIG_KEY_0"]
|
||||
|
||||
def test_credential_is_redacted_from_failures(self, monkeypatch):
|
||||
class P:
|
||||
returncode = 128
|
||||
stdout = ""
|
||||
stderr = "fatal: auth failed using s3cret"
|
||||
|
||||
monkeypatch.setattr(fp.subprocess, "run", lambda *a, **k: P())
|
||||
with pytest.raises(fp.ForgeDeriveError) as exc:
|
||||
fp._run_git("clone", token="s3cret")
|
||||
assert "s3cret" not in str(exc.value) and "***" in str(exc.value)
|
||||
|
||||
def test_no_credential_still_runs(self, monkeypatch):
|
||||
class P:
|
||||
returncode = 0
|
||||
stdout = "ok"
|
||||
stderr = ""
|
||||
seen = {}
|
||||
|
||||
def fake_run(cmd, **kw):
|
||||
seen["env"] = kw.get("env") or {}
|
||||
return P()
|
||||
|
||||
monkeypatch.setattr(fp.subprocess, "run", fake_run)
|
||||
assert fp._run_git("status") == "ok"
|
||||
assert "GIT_CONFIG_COUNT" not in seen["env"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue