diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 3b6dbd0..229360c 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -25,6 +25,7 @@ | workplan | SECRETS-WP-0014 | active | — | workplans/SECRETS-WP-0014-pdp-transport-and-responder-authentication.md | | workplan | SECRETS-WP-0015 | active | — | workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md | | workplan | SECRETS-WP-0016 | finished | — | workplans/SECRETS-WP-0016-engine-manifestations.md | +| workplan | SECRETS-WP-0017 | proposed | — | workplans/SECRETS-WP-0017-bounded-executor-lanes.md | | task | SECRETS-WP-ADHOC-2026-08-21-T01 | done | — | workplans/ADHOC-2026-08-21.md | | task | SECRETS-WP-ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md | | task | SECRETS-WP-0001-T01 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md | @@ -103,6 +104,9 @@ | task | SECRETS-WP-0016-T03 | done | — | workplans/SECRETS-WP-0016-engine-manifestations.md | | task | SECRETS-WP-0016-T04 | done | — | workplans/SECRETS-WP-0016-engine-manifestations.md | | task | SECRETS-WP-0016-T05 | done | — | workplans/SECRETS-WP-0016-engine-manifestations.md | +| task | SECRETS-WP-0017-T01 | wait | — | workplans/SECRETS-WP-0017-bounded-executor-lanes.md | +| task | SECRETS-WP-0017-T02 | todo | — | workplans/SECRETS-WP-0017-bounded-executor-lanes.md | +| task | SECRETS-WP-0017-T03 | wait | — | workplans/SECRETS-WP-0017-bounded-executor-lanes.md | | intake | SECRETS-IN-0001 | closed | — | intakes/intakes.md | | intake | SECRETS-IN-0002 | closed | — | intakes/intakes.md | | intake | SECRETS-IN-0003 | closed | — | intakes/intakes.md | diff --git a/docs/bounded-executor-credential-lanes.md b/docs/bounded-executor-credential-lanes.md new file mode 100644 index 0000000..86cf29d --- /dev/null +++ b/docs/bounded-executor-credential-lanes.md @@ -0,0 +1,123 @@ +# Bounded executor credential lanes: owner answer to NK-WP-0056-T02 + +Status: **proposal** (secrets-engine owner answer, 2026-10-31). Nothing here is +an approval, a grant, or a live change. Founder and platform-owner decisions are +marked as such. + +Request: `net-kingdom/docs/proposals/2026-10-31-nk56-t02-owner-requests.md` +(section "secrets-engine"), hub message `16adf344-92da-443d-a25b-19dc29c5952a`. +Standard under review: NetKingdom Bounded Execution Admission v0.1 (proposed), +example `examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml`. +Pilot executor: `service:nk-bind-executor`, Kubernetes ServiceAccount +`nk-bind-executor` in namespace `sso`. Follow-on work: `SECRETS-WP-0017`. + +## What the engine does today (source facts) + +| Fact | Source | +| --- | --- | +| Consumer lane policies are **read-only**: `{data_path: ["read"]}` plus optional metadata read. No consumer lane can write. | `src/secrets_engine/roles.py` `lane_policy_paths` | +| Engine-operated delivery auth is AppRole only (`VALID_DELIVERY_AUTH_METHODS = ("approle", "none")`). Kubernetes/ESO/OIDC delivery is recorded as `workload_delivery` metadata and not operated. | `src/secrets_engine/catalog.py`; `SCOPE.md` "Catalog and validation" | +| `auth-capability` lanes grant only `update`, and only on exact `ssh/sign/`. | `roles.py` `AUTH_CAPABILITY_ALLOWED_CAPABILITIES` and path check | +| The engine's own CAS write (`provision`) runs under the engine's stage credential from a mode-0600 file. It is not a capability handed to a workload. | `SCOPE.md` "KV provisioning"; `src/secrets_engine/provision.py` | +| Every production live command currently fails closed (approval-claim endpoint not served). Task-scoped protected execution is designed, not shipped; the service-jwt identity (CCR-2026-0038) is login-only. | `pep-stance.yaml`; `docs/task-scoped-openbao-execution-contract.md`; `SECRETS-WP-0013` | +| The existing NetKingdom bind lanes (CCR-2026-0052..0057) are platform-typed `workload-kv-read` / attended-delivery CCRs with Kubernetes auth roles applied by railiance-platform, not secrets-engine catalog lanes. | `railiance-platform/credential-change-requests/CCR-2026-0052..0057` | + +## Question 1: executor OpenBao capabilities (CAS write of svc-authelia, reads for checks) + +**Answer: not supported today; needs a change, split between two owners.** + +secrets-engine does not orchestrate this lane the way it orchestrates its +AppRole lanes, for three reasons: the lane needs a write, the workload logs in +with Kubernetes auth, and live production commands are gated. Routing the +executor's write through `secrets-engine serve` was considered and rejected for +the pilot. It would put T06 behind two unfinished gates (approval-claim and +`SECRETS-WP-0013`), and the engine would then hold a write capability on behalf +of the executor. The executor should hold only its own 15-minute token. + +Proposed split: + +- **railiance-platform** (no CCR is created here) owns the Kubernetes auth role + and the ACL policy. It applies them with a new CCR request type, for example + `workload-kv-rotate-cas`, following the CCR-0052/0053 shape. It also sets + `cas_required=true` on the svc-authelia secret's metadata. This is a one-time + attended step. The executor has no metadata capability and cannot undo it. +- **secrets-engine** catalogs the lane as non-secret metadata + (`delivery_auth.management: existing`). It renders the expected policy from + the catalog and refuses any platform policy file that differs from it. That + check covers wildcards, metadata, delete/destroy and `sys/` paths, and token + creation. secrets-engine also gives a read-only `route` pointer. This is + `SECRETS-WP-0017`. + +### Proposed lane shape (for founder and platform approval) + +| Item | Proposal | +| --- | --- | +| Catalog id | `nk-bind-executor-svc-authelia` (kind `kv`, stage `prod`, risk `high`) | +| Policy name | `net-kingdom-nk-bind-executor` (must not use `admin`) | +| Write path | `platform/data/workloads/net-kingdom/directory-binds/svc-authelia`: `["read", "update"]`. There is no `create`: the path already exists (CAS=0 written in T04), so a deleted path cannot be recreated silently. | +| CAS | `cas_required=true` on that secret's metadata (set by platform). The executor reads the current version from the data response, so it needs no metadata read. | +| Check read | `platform/data/workloads/net-kingdom/synthetic-journey/syn-journey`: `["read"]` | +| Denied (verified as denials) | `platform/metadata/...`, `platform/delete/...`, `platform/destroy/...`, `platform/undelete/...` for both paths; sibling `directory-binds/*`; `auth/token/create`; `sys/` | +| Token self-lifecycle | `auth/token/lookup-self` `["read"]` and `auth/token/revoke-self` `["update"]`, with self-revoke in `finally` (same as the bind-lane pattern) | +| Auth | Kubernetes mount `kubernetes`, role `net-kingdom-nk-bind-executor`, bound SA `nk-bind-executor`, namespace `sso`, audience `openbao`, `bound_claims_confirmed: true` | +| TTL | `ttl`, `token_max_ttl` and `token_explicit_max_ttl` all `15m`; `token_no_default_policy: true`; `token_num_uses` bounded (proposal: 16) | +| Rotation | Nothing long-lived to rotate. The credential is a projected SA token (minted for at most 10m) exchanged for a 15m OpenBao token. | +| Revocation | Platform removes the role binding or the policy (CCR deactivation). Deleting the SA also revokes. Revoke outstanding tokens by accessor. The admission expiry is enforced by the executor runtime and is not OpenBao authority. | +| Dormancy (**founder decision**) | (a) The role exists only during an admitted window: platform applies it at admission and removes it after the receipt. Recommended for the pilot. (b) A standing role whose use is gated only by the admission record. | +| Evidence | Lane id, admission id, actor (SA), exact paths, KV version before and after, accessor fingerprint, pass/fail booleans. Never values. | + +The svc-authelia ESO reader role (`net-kingdom-svc-authelia-bind-eso`, CCR-0053) +stays unchanged and separate. + +## Question 2: directory credential to set svc-authelia's password in LLDAP + +**Answer: secrets-engine can catalog either shape. It recommends a shape that +needs no new privileged account and keeps the dedicated account as a fallback.** + +### Preferred: self-rotation with the current bind credential (no new LLDAP account) + +The executor already reads the current svc-authelia value (version *n*) under +Q1. It can log in to LLDAP as `svc-authelia` with that value and change that +account's own password. It then writes the new value as version *n+1* (CAS). +Revert follows the same path in reverse: change the password back using the new +value, and write the old value as *n+2*. + +- No `lldap_password_manager` membership and no new standing credential. The + blast radius is the one account being rotated. +- No extra secrets-engine lane. Q1's read+update policy covers the whole flow. +- **Must be proven first (net-kingdom / LLDAP deployment owner):** on a + synthetic LLDAP fixture, a bind user such as svc-authelia must be able to log + in and change its own password, for example when it is in + `lldap_strict_readonly`. Account lockout and rate limits must not break + revert. Until this is proven, the shape is a hypothesis. + +### Fallback: dedicated LLDAP executor account as a cataloged credential + +Use this if self-rotation is not possible. + +| Item | Proposal | +| --- | --- | +| LLDAP account | `svc-nk-bind-executor`, member of `lldap_password_manager` only. This can reset passwords of non-admin users, which includes every service bind, so the risk is `high`. | +| Catalog id | `nk-bind-executor-lldap-credential` (kind `kv`, stage `prod`, risk `high`, approval `ccr` plus founder) | +| KV path | `platform/workloads/net-kingdom/directory-binds/svc-nk-bind-executor`, field `LDAP_BIND_PASSWORD` | +| Access | Added to the Q1 executor policy as `["read"]` on that data path. There is no metadata read and no other reader. ESO does not project it, and it lives only in executor memory. | +| Rotation | After each run, the executor changes its own LLDAP password and writes it with CAS. This needs `update` on its own path too. Otherwise the founder rotates it attended with the existing bind-lane setup pattern. The **founder chooses**. | +| Revocation | Remove the account from `lldap_password_manager` or delete the account, revoke the KV read in the policy, and destroy the KV versions (platform). | +| Residual risk | A compromised executor token within 15m can reset any non-admin LLDAP user. This is the main reason the self-rotation shape is preferred. | + +## Items needing the founder + +1. Approve the Q1 lane shape and the dormancy option (a) or (b). +2. Choose the Q2 shape: self-rotation, after the fixture proof, or the fallback + account with its rotation mode. +3. Approve `SECRETS-WP-0017`. Its catalog entries stay unapplied until the + platform CCR exists and is approved. + +## Items for other owners + +- **railiance-platform:** a CCR request type for a Kubernetes-auth CAS + read+update lane, `cas_required=true` on the svc-authelia metadata, the role + and policy above, and the deactivation procedure. +- **net-kingdom:** the LLDAP self-password-change proof on a synthetic fixture. + The executor runtime must self-revoke and must refuse to run outside the + admission window. diff --git a/workplans/SECRETS-WP-0017-bounded-executor-lanes.md b/workplans/SECRETS-WP-0017-bounded-executor-lanes.md new file mode 100644 index 0000000..4f3f329 --- /dev/null +++ b/workplans/SECRETS-WP-0017-bounded-executor-lanes.md @@ -0,0 +1,68 @@ +--- +id: SECRETS-WP-0017 +type: workplan +title: "Catalog and guard bounded-executor CAS rotation lanes (NK-WP-0056 pilot)" +domain: infotech +repo: secrets-engine +status: proposed +flavor: implementation +owner: secrets-engine +topic_slug: netkingdom +created: "2026-10-31" +updated: "2026-10-31" +depends_on: [] +related: [SECRETS-WP-0013] +--- + +Owner answer to NK-WP-0056-T02 (hub message 16adf344). Design and proposed lane +shapes: `docs/bounded-executor-credential-lanes.md`. The engine catalogs and +statically guards the executor lane. railiance-platform applies the Kubernetes +auth role and policy through its own CCR, and the executor holds only its own +15-minute token. Nothing here is a grant or a live change. + +## Model an existing-auth CAS rotation lane in the catalog + +```task +id: SECRETS-WP-0017-T01 +status: wait +needs_human: true +blocked_on: founder approval of the Q1 lane shape (docs/bounded-executor-credential-lanes.md) +priority: high +``` + +Add an explicit lane access mode, for example `access: rotate-cas`, for `kv` +lanes with `delivery_auth.management: existing`. It renders `["read", +"update"]` on the exact data path and no `create`, metadata, delete, destroy or +undelete paths. It requires the `cas_required` declaration, a 15m maximum TTL, +risk `high`, and rotation and deactivation owners. Kubernetes auth is recorded +as existing-auth metadata only. Keep the default read-only `lane_policy_paths` +unchanged. Add tests for the rendered policy and for refusal of wildcard, extra +capability and metadata variants. + +## Check a platform policy file against the catalog-rendered policy + +```task +id: SECRETS-WP-0017-T02 +status: todo +priority: medium +``` + +Add a static, offline comparison of a railiance-platform policy HCL with the +engine-rendered policy for a lane, reported as non-secret pass/fail. Platform +can run it on the CCR policy file before an attended apply. No OpenBao call. + +## Add catalog entries for the pilot executor + +```task +id: SECRETS-WP-0017-T03 +status: wait +needs_human: true +blocked_on: platform CCR for the executor role/policy, and founder choice of LLDAP credential shape +priority: medium +``` + +Add `nk-bind-executor-svc-authelia` (rotate-cas) and the syn-journey check read +as non-secret entries that reference the platform CCR and NK-WP-0056. If the +founder chooses the fallback LLDAP account, also add +`nk-bind-executor-lldap-credential` (high risk). Use `route` for readiness +pointers only.