railiance-platform/tests/test_service_identity_metadata_export.py
codex bd8ca57dc9
All checks were successful
CCR intake gate / gate (push) Successful in 2m10s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
RPF-WP-0067: owner answers for NK-WP-0040-T02 and value-safe identity metadata exporter for NK-WP-0054-T04
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:52:15 +01:00

173 lines
8.3 KiB
Python

import contextlib
import importlib.util
import io
import json
import shutil
import tempfile
import unittest
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location("si_export", ROOT / "scripts/service_identity_metadata_export.py")
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
NOW = lambda: datetime(2026, 10, 28, 12, 0, tzinfo=timezone.utc) # noqa: E731
# Source-derived fixture: the committed role and policy files, laid out at their live mount paths.
SOURCE_ROLES = {
("netkingdom", "platform-admin"): "netkingdom-platform-admin-role.json",
("netkingdom", "credential-broker-keycape-client-secret-generate"):
"credential-broker-keycape-client-secret-generate-oidc-role.json",
("netkingdom", "fluid-telegram-hall-of-helix-workload-kv"): "fluid-telegram-hall-of-helix-oidc-role.json",
("netkingdom", "coding-agent"): "coding-agent-jwt-role.json",
("keycape-services", "secrets-engine-openbao"): "secrets-engine-jwt-role.json",
("approle", "coding-agent"): "coding-agent-approle.json",
}
def source_fixture(tmp: Path) -> Path:
(tmp / "auth-list.json").write_text(json.dumps({
"netkingdom/": {"type": "oidc"}, "keycape-services/": {"type": "jwt"},
"approle/": {"type": "approle"}, "token/": {"type": "token"}}))
for (mount, name), filename in SOURCE_ROLES.items():
folder = tmp / "roles" / mount
folder.mkdir(parents=True, exist_ok=True)
shutil.copy(ROOT / "openbao/auth" / filename, folder / f"{name}.json")
(tmp / "policies").mkdir()
for policy in (ROOT / "openbao/policies").glob("*.hcl"):
shutil.copy(policy, tmp / "policies" / policy.name)
return tmp
def run(argv):
out, err = io.StringIO(), io.StringIO()
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
code = m.main(argv, now=NOW)
return code, out.getvalue(), err.getvalue()
class OpenBaoExportTests(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.tmp)
def export(self):
code, out, err = run(["openbao", "--fixture-dir", str(source_fixture(self.tmp))])
self.assertEqual(0, code, err)
return json.loads(out)
def role(self, doc, mount, name):
return next(r for r in doc["openbao"]["roles"] if r["mount"] == mount and r["name"] == name)
def test_envelope_and_mounts(self):
doc = self.export()
self.assertEqual(m.SCHEMA, doc["schema"])
self.assertEqual("fixture", doc["source_kind"])
self.assertEqual("2026-10-28T12:00:00Z", doc["collected_at"])
self.assertIn({"path": "token", "type": "token"}, doc["openbao"]["auth_mounts"])
self.assertEqual(6, len(doc["openbao"]["roles"]))
def test_fluid_telegram_policy_is_exact_kv_operator_matrix(self):
role = self.role(self.export(), "netkingdom", "fluid-telegram-hall-of-helix-workload-kv")
self.assertEqual({"groups": ["fluid-telegram-operators"], "/assurance/level": "aal2"}, role["bound_claims"])
self.assertEqual(900, role["token_ttl_seconds"])
self.assertEqual(1800, role["token_explicit_max_ttl_seconds"])
rules = {r["path"]: r["capabilities"] for r in role["effective_rules"]}
base = "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/"
self.assertEqual(["read"], rules[base + "operator-app"])
self.assertEqual(["create", "read", "update"], rules[base + "bot-token"])
self.assertFalse(any("metadata" in p or "delete" in c for p, c in rules.items()))
self.assertTrue(role["effective_complete"])
def test_coding_agent_deny_overrides_issue_core_runtime_read(self):
doc = self.export()
for mount in ("netkingdom", "approle"):
role = self.role(doc, mount, "coding-agent")
rules = {r["path"]: r["capabilities"] for r in role["effective_rules"]}
self.assertEqual(["deny"], rules["platform/data/workloads/issue-core/issue-core/issue-core-runtime"])
readable_data = [p for p, c in rules.items() if "/data/" in p and c != ["deny"]]
self.assertEqual([], readable_data)
self.assertEqual(900, role["token_max_ttl_seconds"])
jwt = self.role(doc, "netkingdom", "coding-agent")
self.assertEqual(["service", "agent"], jwt["bound_claims"]["principal_type"])
def test_live_shape_drops_unlisted_fields_and_normalizes_ttls(self):
view = m.role_view("approle", "approle", "x", {
"token_ttl": 900, "secret_id_ttl": 300, "role_id": "ignored", "secret_id_accessor": "ignored",
"policies": ["p"], "local_secret_ids": False})
self.assertNotIn("role_id", view)
self.assertNotIn("secret_id_accessor", view)
self.assertEqual(["p"], view["token_policies"])
self.assertEqual(300, view["secret_id_ttl_seconds"])
def test_secret_shaped_value_refuses_without_echo(self):
fixture = source_fixture(self.tmp)
token = "hvs." + "A1b2C3d4" * 4
path = fixture / "roles/netkingdom/coding-agent.json"
data = json.loads(path.read_text())
data["bound_claims"]["sub"] = token
path.write_text(json.dumps(data))
code, out, err = run(["openbao", "--fixture-dir", str(fixture)])
self.assertEqual(3, code)
self.assertEqual("", out)
self.assertNotIn(token, err)
self.assertIn("1 secret-shaped", err)
def test_unreadable_policy_marks_role_incomplete(self):
fixture = source_fixture(self.tmp)
(fixture / "policies/agent-high-risk-boundary.hcl").unlink()
code, out, _ = run(["openbao", "--fixture-dir", str(fixture)])
role = self.role(json.loads(out), "netkingdom", "coding-agent")
self.assertFalse(role["effective_complete"])
def test_names_and_paths_are_not_false_positives(self):
for value in ("credential-broker-keycape-client-secret-generate",
"platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session",
"http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"):
self.assertFalse(m.looks_secret(value), value)
self.assertTrue(m.looks_secret("SCRAM-SHA-256$4096:abc"))
self.assertTrue(m.looks_secret("Zx9" * 12))
class PostgresExportTests(unittest.TestCase):
ROW = json.dumps({
"server_version": "16.4",
"roles": [
{"name": "postgres", "superuser": True, "create_role": True, "create_db": True, "can_login": True,
"replication": True, "bypass_rls": True, "inherit": True, "connection_limit": -1,
"valid_until": None, "member_of": []},
{"name": "privacyidea", "superuser": False, "create_role": False, "create_db": False,
"can_login": True, "replication": False, "bypass_rls": False, "inherit": True,
"connection_limit": -1, "valid_until": None, "member_of": [], "rolpassword": "********"}],
"databases": [{"name": "privacyidea_db", "owner": "privacyidea"}]})
def test_export_projects_only_role_attributes(self):
tmp = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, tmp)
(tmp / "row.txt").write_text(self.ROW + "\n")
code, out, err = run(["postgresql", "--cluster", "net-kingdom-pg", "--input", str(tmp / "row.txt")])
self.assertEqual(0, code, err)
doc = json.loads(out)["postgresql"]
self.assertEqual("net-kingdom-pg", doc["cluster"])
pi = next(r for r in doc["roles"] if r["name"] == "privacyidea")
self.assertFalse(pi["superuser"])
self.assertNotIn("rolpassword", pi)
self.assertEqual([{"name": "privacyidea_db", "owner": "privacyidea"}], doc["databases"])
def test_sql_is_read_only_and_never_touches_password_catalog(self):
code, out, _ = run(["postgresql-sql"])
self.assertEqual(0, code)
sql = out.lower()
self.assertTrue(sql.startswith("select "))
for word in ("pg_authid", "rolpassword", "insert", "update ", "delete", "alter", "grant ", ";"):
self.assertNotIn(word, sql)
def test_rejects_multi_row_input(self):
code, _, err = run(["postgresql", "--cluster", "c", "--input", "/dev/null"])
self.assertEqual(2, code)
self.assertIn("ValueError", err)
if __name__ == "__main__":
unittest.main()