From cdf0d9bf2526692529b270386a4e271ffcde0388 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 31 Oct 2026 15:42:30 +0100 Subject: [PATCH] NK-WP-0056: founder decisions folded in; runtime split into T11 and T12 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 Assistant: claude-code Assistant-Model: opus Assistant-Process: 994874@bnt-lap001 Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87 --- WORK-RECORDS.md | 2 + .../bounded-execution-admission_v0.1.md | 20 ++++++ .../bind-rotation-svc-authelia.yaml | 14 ++--- .../bind-split-create-service-accounts.yaml | 4 +- .../bind-split-read-only-verify.yaml | 4 +- .../bind-split-switch-super-admin.yaml | 4 +- .../invalid/human-intrinsic-step.yaml | 4 +- .../generate_examples.py | 6 +- .../tests/test_validate.py | 5 +- .../declarations/bounded-executor.yaml | 19 +----- workplans/NK-WP-0056-bounded-executor-path.md | 61 ++++++++++++++++++- 11 files changed, 107 insertions(+), 36 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 75d2ac9..fbaa2c2 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -353,6 +353,8 @@ | task | NK-WP-0056-T08 | done | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T09 | done | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T10 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | +| task | NK-WP-0056-T11 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | +| task | NK-WP-0056-T12 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | | intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NET-IN-0001 | open | — | intakes/intakes.md | diff --git a/canon/standards/bounded-execution-admission_v0.1.md b/canon/standards/bounded-execution-admission_v0.1.md index 3279b1d..d0fc207 100644 --- a/canon/standards/bounded-execution-admission_v0.1.md +++ b/canon/standards/bounded-execution-admission_v0.1.md @@ -193,6 +193,26 @@ once Railiance declarations exist for it. The receipt adds the outcome per step, check results, whether a revert ran, and the budget used. It never holds a secret value. +Founder decision 2026-10-31, agreed with audit-core (AUDIT-WP-0018): + +- When the executor consumes the admission, before its first step, it emits a separate + `execution.run.started` event, so a run that crashes before its receipt is still detectable. +- It then emits one `execution.attribution.receipt` per run: + - `target` and `action` at the admission level (`admission:`, `bounded-execution`); + - per-step detail in `steps[]`; + - `decision_refs` labelled `admission`, `admission_digest` and `approval`. +- Envelope ids are `run::started` and `run::receipt`. + +**Policy decision point.** access-engine's gate (FLEX-WP-0040) has two decisions: `start` before +consume, and `execute-step` after it. The T06 pilot runs without the gate: its receipt records the +gate as `not-evaluated` and claims no allow. Any further admission needs the gate, or an exception +the founder records (founder decision 2026-10-31). + +**Credentials during a run.** Write roles exist only during the admitted window: the platform creates +them when an admission is approved and deletes them after the receipt (CCR-2026-0058). A directory +bind rotates its own password, so no password-manager account is needed (founder decisions +2026-10-31). + ## 7. The bind split as admissions NK-WP-0056 tagged each step of the 2026-10-30 LLDAP bind split. The examples express them: diff --git a/examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml b/examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml index 990bd9d..85a90e2 100644 --- a/examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml +++ b/examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml @@ -29,7 +29,7 @@ spec: scope: openbao/platform/workloads/net-kingdom/directory-binds/svc-authelia environments: &id001 - PRODUCTION - - mode: ADMINISTER + - mode: OPERATE scope: lldap/people/svc-authelia environments: *id001 - mode: OPERATE @@ -49,7 +49,7 @@ spec: plan: source: repo: net-kingdom - commit: 43a78907593a + commit: c9ab01e23e92 files: - path: tools/service-binds/bind-lane.py sha256: 5b31b3567af07bba09530c6d3b0ed8b5c947df22bd13ac0442dc33c0d9b420c0 @@ -69,13 +69,13 @@ spec: - kv-advanced revert: restore-kv - id: set-directory - operation: lldap.user.set-password-from-kv + operation: lldap.user.change-own-password-from-kv target: lldap:svc-authelia consumer: authelia class: delegable mutates: true requires: - mode: ADMINISTER + mode: OPERATE scope: lldap/people/svc-authelia checks_after: - bind-signs-in @@ -115,13 +115,13 @@ spec: mode: OPERATE scope: openbao/platform/workloads/net-kingdom/directory-binds/svc-authelia - id: restore-directory - operation: lldap.user.set-password-from-kv + operation: lldap.user.change-own-password-from-kv target: lldap:svc-authelia consumer: authelia class: delegable mutates: true requires: - mode: ADMINISTER + mode: OPERATE scope: lldap/people/svc-authelia - id: redeliver operation: eso.force-sync @@ -141,7 +141,7 @@ spec: requires: mode: OPERATE scope: k8s/sso/deployment/authelia - plan_digest: sha256:47de5fcaa09fdadb4ea501e47f85fc12da5b6ce31b28c2ccad34a25ca9599d0f + plan_digest: sha256:36328096076c71ade582173092511be6aee546281ef5edb3c2acaadaf7c9e1fb checks: - id: kv-advanced description: KV svc-authelia has exactly one new version, CAS on the previous one diff --git a/examples/bounded-execution-admission/bind-split-create-service-accounts.yaml b/examples/bounded-execution-admission/bind-split-create-service-accounts.yaml index 9ca9106..ecd7368 100644 --- a/examples/bounded-execution-admission/bind-split-create-service-accounts.yaml +++ b/examples/bounded-execution-admission/bind-split-create-service-accounts.yaml @@ -43,7 +43,7 @@ spec: plan: source: repo: net-kingdom - commit: 43a78907593a + commit: c9ab01e23e92 files: - path: sso-mfa/k8s/lldap/bind-split.py sha256: 4decc3bbe65120ff6c4cef5613b9a41a60168ae64ecd4c1380f61a2fcd37b6b3 @@ -93,7 +93,7 @@ spec: requires: mode: OPERATE scope: openbao/platform/workloads/net-kingdom/directory-binds - plan_digest: sha256:be02f6850b329e2e2262bc720a70f881a146183ff70fb3ac20ae72e3a5ae70e8 + plan_digest: sha256:139e7b874d8d8ba9419f141bf9e10341a9a0057bb75fa47eee32c2ee19b396ce checks: - id: accounts-read-only description: each account exists in lldap_strict_readonly only and cannot write diff --git a/examples/bounded-execution-admission/bind-split-read-only-verify.yaml b/examples/bounded-execution-admission/bind-split-read-only-verify.yaml index 30557c3..b287e72 100644 --- a/examples/bounded-execution-admission/bind-split-read-only-verify.yaml +++ b/examples/bounded-execution-admission/bind-split-read-only-verify.yaml @@ -46,7 +46,7 @@ spec: plan: source: repo: net-kingdom - commit: 43a78907593a + commit: c9ab01e23e92 files: - path: sso-mfa/k8s/lldap/bind-split.py sha256: 4decc3bbe65120ff6c4cef5613b9a41a60168ae64ecd4c1380f61a2fcd37b6b3 @@ -73,7 +73,7 @@ spec: scope: k8s/sso checks_after: - split-shape - plan_digest: sha256:c07dd58e6581049441d512edb60753bfe7102e3c89ea28d55081141373844d0d + plan_digest: sha256:0459999b93bb8aee0dacb1b36ea95d114ac74d0ed775095f3ee5dc2ee66203cd checks: - id: split-shape description: every consumer binds as its own svc- account; no service binds as admin diff --git a/examples/bounded-execution-admission/bind-split-switch-super-admin.yaml b/examples/bounded-execution-admission/bind-split-switch-super-admin.yaml index 3d7ab7a..4be7007 100644 --- a/examples/bounded-execution-admission/bind-split-switch-super-admin.yaml +++ b/examples/bounded-execution-admission/bind-split-switch-super-admin.yaml @@ -43,7 +43,7 @@ spec: plan: source: repo: net-kingdom - commit: 43a78907593a + commit: c9ab01e23e92 files: - path: sso-mfa/k8s/lldap/bind-split.py sha256: 4decc3bbe65120ff6c4cef5613b9a41a60168ae64ecd4c1380f61a2fcd37b6b3 @@ -93,7 +93,7 @@ spec: requires: mode: OPERATE scope: k8s/sso/deployment/lldap - plan_digest: sha256:60578e013b23fd3d68fead65956c939cde1eb9230d9f9fdbe1aaa677e8e0de11 + plan_digest: sha256:5f51601ce96a07da5fa56b5b00c2066c572e3b19a4677b1a16cee3178295cae0 checks: - id: ld-admin-administers description: ld-admin signs in and administers; admin keeps lldap_admin membership diff --git a/tools/bounded-execution-admission/fixtures/invalid/human-intrinsic-step.yaml b/tools/bounded-execution-admission/fixtures/invalid/human-intrinsic-step.yaml index 1c9c543..90efec5 100644 --- a/tools/bounded-execution-admission/fixtures/invalid/human-intrinsic-step.yaml +++ b/tools/bounded-execution-admission/fixtures/invalid/human-intrinsic-step.yaml @@ -42,7 +42,7 @@ spec: plan: source: repo: net-kingdom - commit: 43a78907593a + commit: c9ab01e23e92 files: - path: sso-mfa/k8s/lldap/bind-split.py sha256: 4decc3bbe65120ff6c4cef5613b9a41a60168ae64ecd4c1380f61a2fcd37b6b3 @@ -104,7 +104,7 @@ spec: requires: mode: OPERATE scope: k8s/sso/deployment/lldap - plan_digest: sha256:33063248bed00c84b640a37344a4c9e52285e76cacb68b92d08872cd9b7c77db + plan_digest: sha256:139f0ca7564184290c7cb1f6b20bdb9807db98e5609a79083ac6f7aea13bb50c checks: - id: ld-admin-administers description: ld-admin signs in and administers; admin keeps lldap_admin membership diff --git a/tools/bounded-execution-admission/generate_examples.py b/tools/bounded-execution-admission/generate_examples.py index 3da9f79..a1b6f9b 100644 --- a/tools/bounded-execution-admission/generate_examples.py +++ b/tools/bounded-execution-admission/generate_examples.py @@ -52,17 +52,17 @@ rotation = doc( "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": "ADMINISTER", "scope": "lldap/people/svc-authelia", "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.set-password-from-kv", "lldap:svc-authelia", "authelia", "delegable", True, "ADMINISTER", "lldap/people/svc-authelia", ["bind-signs-in"], "restore-directory"), + 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.set-password-from-kv", "lldap:svc-authelia", "authelia", "delegable", True, "ADMINISTER", "lldap/people/svc-authelia"), + 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), diff --git a/tools/bounded-execution-admission/tests/test_validate.py b/tools/bounded-execution-admission/tests/test_validate.py index ad05b30..85590fd 100644 --- a/tools/bounded-execution-admission/tests/test_validate.py +++ b/tools/bounded-execution-admission/tests/test_validate.py @@ -95,7 +95,10 @@ class RuleTests(unittest.TestCase): {"mode": "GOVERN", "scope": "lldap", "environments": ["PRODUCTION"]})))) # whole-directory ADMINISTER for a delegable step at medium risk def widen(s): - s["scope_ceiling"][1]["scope"] = "lldap" + s["scope_ceiling"][1].update(mode="ADMINISTER", scope="lldap") + for step in s["plan"]["steps"] + s["plan"]["revert_steps"]: + if step["requires"]["scope"].startswith("lldap/"): + step["requires"]["mode"] = "ADMINISTER" self.assertIn("BEA-5", rules(mutate(ROTATION, widen))) def test_bea6_least_privilege(self): diff --git a/tools/service-identity/declarations/bounded-executor.yaml b/tools/service-identity/declarations/bounded-executor.yaml index 78e41e7..ca9a8d1 100644 --- a/tools/service-identity/declarations/bounded-executor.yaml +++ b/tools/service-identity/declarations/bounded-executor.yaml @@ -31,9 +31,10 @@ identities: principal_type: WORKLOAD conventions: [executor] executor_subject: "service:nk-bind-executor" - purpose: "OpenBao Kubernetes-auth role for sso/nk-bind-executor: write the next version of the svc-authelia bind (CAS) and read it for checks; lane shape pending secrets-engine and railiance-platform answers (T02)" + purpose: "OpenBao Kubernetes-auth role for sso/nk-bind-executor (CCR-2026-0058, approved, window-scoped): write the next version of the svc-authelia bind (CAS) and read it; holding the current value is what lets the executor change the bind's own password in LLDAP" grants: - - {mode: OPERATE, scope: openbao/platform/workloads/net-kingdom/directory-binds/svc-authelia, activation: APPROVED, environments: [PRODUCTION], note: "KV data read and CAS write; 15 min TTL"} + - {mode: OPERATE, scope: openbao/platform/workloads/net-kingdom/directory-binds/svc-authelia, activation: APPROVED, environments: [PRODUCTION], note: "KV data read and CAS write; 15 min TTL; role exists only in the admitted window (CCR-2026-0058)"} + - {mode: OPERATE, scope: lldap/people/svc-authelia, activation: APPROVED, environments: [PRODUCTION], note: "self-rotation: signs in as the bind with the KV value and changes its own password (hub 3d0c8f86)"} interactive_login: false second_factor: not_applicable custody: [openbao-lease] @@ -41,20 +42,6 @@ identities: owner: "railiance-platform (OpenBao role) for NetKingdom" rotation: {path: "OpenBao lease per run; no standing secret", coupling: [bounded-executor]} status: planned - - name: svc-executor-pw - principal_type: SERVICE - conventions: [executor] - executor_subject: "service:nk-bind-executor" - purpose: "LLDAP account in lldap_password_manager: set svc-authelia's password from KV. LLDAP cannot narrow it to one account (it can reset any non-admin user); each admission narrows it to the accounts it names" - grants: - - {mode: ADMINISTER, scope: lldap/people, activation: APPROVED, environments: [PRODUCTION], note: "lldap_password_manager; admission ceiling narrows to lldap/people/"} - interactive_login: false - second_factor: not_applicable - custody: [openbao-lease] - consumers: [{name: bounded-executor, kind: service}] - owner: "NetKingdom directory and User Engine owners" - rotation: {path: "Its own password held in OpenBao; lane shape pending secrets-engine (T02)", coupling: [bounded-executor]} - status: planned - name: svc-executor-read principal_type: SERVICE conventions: [bind] diff --git a/workplans/NK-WP-0056-bounded-executor-path.md b/workplans/NK-WP-0056-bounded-executor-path.md index fd4ce59..0627981 100644 --- a/workplans/NK-WP-0056-bounded-executor-path.md +++ b/workplans/NK-WP-0056-bounded-executor-path.md @@ -121,6 +121,18 @@ NetKingdom proved on the pinned LLDAP; audit-core holds receipts plus a `run.sta creates the rapp, the image is built in net-kingdom, one Job per admission; glas-harness: agent executors after GLAS-WP-0018-T04. +**2026-10-31, founder decisions after the answers** (hub c29c6eda, 3d0c8f86, 19afd15e): + +- The executor's OpenBao write role is window-scoped. CCR-2026-0058 was amended, approved and pushed. +- The bind rotates its own password, so `svc-executor-pw` is dropped and RPF-WP-0069-T06 is cancelled. +- The pilot runs without the policy gate (`not-evaluated`); later admissions need the gate or a recorded exception. +- Packaging is `rapp-bounded-executor`, one Job per admission in `sso`, with the image built in net-kingdom. +- Receipts are a started event plus one receipt per run. +- These owner workplans are approved: APPROVAL-WP-0007, FLEX-WP-0040, SECRETS-WP-0017, RPF-WP-0069, AUDIT-WP-0018 and + GLAS-WP-0018. glas-harness owns agent executors later. + +Runtime work is split into T11 (core runner) and T12 (pilot operations and packaging). + ## Executor identity and authority ceiling ```task @@ -396,6 +408,53 @@ check between consumers; failure triggers the revert path. **Done when:** the journey runs unattended against live with no person involved, and fails closed against a deliberately broken bind on a synthetic directory (T08). +## Executor runtime: core runner + +```task +id: NK-WP-0056-T11 +status: todo +priority: high +assignee: claude-code +depends_on: [NK-WP-0056-T01, NK-WP-0056-T03] +``` + +The executor itself, in net-kingdom (founder decision 6d1adce9): + +- consume one admission through the approval-engine carrier (independent GET, digest check, aal2 check, consume once); +- refuse outside the window or ceiling; +- run the pinned steps one consumer at a time, with checks after each mutating step, settle times, revert on + failure, the budget (wall time, attempts) and an `on_unknown: stop`; +- emit `execution.run.started` and the receipt (PDP `not-evaluated` for the pilot). + +Tested against in-process fakes for the carrier, operations and audit sink. + +**Done when:** the runner passes its tests, including revert on a failed check, budget exhaustion, a stale or +reused approval, and a step outside the ceiling. Every refusal must happen before any effect. + +## Executor runtime: pilot operations and packaging + +```task +id: NK-WP-0056-T12 +status: todo +priority: high +assignee: claude-code +depends_on: [NK-WP-0056-T11, NK-WP-0056-T08] +``` + +The pilot's operations: + +- OpenBao read and CAS write of the next version; +- LLDAP self-rotation of the bind; +- ExternalSecret force-sync and its status check; +- Deployment restart and rollout check; +- the synthetic journey. + +Run them on the T08 synthetic stack, including a revert. Add the Containerfile and image workflow in +net-kingdom. The `rapp-bounded-executor` package is created with `rmgr rapp wrap`, and the founder creates the +Forgejo remote. It runs one Job per admission in `sso` with an egress-only NetworkPolicy. + +**Done when:** a synthetic rotation completes and a deliberately failing one reverts, with receipts; the image builds. + ## Pilot: unattended bind rotation under one admission ```task @@ -405,7 +464,7 @@ priority: high assignee: claude-code needs_human: true intervention_note: "Founder signs the admission for the single live pilot rotation." -depends_on: [NK-WP-0056-T01, NK-WP-0056-T03, NK-WP-0056-T04, NK-WP-0056-T05, NK-WP-0056-T10] +depends_on: [NK-WP-0056-T01, NK-WP-0056-T03, NK-WP-0056-T04, NK-WP-0056-T05, NK-WP-0056-T10, NK-WP-0056-T12] state_hub_task_id: "6c3939f8-23a9-5f70-a3b1-94f4b291706b" ```