Executor credentials declared (planned) with executor_subject; service-identity gains convention executor (SI-15: approved-only grants, no person, not interactive) and --admissions, checking that an admission's ceiling lies inside the executor's declared grants (pilot covered). RBAC in sso-mfa/k8s/bounded-executor (not applied): ServiceAccount without automount, a Role limited to the authelia Deployment and ExternalSecret, no read of credential objects; tests tie Role, declaration and pilot ceiling. The pilot example's delivery check now reads the ExternalSecret status. railiance-apps and glas-harness answers recorded (T02). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 994874@bnt-lap001 Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
67 lines
3.2 KiB
Python
67 lines
3.2 KiB
Python
"""The bounded executor's Kubernetes authority is exactly the pilot's (NK-WP-0056-T03). Source only, no cluster."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import unittest
|
|
|
|
import yaml
|
|
|
|
HERE = pathlib.Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[2]
|
|
DOCS = list(yaml.safe_load_all((HERE / "rbac.yaml").read_text()))
|
|
DECLARATION = yaml.safe_load((ROOT / "tools/service-identity/declarations/bounded-executor.yaml").read_text())
|
|
PILOT = yaml.safe_load((ROOT / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml").read_text())
|
|
KIND_SCOPE = {("apps", "deployments"): "deployment", ("external-secrets.io", "externalsecrets"): "externalsecret"}
|
|
FORBIDDEN_RESOURCES = {"secrets", "configmaps", "pods", "pods/exec", "pods/log", "serviceaccounts", "serviceaccounts/token",
|
|
"roles", "rolebindings", "clusterroles", "clusterrolebindings", "*"}
|
|
|
|
|
|
def by_kind(kind):
|
|
return [d for d in DOCS if d["kind"] == kind]
|
|
|
|
|
|
def role_scopes():
|
|
(role,) = by_kind("Role")
|
|
return {f"k8s/sso/{KIND_SCOPE[(group, resource)]}/{name}"
|
|
for rule in role["rules"] for group in rule["apiGroups"] for resource in rule["resources"]
|
|
for name in rule["resourceNames"]}
|
|
|
|
|
|
class RbacTests(unittest.TestCase):
|
|
def test_only_namespaced_objects_in_sso(self):
|
|
self.assertEqual(sorted(d["kind"] for d in DOCS), ["Role", "RoleBinding", "ServiceAccount"])
|
|
self.assertTrue(all(d["metadata"]["namespace"] == "sso" for d in DOCS))
|
|
|
|
def test_service_account_does_not_automount(self):
|
|
(sa,) = by_kind("ServiceAccount")
|
|
self.assertEqual(sa["metadata"]["name"], "nk-bind-executor")
|
|
self.assertIs(sa["automountServiceAccountToken"], False)
|
|
|
|
def test_every_rule_is_named_and_narrow(self):
|
|
(role,) = by_kind("Role")
|
|
for rule in role["rules"]:
|
|
with self.subTest(rule=rule["resources"]):
|
|
self.assertTrue(rule.get("resourceNames"), "every rule names its objects")
|
|
for field in ("apiGroups", "resources", "verbs", "resourceNames"):
|
|
self.assertNotIn("*", rule[field])
|
|
self.assertLessEqual(set(rule["verbs"]), {"get", "watch", "patch"})
|
|
self.assertFalse(set(rule["resources"]) & FORBIDDEN_RESOURCES)
|
|
self.assertTrue(set(rule["resources"]) <= {r for _, r in KIND_SCOPE})
|
|
|
|
def test_binding_binds_only_the_executor(self):
|
|
(binding,) = by_kind("RoleBinding")
|
|
self.assertEqual(binding["roleRef"], {"apiGroup": "rbac.authorization.k8s.io", "kind": "Role", "name": "nk-bind-executor"})
|
|
self.assertEqual(binding["subjects"], [{"kind": "ServiceAccount", "name": "nk-bind-executor", "namespace": "sso"}])
|
|
|
|
def test_role_equals_declaration_equals_pilot_ceiling(self):
|
|
(sa_identity,) = [i for i in DECLARATION["identities"] if i["name"] == "nk-bind-executor"]
|
|
declared = {g["scope"] for g in sa_identity["grants"]}
|
|
ceiling = {g["scope"] for g in PILOT["spec"]["scope_ceiling"] if g["scope"].startswith("k8s/")}
|
|
self.assertEqual(role_scopes(), declared)
|
|
self.assertEqual(declared, ceiling)
|
|
self.assertEqual(PILOT["spec"]["executor"]["identity"], sa_identity["executor_subject"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|