Compare commits
3 commits
c2b6bd5ef3
...
84beefc406
| Author | SHA1 | Date | |
|---|---|---|---|
| 84beefc406 | |||
| 6bab3bbfdf | |||
| 6c9b450043 |
4 changed files with 99 additions and 29 deletions
|
|
@ -138,7 +138,7 @@ platform/workloads/core-hub/forgejo-source-read:
|
|||
feeding ExternalSecret core-hub/repo-manager-forgejo-source, which materializes Secret repo-manager-forgejo-source
|
||||
with the single key token (remoteRef workloads/core-hub/forgejo-source-read property FORGEJO_SOURCE_TOKEN),
|
||||
explicit empty target metadata template, creationPolicy Owner, deletionPolicy Retain. rapp-core-hub
|
||||
then sets publisher.existingSecret to repo-manager-forgejo-source with tokenKey token. Both ma…
|
||||
then sets publisher.existingSecret to repo-manager-forgejo-source with tokenKey token. Both ma
|
||||
platform/workloads/core-hub/runtime:
|
||||
description: Project the existing Core Hub protected-API token into the private railiance01 runtime
|
||||
through a namespace-limited ExternalSecret while preserving the token across the relocation window.
|
||||
|
|
@ -458,7 +458,7 @@ platform/workloads/secrets-engine/approval-client:
|
|||
sso/keycape-secrets-engine-approval-client and Secret sso/keycape-secrets-engine-approval-client with
|
||||
key client-secret. KeyCape resolves it as KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET through a
|
||||
secretKeyRef, matching the live KEYCAPE_RAPP_QONTO_CLIENT_SECRET shape observed on image main-153258b.
|
||||
Manifests: argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstor…'
|
||||
Manifests: argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstor'
|
||||
platform/workloads/secrets-engine/approval-requester:
|
||||
description: T03 create-only requester KeyCape verifier custody; execute only the three fixed T03 request
|
||||
records.
|
||||
|
|
|
|||
|
|
@ -36,8 +36,12 @@ PROPOSALS = os.path.join(os.path.dirname(__file__), "..", "docs", "credential-ow
|
|||
|
||||
|
||||
def _clip(text: str | None) -> str:
|
||||
"""Fit OpenBao's custom_metadata limit, which counts bytes, not characters."""
|
||||
text = " ".join(str(text or "").split())
|
||||
return text if len(text) <= MAX_VALUE else text[: MAX_VALUE - 1] + "…"
|
||||
if len(text.encode()) <= MAX_VALUE:
|
||||
return text
|
||||
cut = text.encode()[: MAX_VALUE - 3].decode(errors="ignore")
|
||||
return cut + "..."
|
||||
|
||||
|
||||
def _first_sentence(text: str | None) -> str:
|
||||
|
|
@ -111,7 +115,7 @@ def check(doc: dict) -> int:
|
|||
bad = 0
|
||||
for path, e in doc.items():
|
||||
missing = [k for k in REQUIRED if not e.get(k)]
|
||||
long = [k for k in REQUIRED if len(str(e.get(k, ""))) > MAX_VALUE]
|
||||
long = [k for k in REQUIRED if len(str(e.get(k, "")).encode()) > MAX_VALUE]
|
||||
state = e.get("review")
|
||||
if missing or long or state != "accepted":
|
||||
bad += 1
|
||||
|
|
@ -121,23 +125,51 @@ def check(doc: dict) -> int:
|
|||
return 0 if bad == 0 else 1
|
||||
|
||||
|
||||
RECEIPT = os.path.join(HOME, ".local/state/railiance/receipts/custody-describe.json")
|
||||
# The warden wrapper runs with a temporary HOME, so resolve the real one.
|
||||
import pwd
|
||||
REAL_HOME = pwd.getpwuid(os.getuid()).pw_dir
|
||||
RECEIPT = os.path.join(REAL_HOME, ".local/state/railiance/receipts/custody-describe.json")
|
||||
|
||||
|
||||
def _bao(args: list[str], env: dict) -> tuple[int, str, str]:
|
||||
"""Run bao; return (rc, stdout, http_code). stderr is reduced to its HTTP code."""
|
||||
r = subprocess.run(["bao", *args], capture_output=True, text=True, env=env)
|
||||
m = re.search(r"Code: (\d{3})", r.stderr or "")
|
||||
return r.returncode, r.stdout, (m.group(1) if m else ("" if r.returncode == 0 else "no-code"))
|
||||
|
||||
|
||||
def _write(mount: str, rest: str, fields: dict, env: dict) -> str:
|
||||
flags = [f"-custom-metadata={k}={_clip(v)}" for k, v in fields.items()]
|
||||
rc, _, code = _bao(["kv", "metadata", "patch", f"-mount={mount}", *flags, rest], env)
|
||||
if rc == 0:
|
||||
return "patched"
|
||||
if code not in ("403", "405"):
|
||||
return f"failed patch http={code}"
|
||||
# No patch capability: read metadata (never data), merge, write it back whole,
|
||||
# so custom_metadata set by earlier CCRs survives.
|
||||
import json
|
||||
rc, out, code = _bao(["kv", "metadata", "get", "-format=json", f"-mount={mount}", rest], env)
|
||||
if rc != 0:
|
||||
return f"failed metadata-get http={code}"
|
||||
current = ((json.loads(out or "{}").get("data") or {}).get("custom_metadata")) or {}
|
||||
merged = {**current, **{k: _clip(v) for k, v in fields.items()}}
|
||||
flags = [f"-custom-metadata={k}={v}" for k, v in merged.items()]
|
||||
rc, _, code = _bao(["kv", "metadata", "put", f"-mount={mount}", *flags, rest], env)
|
||||
return "merged" if rc == 0 else f"failed put http={code}"
|
||||
|
||||
|
||||
def apply(doc: dict, receipt: str = RECEIPT) -> int:
|
||||
"""Silent: the warden wrapper refuses any output, so results go to a 0600 receipt."""
|
||||
import json
|
||||
env = dict(os.environ)
|
||||
env.setdefault("BAO_ADDR", "http://127.0.0.1:18200")
|
||||
results = {}
|
||||
for path, e in doc.items():
|
||||
if e.get("review") != "accepted" or any(not e.get(k) for k in REQUIRED):
|
||||
results[path] = "skipped"
|
||||
continue
|
||||
mount, rest = path.split("/", 1)
|
||||
args = ["bao", "kv", "metadata", "patch", f"-mount={mount}"]
|
||||
args += [f"-custom-metadata={k}={_clip(e[k])}" for k in REQUIRED]
|
||||
args.append(rest)
|
||||
ok = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||
results[path] = "patched" if ok else "failed"
|
||||
results[path] = _write(mount, rest, {k: e[k] for k in REQUIRED}, env)
|
||||
os.makedirs(os.path.dirname(receipt), mode=0o700, exist_ok=True)
|
||||
old_umask = os.umask(0o077)
|
||||
try:
|
||||
|
|
@ -145,7 +177,7 @@ def apply(doc: dict, receipt: str = RECEIPT) -> int:
|
|||
json.dump(results, f, indent=1, sort_keys=True)
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
return 1 if "failed" in results.values() else 0
|
||||
return 1 if any(v.startswith("failed") for v in results.values()) else 0
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
|
@ -9,32 +13,47 @@ _spec.loader.exec_module(cd)
|
|||
FULL = dict(description="d", owner="o", used_by="u", rotation="r", on_loss="l")
|
||||
|
||||
|
||||
def test_apply_patches_only_accepted_complete_entries_and_only_metadata(tmp_path, capsys):
|
||||
def _r(rc=0, out="", err=""):
|
||||
return MagicMock(returncode=rc, stdout=out, stderr=err)
|
||||
|
||||
|
||||
def test_apply_patches_only_accepted_complete_entries_silently(tmp_path, capsys):
|
||||
doc = {
|
||||
"platform/workloads/a/b": {"review": "accepted", **FULL},
|
||||
"platform/workloads/c/d": {"review": "draft", **FULL},
|
||||
"operators/e": {"review": "accepted", **dict(FULL, used_by="")},
|
||||
}
|
||||
with patch.object(cd.subprocess, "run", return_value=MagicMock(returncode=0)) as run:
|
||||
assert cd.apply(doc, receipt=str(tmp_path / 'r.json')) == 0
|
||||
assert capsys.readouterr() == ('', '')
|
||||
import json, os, stat
|
||||
r = tmp_path / 'r.json'
|
||||
assert json.load(open(r)) == {'platform/workloads/a/b': 'patched', 'platform/workloads/c/d': 'skipped', 'operators/e': 'skipped'}
|
||||
assert stat.S_IMODE(os.stat(r).st_mode) == 0o600
|
||||
with patch.object(cd.subprocess, "run", return_value=_r()) as run:
|
||||
assert cd.apply(doc, receipt=str(tmp_path / "r.json")) == 0
|
||||
assert capsys.readouterr() == ("", "")
|
||||
assert run.call_count == 1
|
||||
args = run.call_args.args[0]
|
||||
assert args[:5] == ["bao", "kv", "metadata", "patch", "-mount=platform"]
|
||||
assert args[-1] == "workloads/a/b"
|
||||
assert not any(a in ("get", "put", "patch-data") or "/data/" in a for a in args)
|
||||
assert run.call_args.kwargs["stdout"] is cd.subprocess.DEVNULL and run.call_args.kwargs["stderr"] is cd.subprocess.DEVNULL
|
||||
assert args[:5] == ["bao", "kv", "metadata", "patch", "-mount=platform"] and args[-1] == "workloads/a/b"
|
||||
assert not any(a in ("get", "put") or "/data/" in a for a in args)
|
||||
r = tmp_path / "r.json"
|
||||
assert json.load(open(r)) == {"platform/workloads/a/b": "patched", "platform/workloads/c/d": "skipped", "operators/e": "skipped"}
|
||||
assert stat.S_IMODE(os.stat(r).st_mode) == 0o600
|
||||
|
||||
|
||||
def test_apply_reports_failure_without_output(tmp_path, capsys):
|
||||
def test_patch_refused_falls_back_to_merge_keeping_existing_metadata(tmp_path):
|
||||
existing = json.dumps({"data": {"custom_metadata": {"ccr": "CCR-2026-0033", "owner": "old"}}})
|
||||
calls = [_r(2, err="Error ... Code: 403. Errors: permission denied"), _r(0, out=existing), _r(0)]
|
||||
doc = {"platform/workloads/a/b": {"review": "accepted", **FULL}}
|
||||
with patch.object(cd.subprocess, "run", return_value=MagicMock(returncode=2)):
|
||||
assert cd.apply(doc, receipt=str(tmp_path / 'r.json')) == 1
|
||||
assert capsys.readouterr() == ('', '')
|
||||
with patch.object(cd.subprocess, "run", side_effect=calls) as run:
|
||||
assert cd.apply(doc, receipt=str(tmp_path / "r.json")) == 0
|
||||
get_args, put_args = run.call_args_list[1].args[0], run.call_args_list[2].args[0]
|
||||
assert get_args[1:4] == ["kv", "metadata", "get"]
|
||||
assert put_args[1:4] == ["kv", "metadata", "put"]
|
||||
assert "-custom-metadata=ccr=CCR-2026-0033" in put_args and "-custom-metadata=owner=o" in put_args
|
||||
assert json.load(open(tmp_path / "r.json")) == {"platform/workloads/a/b": "merged"}
|
||||
|
||||
|
||||
def test_failure_records_only_http_code(tmp_path, capsys):
|
||||
doc = {"platform/workloads/a/b": {"review": "accepted", **FULL}}
|
||||
with patch.object(cd.subprocess, "run", return_value=_r(2, err="Code: 500. secret-ish detail")):
|
||||
assert cd.apply(doc, receipt=str(tmp_path / "r.json")) == 1
|
||||
assert capsys.readouterr() == ("", "")
|
||||
assert json.load(open(tmp_path / "r.json")) == {"platform/workloads/a/b": "failed patch http=500"}
|
||||
|
||||
|
||||
def test_check_refuses_drafts_missing_and_overlong_fields():
|
||||
|
|
@ -47,5 +66,16 @@ def test_check_refuses_drafts_missing_and_overlong_fields():
|
|||
assert cd.check({"p/ok": doc["p/ok"]}) == 0
|
||||
|
||||
|
||||
def test_clip_respects_openbao_value_limit():
|
||||
assert len(cd._clip("y" * 2000)) == cd.MAX_VALUE
|
||||
def test_clip_respects_openbao_byte_limit():
|
||||
assert len(cd._clip("y" * 2000).encode()) <= cd.MAX_VALUE
|
||||
assert len(cd._clip("→" * 400).encode()) <= cd.MAX_VALUE
|
||||
assert cd.check({"p/x": {"review": "accepted", **dict(FULL, used_by="y" * 510 + "…")}}) == 1
|
||||
|
||||
|
||||
def test_receipt_ignores_temporary_home_and_bao_addr_defaults(tmp_path, monkeypatch):
|
||||
assert cd.RECEIPT.startswith(pwd.getpwuid(os.getuid()).pw_dir)
|
||||
monkeypatch.delenv("BAO_ADDR", raising=False)
|
||||
doc = {"platform/workloads/a/b": {"review": "accepted", **FULL}}
|
||||
with patch.object(cd.subprocess, "run", return_value=_r()) as run:
|
||||
cd.apply(doc, receipt=str(tmp_path / "r.json"))
|
||||
assert run.call_args.kwargs["env"]["BAO_ADDR"] == "http://127.0.0.1:18200"
|
||||
|
|
|
|||
|
|
@ -122,6 +122,14 @@ then read `~/.local/state/railiance/receipts/custody-describe.json`.
|
|||
(First attempt 2026-10-09 refused by the wrapper because apply printed status
|
||||
lines; nothing written, all entries were still drafts. apply is now silent.)
|
||||
|
||||
2026-10-09 founder runs 3-4: the wrapper has a temporary HOME and the admin
|
||||
policy has no `patch` capability, so apply now writes its receipt under the
|
||||
real home and falls back to metadata get + merged put. Run 4 receipt: 27 of 29
|
||||
accepted entries `merged` (existing custom_metadata kept), 2 `failed put
|
||||
http=400` (`core-hub/forgejo-source-read`, `secrets-engine/approval-client`):
|
||||
`used_by` was 514 bytes, clipped by characters. Clipping is now by bytes; those
|
||||
two need one more run.
|
||||
|
||||
## Resolve the rest with their owners
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue