Add the scoped ops-mason-builder ClusterRole

Assistant: grok
Assistant-Session: 01a1625d-476f-7373-9e10-956ff3ac4333
This commit is contained in:
codex 2026-10-22 08:48:15 +02:00
parent c27f50b00e
commit bd6660a916
4 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,29 @@
# Scoped ops-mason builder (founder decision 2026-10-22, MASON-IN-0006 (b)).
# Kinds kubernetes_plane applies, plus get/list/watch on services for dependency
# reads, plus create on self-subject access reviews so kubectl auth can-i works.
# Excluded: pods, secrets, configmaps, serviceaccounts/token, impersonate,
# roles, rolebindings, clusterroles, clusterrolebindings, nodes/proxy, pods/exec,
# escalate, bind, wildcards.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ops-mason-builder
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: ops-mason-builder
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["authorization.k8s.io"]
resources: ["selfsubjectaccessreviews", "selfsubjectrulesreviews"]
verbs: ["create"]

View file

@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ops-mason-builder
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: ops-mason-builder
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ops-mason-builder
subjects:
- kind: User
name: ops-mason-builder
apiGroup: rbac.authorization.k8s.io

View file

@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# MASON-IN-0006 choice (b), decision 30d29060-8d14-4a8e-9cde-34aa826eb8d1.
# Scoped builder for ops-mason. User ops-mason-builder, no ServiceAccount token,
# no Secret. Applied by argocd/railiance01/applications/ops-mason-builder.application.yaml.
# Manual sync. targetRevision is pinned to the commit that introduced these manifests.
resources:
- clusterrole.yaml
- clusterrolebinding.yaml

View file

@ -0,0 +1,89 @@
"""Offline guard for the ops-mason builder identity (MASON-IN-0006 choice (b)).
Parses the GitOps manifests and asserts the founder's 2026-10-22 boundary:
the five granted rules only, no forbidden resources, no escalate/bind/impersonate,
no wildcards, and a User subject with no ServiceAccount.
"""
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
ADDON = ROOT / "argocd/platform-addons/ops-mason-builder"
WRITE_VERBS = ["create", "get", "list", "watch", "update", "patch", "delete"]
READ_VERBS = ["get", "list", "watch"]
FORBIDDEN_RESOURCES = {
"secrets", "pods", "serviceaccounts/token", "configmaps", "roles",
"rolebindings", "clusterroles", "clusterrolebindings", "nodes",
"nodes/proxy", "pods/exec", "pods/log",
}
FORBIDDEN_VERBS = {"escalate", "bind", "impersonate"}
EXPECTED_RULES = [
{"apiGroups": [""], "resources": ["namespaces"], "verbs": WRITE_VERBS},
{"apiGroups": [""], "resources": ["serviceaccounts"], "verbs": WRITE_VERBS},
{"apiGroups": ["networking.k8s.io"], "resources": ["networkpolicies"],
"verbs": WRITE_VERBS},
{"apiGroups": [""], "resources": ["services"], "verbs": READ_VERBS},
{"apiGroups": ["authorization.k8s.io"],
"resources": ["selfsubjectaccessreviews", "selfsubjectrulesreviews"],
"verbs": ["create"]},
]
def _docs():
kust = yaml.safe_load((ADDON / "kustomization.yaml").read_text())
docs = []
for res in kust["resources"]:
docs.extend(d for d in yaml.safe_load_all((ADDON / res).read_text()) if d)
return docs
def _one(kind):
found = [d for d in _docs() if d["kind"] == kind]
assert len(found) == 1, kind
return found[0]
def test_only_expected_kinds_no_secret():
kinds = sorted(d["kind"] for d in _docs())
assert kinds == ["ClusterRole", "ClusterRoleBinding"]
for path in ADDON.iterdir():
if path.is_file():
assert "kind: Secret" not in path.read_text(), path
def test_cluster_role_exact_rules_no_wildcards():
role = _one("ClusterRole")
assert role["metadata"]["name"] == "ops-mason-builder"
assert "aggregationRule" not in role
assert role["rules"] == EXPECTED_RULES
granted = set()
for rule in role["rules"]:
values = [*rule["verbs"], *rule.get("apiGroups", []), *rule.get("resources", [])]
assert not any("*" in v for v in values), rule
assert FORBIDDEN_VERBS.isdisjoint(rule["verbs"]), rule
for resource in rule["resources"]:
assert resource not in FORBIDDEN_RESOURCES, resource
granted.add(resource)
assert granted == {
"namespaces", "serviceaccounts", "networkpolicies", "services",
"selfsubjectaccessreviews", "selfsubjectrulesreviews",
}
by_resource = {r["resources"][0]: r["verbs"] for r in role["rules"] if len(r["resources"]) == 1}
assert by_resource["namespaces"] == WRITE_VERBS
assert by_resource["serviceaccounts"] == WRITE_VERBS
assert by_resource["networkpolicies"] == WRITE_VERBS
assert by_resource["services"] == READ_VERBS
review = next(r for r in role["rules"] if "selfsubjectaccessreviews" in r["resources"])
assert review["verbs"] == ["create"]
assert review["resources"] == ["selfsubjectaccessreviews", "selfsubjectrulesreviews"]
def test_binding_targets_only_the_user():
binding = _one("ClusterRoleBinding")
assert binding["roleRef"]["name"] == "ops-mason-builder"
assert binding["roleRef"]["kind"] == "ClusterRole"
assert binding["subjects"] == [{"kind": "User", "name": "ops-mason-builder",
"apiGroup": "rbac.authorization.k8s.io"}]
assert all(s["kind"] != "ServiceAccount" for s in binding["subjects"])