Window-scoped write role (CCR-2026-0058 approved as amended), bind self-rotation (pilot step is OPERATE on the one bind; svc-executor-pw dropped), pilot without the PDP gate (not-evaluated), started event plus one receipt per run, rapp Job in sso, owner workplans approved. New tasks: T11 core runner, T12 pilot operations and packaging; T06 depends on T12. 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
144 lines
13 KiB
Python
144 lines
13 KiB
Python
"""Regenerate examples/bounded-execution-admission/ and the human-intrinsic invalid fixture (NK-WP-0056-T01).
|
|
|
|
Pins the named files at the current commit and computes plan_digest. Run after changing those files or the schema.
|
|
"""
|
|
import hashlib, json, pathlib, subprocess, sys, yaml
|
|
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "tools/bounded-execution-admission"))
|
|
import validate as V
|
|
commit = subprocess.run(["git", "-C", str(ROOT), "rev-parse", "--short=12", "HEAD"], capture_output=True, text=True).stdout.strip()
|
|
def pin(*paths): return [{"path": p, "sha256": hashlib.sha256((ROOT / p).read_bytes()).hexdigest()} for p in paths]
|
|
def sha(p): return hashlib.sha256((ROOT / p).read_bytes()).hexdigest()
|
|
JOURNEY = "sso-mfa/k8s/synthetic-journey/journey.py"
|
|
LANE = "tools/service-binds/bind-lane.py"
|
|
SPLIT = "sso-mfa/k8s/lldap/bind-split.py"
|
|
COMMON = {
|
|
"approval": {"approvers": ["admin"], "principal_type": "HUMAN", "assurance": "aal2",
|
|
"carriers": ["approval-engine", "founder-ssh-signature"]},
|
|
"consumption": {"single_use": True, "consume_before_first_step": True},
|
|
"receipt": {"schema": "netkingdom.playbook-capability-contract/v0.2#execution-attribution-receipt",
|
|
"emit_to": "audit-core", "retain": "by-hash-no-secret-values",
|
|
"independent_verifier": "tools/bounded-execution-admission/validate.py check-approval + receipt source hashes"},
|
|
}
|
|
EXECUTOR = {"identity": "service:nk-bind-executor", "principal_type": "WORKLOAD",
|
|
"runtime": "k8s sso/serviceaccount nk-bind-executor (planned, NK-WP-0056-T03)"}
|
|
P = ["PRODUCTION"]
|
|
def check(cid, desc, ref, timeout, settle=0):
|
|
c = {"id": cid, "description": desc, "ref": ref, "sha256": sha(ref), "timeout_seconds": timeout, "expect": "pass"}
|
|
if settle: c["settle_seconds"] = settle
|
|
return c
|
|
def doc(id_, title, purpose, risk, window, ceiling, radius, files, steps, reverts, checks, policy, budget, not_auth, trust=("runtime_secret_trust", "runtime_identity_trust")):
|
|
spec = {"id": id_, "title": title, "purpose": purpose, "work_ref": "NK-WP-0056-T01 example", "risk_class": risk,
|
|
"window": window, **{"approval": COMMON["approval"]}, "executor": EXECUTOR, "scope_ceiling": ceiling,
|
|
"blast_radius": radius, "trust_requires": list(trust),
|
|
"plan": {"source": {"repo": "net-kingdom", "commit": commit, "files": pin(*files)}, "steps": steps,
|
|
**({"revert_steps": reverts} if reverts else {})},
|
|
"plan_digest": "", "checks": checks, "stop_and_revert": policy, "budget": budget,
|
|
"consumption": COMMON["consumption"], "receipt": COMMON["receipt"], "not_authorized": not_auth}
|
|
d = {"apiVersion": "netkingdom.io/bounded-execution-admission/v0.1", "kind": "BoundedExecutionAdmission", "spec": spec}
|
|
spec["plan_digest"] = V.plan_digest(d)
|
|
return d
|
|
def step(id_, op, target, consumer, cls, mutates, mode, scope, checks=None, revert=None, irreversible=None):
|
|
s = {"id": id_, "operation": op, "target": target, "consumer": consumer, "class": cls, "mutates": mutates,
|
|
"requires": {"mode": mode, "scope": scope}}
|
|
if checks: s["checks_after"] = checks
|
|
if revert: s["revert"] = revert
|
|
if irreversible: s["irreversible"] = True
|
|
return s
|
|
|
|
KV = "openbao/platform/workloads/net-kingdom/directory-binds/svc-authelia"
|
|
rotation = doc(
|
|
"BEA-2026-0001", "Rotate the svc-authelia directory bind (bind-split steps 5-8, one consumer)",
|
|
"Pilot shape for NK-WP-0056-T06: generate a new bind password into OpenBao, set it in LLDAP, let ESO deliver it, restart Authelia, prove sign-in with the synthetic journey; revert on any failed check.",
|
|
"medium", {"not_before": "2026-11-03T09:00:00Z", "not_after": "2026-11-03T11:00:00Z"},
|
|
[{"mode": "OPERATE", "scope": KV, "environments": P},
|
|
{"mode": "OPERATE", "scope": "lldap/people/svc-authelia", "environments": P},
|
|
{"mode": "OPERATE", "scope": "k8s/sso/externalsecret/authelia-secrets", "environments": P},
|
|
{"mode": "OPERATE", "scope": "k8s/sso/deployment/authelia", "environments": P}],
|
|
{"unit": "consumer", "consumers": ["authelia"], "max_concurrent": 1},
|
|
[LANE, JOURNEY],
|
|
[step("generate", "openbao.kv.write-next-version", "openbao:" + KV[len("openbao/"):], "authelia", "delegable", True, "OPERATE", KV, ["kv-advanced"], "restore-kv"),
|
|
step("set-directory", "lldap.user.change-own-password-from-kv", "lldap:svc-authelia", "authelia", "delegable", True, "OPERATE", "lldap/people/svc-authelia", ["bind-signs-in"], "restore-directory"),
|
|
step("deliver", "eso.force-sync", "k8s:sso/externalsecret/authelia-secrets", "authelia", "delegable", True, "OPERATE", "k8s/sso/externalsecret/authelia-secrets", ["eso-synced"], "redeliver"),
|
|
step("restart", "k8s.rollout-restart", "k8s:sso/deployment/authelia", "authelia", "delegable", True, "OPERATE", "k8s/sso/deployment/authelia", ["synthetic-journey"], "restart-again")],
|
|
[step("restore-kv", "openbao.kv.write-previous-value-as-next-version", "openbao:" + KV[len("openbao/"):], "authelia", "delegable", True, "OPERATE", KV),
|
|
step("restore-directory", "lldap.user.change-own-password-from-kv", "lldap:svc-authelia", "authelia", "delegable", True, "OPERATE", "lldap/people/svc-authelia"),
|
|
step("redeliver", "eso.force-sync", "k8s:sso/externalsecret/authelia-secrets", "authelia", "delegable", True, "OPERATE", "k8s/sso/externalsecret/authelia-secrets"),
|
|
step("restart-again", "k8s.rollout-restart", "k8s:sso/deployment/authelia", "authelia", "delegable", True, "OPERATE", "k8s/sso/deployment/authelia")],
|
|
[check("kv-advanced", "KV svc-authelia has exactly one new version, CAS on the previous one", LANE, 30),
|
|
check("bind-signs-in", "the new KV value signs in to LLDAP as svc-authelia", LANE, 30),
|
|
check("eso-synced", "the ExternalSecret is Ready with a refresh after the step started; the executor never reads the Secret", LANE, 120),
|
|
check("synthetic-journey", "syn-journey signs in through KeyCape, Authelia and privacyIDEA at aal2", JOURNEY, 90)],
|
|
{"on_check_failure": "revert", "on_budget_exhausted": "revert", "on_unknown": "stop",
|
|
"on_revert_failure": "stop-and-alert", "alert": "founder via State Hub message and audit-core"},
|
|
{"wall_time_seconds": 1200, "attempts": 2, "cost": {"compute_cpu_seconds": 120, "egress_bytes": 5000000, "currency": "EUR", "amount": 0.05}},
|
|
["any bind other than svc-authelia", "any LLDAP group change", "changing LLDAP's configured super-admin",
|
|
"reading or changing any human account", "any change outside the sso namespace"])
|
|
|
|
create = doc(
|
|
"BEA-2026-0002", "Create the service bind accounts (bind-split step 3, root-class)",
|
|
"Create svc-authelia, svc-keycape and svc-privacyidea in LLDAP as read-only binds. Needs lldap_admin: LLDAP has no narrower grant that can create users (SI-X6), so the founder admits this run and the executor runs it.",
|
|
"high", {"not_before": "2026-11-03T09:00:00Z", "not_after": "2026-11-03T12:00:00Z"},
|
|
[{"mode": "ADMINISTER", "scope": "lldap", "environments": P},
|
|
{"mode": "OPERATE", "scope": "openbao/platform/workloads/net-kingdom/directory-binds", "environments": P}],
|
|
{"unit": "account", "consumers": ["lldap"], "max_concurrent": 1},
|
|
[SPLIT, LANE],
|
|
[step("create-accounts", "lldap.user.create-read-only-binds", "lldap:svc-authelia,svc-keycape,svc-privacyidea", "lldap", "root-class", True, "ADMINISTER", "lldap", ["accounts-read-only"], "delete-accounts"),
|
|
step("hold-passwords", "openbao.kv.write-cas0", "openbao:platform/workloads/net-kingdom/directory-binds", "lldap", "root-class", True, "OPERATE", "openbao/platform/workloads/net-kingdom/directory-binds", ["accounts-sign-in"], "destroy-kv")],
|
|
[step("delete-accounts", "lldap.user.delete-created", "lldap:svc-authelia,svc-keycape,svc-privacyidea", "lldap", "root-class", True, "ADMINISTER", "lldap"),
|
|
step("destroy-kv", "openbao.kv.destroy-created", "openbao:platform/workloads/net-kingdom/directory-binds", "lldap", "root-class", True, "OPERATE", "openbao/platform/workloads/net-kingdom/directory-binds")],
|
|
[check("accounts-read-only", "each account exists in lldap_strict_readonly only and cannot write", SPLIT, 60),
|
|
check("accounts-sign-in", "each KV value signs in to LLDAP as its account", LANE, 60)],
|
|
{"on_check_failure": "revert", "on_budget_exhausted": "revert", "on_unknown": "stop",
|
|
"on_revert_failure": "stop-and-alert", "alert": "founder via State Hub message"},
|
|
{"wall_time_seconds": 900, "attempts": 1, "cost": {"compute_cpu_seconds": 60, "egress_bytes": 1000000, "currency": "EUR", "amount": 0.02}},
|
|
["switching any consumer to the new accounts", "any human account", "changing LLDAP's configured super-admin"])
|
|
|
|
superadmin = doc(
|
|
"BEA-2026-0003", "Switch LLDAP's configured super-admin to ld-admin (bind-split step 9, root-class)",
|
|
"Change LLDAP_LDAP_USER_DN from admin to ld-admin and restart LLDAP; admin keeps its rights through lldap_admin membership. Changes who administers the directory, so the founder admits the run.",
|
|
"high", {"not_before": "2026-11-03T09:00:00Z", "not_after": "2026-11-03T11:00:00Z"},
|
|
[{"mode": "ADMINISTER", "scope": "lldap", "environments": P},
|
|
{"mode": "OPERATE", "scope": "k8s/sso/deployment/lldap", "environments": P}],
|
|
{"unit": "consumer", "consumers": ["lldap"], "max_concurrent": 1},
|
|
[SPLIT, JOURNEY],
|
|
[step("switch-user-dn", "lldap.config.set-ldap-user-dn", "k8s:sso/secret/lldap-secrets#LLDAP_LDAP_USER_DN", "lldap", "root-class", True, "ADMINISTER", "lldap", ["ld-admin-administers"], "switch-back"),
|
|
step("restart-lldap", "k8s.rollout-restart", "k8s:sso/deployment/lldap", "lldap", "root-class", True, "OPERATE", "k8s/sso/deployment/lldap", ["synthetic-journey"], "restart-lldap-again")],
|
|
[step("switch-back", "lldap.config.set-ldap-user-dn", "k8s:sso/secret/lldap-secrets#LLDAP_LDAP_USER_DN", "lldap", "root-class", True, "ADMINISTER", "lldap"),
|
|
step("restart-lldap-again", "k8s.rollout-restart", "k8s:sso/deployment/lldap", "lldap", "root-class", True, "OPERATE", "k8s/sso/deployment/lldap")],
|
|
[check("ld-admin-administers", "ld-admin signs in and administers; admin keeps lldap_admin membership", SPLIT, 60),
|
|
check("synthetic-journey", "all three binds still serve sign-in: syn-journey reaches aal2", JOURNEY, 90, settle=125)],
|
|
{"on_check_failure": "revert", "on_budget_exhausted": "revert", "on_unknown": "stop",
|
|
"on_revert_failure": "stop-and-alert", "alert": "founder via State Hub message; crisis list item for ld-admin"},
|
|
{"wall_time_seconds": 900, "attempts": 1, "cost": {"compute_cpu_seconds": 60, "egress_bytes": 1000000, "currency": "EUR", "amount": 0.02}},
|
|
["changing admin's own password", "sealing ld-admin's password (human-intrinsic, step 10)", "any service bind change"])
|
|
|
|
verify = doc(
|
|
"BEA-2026-0004", "Read-only verification of the directory split (bind-split steps 2, 4 and 10 verify)",
|
|
"Plan and verify reads that the founder used to do signed in as admin: account inventory, group membership, bind identities of the consumers. No mutation, so nothing to revert.",
|
|
"low", {"not_before": "2026-11-03T09:00:00Z", "not_after": "2026-11-04T09:00:00Z"},
|
|
[{"mode": "OBSERVE", "scope": "lldap/people", "environments": P},
|
|
{"mode": "OBSERVE", "scope": "k8s/sso", "environments": P}],
|
|
{"unit": "consumer", "consumers": ["lldap", "authelia", "keycape", "privacyidea"], "max_concurrent": 4},
|
|
[SPLIT],
|
|
[step("inventory", "lldap.read.users-and-groups", "lldap:people", "lldap", "delegable", False, "OBSERVE", "lldap/people", ["split-shape"]),
|
|
step("consumer-binds", "k8s.read.bind-identities", "k8s:sso", "authelia", "delegable", False, "OBSERVE", "k8s/sso", ["split-shape"])],
|
|
[],
|
|
[check("split-shape", "every consumer binds as its own svc- account; no service binds as admin", SPLIT, 60)],
|
|
{"on_check_failure": "stop", "on_budget_exhausted": "stop", "on_unknown": "stop", "on_revert_failure": "stop-and-alert"},
|
|
{"wall_time_seconds": 300, "attempts": 3, "cost": {"compute_cpu_seconds": 30, "egress_bytes": 500000, "currency": "EUR", "amount": 0.01}},
|
|
["any write to LLDAP, Kubernetes or OpenBao"])
|
|
|
|
out = ROOT / "examples/bounded-execution-admission"
|
|
for name, d in [("bind-rotation-svc-authelia", rotation), ("bind-split-create-service-accounts", create),
|
|
("bind-split-switch-super-admin", superadmin), ("bind-split-read-only-verify", verify)]:
|
|
(out / f"{name}.yaml").write_text("# NK-WP-0056-T01 example: the bind split (NK-WP-0054) as admissions. Illustrative: the executor\n"
|
|
"# (T03) and its operations do not exist yet; files are pinned at the commit named in plan.source.\n"
|
|
+ yaml.safe_dump(d, sort_keys=False, width=120))
|
|
# Invalid: step 10, the founder's own password, is human-intrinsic.
|
|
bad = json.loads(json.dumps(superadmin)); s = bad["spec"]; s["id"] = "BEA-2026-0099"
|
|
s["plan"]["steps"].append(step("change-admin-password", "lldap.user.set-password", "lldap:admin", "lldap", "human-intrinsic", True, "ADMINISTER", "lldap", ["ld-admin-administers"], "switch-back"))
|
|
s["plan_digest"] = V.plan_digest(bad)
|
|
fx = ROOT / "tools/bounded-execution-admission/fixtures"; (fx / "invalid").mkdir(parents=True, exist_ok=True)
|
|
(fx / "invalid/human-intrinsic-step.yaml").write_text(yaml.safe_dump(bad, sort_keys=False, width=120))
|
|
print("commit", commit)
|