RPF-WP-0069: answer NK-WP-0056-T02; add workload-kv-write-next-version; CCR-2026-0058 (proposed)
Owner answer for the bounded executor (hub 9d08288d): one Kubernetes role per lane for sso/nk-bind-executor; new contract-only request type for a CAS next-version write (no create, metadata or delete, 15m, agents fenced); CCR-2026-0058 drafted as proposed, not approved; audit sender in the CCR-2026-0021/0022 shape once audit-core admits it. 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
This commit is contained in:
parent
c5f9770c97
commit
8b57d270de
8 changed files with 553 additions and 0 deletions
|
|
@ -48,6 +48,7 @@
|
|||
| workplan | RPF-WP-0066 | proposed | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| workplan | RPF-WP-0067 | active | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
|
||||
| workplan | RPF-WP-0068 | active | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| workplan | RPF-WP-0069 | active | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md |
|
||||
|
|
@ -225,6 +226,12 @@
|
|||
| task | RPF-WP-0068-T02 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0068-T03 | wait | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0068-T04 | todo | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0069-T01 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T02 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T03 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T04 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T05 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T06 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| intake | RPF-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | RPF-IN-0002 | open | — | intakes/intakes.md |
|
||||
| intake | RPF-IN-0003 | open | — | intakes/intakes.md |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
id: CCR-2026-0058
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-write-next-version
|
||||
title: NetKingdom bounded executor writes the next svc-authelia bind version (pilot)
|
||||
status: proposed
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T02 owner request (hub 9d08288d), for NK-WP-0056-T06: the bounded
|
||||
executor service:nk-bind-executor rotates the svc-authelia LLDAP bind unattended
|
||||
under one founder admission (Bounded Execution Admission v0.1, proposed). The
|
||||
executor must write the next KV version of the bind with CAS and read it back for
|
||||
its checks and revert. Drafted by the railiance-platform worker; not approved.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-bind-executor
|
||||
environment: production
|
||||
purpose: Let only the executor ServiceAccount sso/nk-bind-executor read and write the
|
||||
next version of platform/workloads/net-kingdom/directory-binds/svc-authelia, with
|
||||
CAS on the current version; no create, metadata, delete, destroy, list or sibling
|
||||
path
|
||||
admission:
|
||||
standard: 'net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed)'
|
||||
executor_identity: service:nk-bind-executor
|
||||
example: net-kingdom examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml
|
||||
note: The OpenBao role does not evaluate the admission. The admission (approval-engine
|
||||
consume, window, plan digest) is enforced by the executor and its owners; this
|
||||
request bounds what a compromised or unadmitted executor can reach to one value.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-authelia
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
policy_name: net-kingdom-bind-executor-svc-authelia-write
|
||||
policy_file: openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl
|
||||
write:
|
||||
mode: next-version
|
||||
cas_required: true
|
||||
create: false
|
||||
metadata_read: false
|
||||
delete: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-bind-executor-svc-authelia-write
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-bind-executor-svc-authelia-write
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- nk-bind-executor
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: false
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-bind-executor-svc-authelia-write
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
agent_fence:
|
||||
data_and_metadata_denied: true
|
||||
note: The svc-authelia data and metadata paths are already denied to coding agents
|
||||
since CCR-2026-0053; setup re-checks the deny before the role is created.
|
||||
lifecycle:
|
||||
deactivate: Delete the role, then the policy; the KV entry and the ESO reader lane
|
||||
(CCR-2026-0053) are unchanged. The bind falls back to founder-run rotation.
|
||||
rotate: Executor reads the current version N, sets the new password in LLDAP, writes
|
||||
version N+1 with options.cas=N; ESO projects it, Authelia restarts, the synthetic
|
||||
journey proves sign-in. Revert writes the prior value as version N+2 with options.cas=N+1.
|
||||
compromised: Delete the role first (stops new logins; existing tokens expire within
|
||||
15m, or revoke by accessor), then reset svc-authelia in LLDAP and rotate attended
|
||||
as in CCR-2026-0053.
|
||||
delivery:
|
||||
surface: native-workload-login
|
||||
namespace: sso
|
||||
service_account: nk-bind-executor
|
||||
reader: 'Executor runtime (net-kingdom source, rapp-* package per ADR-0015, NK-WP-0056-T03);
|
||||
logs in with its projected ServiceAccount token, audience openbao; no Secret is
|
||||
created by this request'
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- A standing write role on the svc-authelia bind is new; until now only the attended
|
||||
platform-admin wrote this path. A compromised executor ServiceAccount can replace
|
||||
the bind value (Authelia sign-in outage on next restart) but cannot read any other
|
||||
path, create paths, delete versions or read metadata.
|
||||
- cas_required on the key makes every write state the version it replaces; a racing
|
||||
or replayed write is refused by the engine, not by the executor.
|
||||
- The executor also reads the current value (needed for CAS and revert). svc-authelia
|
||||
is a read-only directory bind (lldap_strict_readonly).
|
||||
- The role is a separate role from the ESO reader (CCR-2026-0053) and from the journey
|
||||
read lane (NK-WP-0056-T10); policies are never combined in one role.
|
||||
- Node root, ESO administration, cluster admins able to mint tokens for sso/nk-bind-executor
|
||||
and platform-admin remain inside this credential boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy and role read back; login as sso/nk-bind-executor (audience openbao)
|
||||
gets only this policy with a lease of at most 15m
|
||||
- Write of the next version with options.cas equal to the current version succeeds
|
||||
in a rehearsal on a synthetic sibling-free test path or under the T06 admission;
|
||||
the value is compared in memory only
|
||||
negative:
|
||||
- Write without options or with a stale cas is refused
|
||||
- Create of a new sibling path, metadata read, delete, destroy, undelete and list
|
||||
are refused
|
||||
- Wrong ServiceAccount, wrong namespace and wrong audience logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request.
|
||||
- ServiceAccount sso/nk-bind-executor exists from the reviewed rapp-* package
|
||||
(bound_claims_confirmed then set to true by the platform-owner).
|
||||
- Attended platform-admin (aal2) session sets cas_required on the key and applies
|
||||
policy and role from a reviewed commit.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
107
docs/credential-lane-designs/bounded-executor.md
Normal file
107
docs/credential-lane-designs/bounded-executor.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Bounded executor: OpenBao lanes and audit sender (owner answer, NK-WP-0056-T02)
|
||||
|
||||
Owner answer from railiance-platform to NetKingdom's request
|
||||
(net-kingdom `docs/proposals/2026-10-31-nk56-t02-owner-requests.md` §railiance-platform;
|
||||
State Hub message `9d08288d`). Basis: Bounded Execution Admission v0.1 (proposed) and its pilot
|
||||
example `bind-rotation-svc-authelia.yaml`. Workplan: `workplans/RPF-WP-0069-bounded-executor-lanes.md`.
|
||||
|
||||
Everything here is source and proposals. No role, policy, Secret or KV value was created or read.
|
||||
CCR-2026-0058 is `proposed`; approvals and the founder's choices below stay open.
|
||||
|
||||
## Q1. Kubernetes auth role for `sso/nk-bind-executor`: which request types?
|
||||
|
||||
**Answer: one role per lane, all bound to the one ServiceAccount, two request types.**
|
||||
|
||||
The existing `workload-kv-read` contract requires a role to carry exactly its own policy
|
||||
(`scripts/credential-change.py` `validate_workload_kv_read`, "openbao.auth.policies must contain exactly
|
||||
the policy used by the auth method"; applier check "must contain only openbao.policy_name"). We keep that
|
||||
rule. The executor logs in once per lane with the same projected ServiceAccount token (audience
|
||||
`openbao`); each token carries one policy and lives at most 15 minutes. A combined role would make every
|
||||
lane's review depend on every other lane.
|
||||
|
||||
| Executor need | Request type | Status |
|
||||
| --- | --- | --- |
|
||||
| Write the next version of `platform/workloads/net-kingdom/directory-binds/svc-authelia` (CAS) and read it for checks/revert (T06) | `workload-kv-write-next-version` (**new**, `scripts/workload_kv_write_next_version.py`) | Type in source; CCR-2026-0058 `proposed` |
|
||||
| Read `platform/workloads/net-kingdom/synthetic-journey/syn-journey` (`PASSWORD`, `TOTP_SEED`) for the journey (T10) | `workload-kv-read`, native workload login (`token_self_lifecycle: true`, `metadata_read: false`), as the native reader proof in CCR-2026-0053 | Not drafted; see below |
|
||||
| LLDAP credential to set `svc-authelia`'s password | `workload-kv-read` of a new custody entry, after secrets-engine answers its Q2 | Not drafted |
|
||||
| audit-core sender bearer (receipts) | `workload-kv-read` with ESO delivery, as CCR-2026-0021/0022 | Not drafted; see Q2 |
|
||||
|
||||
### The new type `workload-kv-write-next-version`
|
||||
|
||||
Prepared the way `attended-service-credential-delivery` was added: module
|
||||
`scripts/workload_kv_write_next_version.py`, tests `tests/test_workload_kv_write_next_version.py`, dispatch in
|
||||
`scripts/credential-change.py` (validate, plan, operator-commands, runbook). It is **contract-only**: there
|
||||
is no delegated applier (it is not in `LANE_MODULES`). The attended platform-admin applies policy and role
|
||||
from a reviewed commit, like CCR-2026-0052..0055.
|
||||
|
||||
The contract refuses anything wider than:
|
||||
|
||||
- one exact `platform/workloads/...` path on mount `platform`; no wildcard, no sibling;
|
||||
- policy (generated by `policy_hcl`, artifact `openbao/policies/<policy>.hcl`):
|
||||
`read` + `update` on `platform/data/<path>` only, `required_parameters = ["options"]`,
|
||||
`allowed_parameters = {data, options}`; plus `lookup-self`/`revoke-self`. No `create` (a missing path is
|
||||
refused, so the executor cannot mint new entries), no `metadata/`, `delete/`, `destroy/`, `undelete/`, list;
|
||||
- key-level `cas_required = true`, set by the attended platform-admin at setup. The engine then refuses
|
||||
every write that does not name the version it replaces; races and replays fail closed in OpenBao, not in
|
||||
executor code. (`required_parameters = ["options"]` alone cannot force `options.cas`; the key setting does.)
|
||||
- Kubernetes auth: one ServiceAccount, one namespace, audience `openbao`, `ttl`/`token_max_ttl`/
|
||||
`token_explicit_max_ttl` = 15m, `token_no_default_policy: true`, exactly one policy;
|
||||
- `access_frontdoor.resolvable: false`; `agent_fence.data_and_metadata_denied: true` (coding agents fenced);
|
||||
- `admission` names the standard and executor identity; full `verification` and `lifecycle`; explicit
|
||||
approval by every `review.required_approvers` role from `approved` onward.
|
||||
|
||||
Revert (`restore-kv` in the example) is a further next-version write of the prior value with CAS. Reading
|
||||
an older version is a `read` on `data/<path>?version=N`, which the policy already grants; no extra
|
||||
capability is needed.
|
||||
|
||||
**What the role does not do.** OpenBao does not evaluate the admission. The role is standing: a compromised
|
||||
`nk-bind-executor` ServiceAccount can replace the svc-authelia bind (sign-in outage on Authelia's next
|
||||
restart) but reach nothing else. The admission window, consume-once and plan digest are enforced by the
|
||||
executor, approval-engine and access-engine. If that is not enough, the founder can choose a
|
||||
window-scoped role instead (created by the attended platform-admin at the window start, removed after);
|
||||
that removes the "unattended" property of T06 for the platform side. Recommendation: standing role for the
|
||||
pilot, because the reachable damage is one read-only directory bind already covered by an attended
|
||||
recovery path (CCR-2026-0053 `lifecycle.compromised`).
|
||||
|
||||
### Journey secrets (T10)
|
||||
|
||||
CCR-2026-0057 is `attended-service-credential-delivery` with `runtime_access: false`; its contract forbids
|
||||
any role or policy on that path. A `workload-kv-read` request for the executor therefore changes
|
||||
CCR-2026-0057's stated boundary. It must say so, cite CCR-2026-0057, and be approved by both
|
||||
platform-owner and net-kingdom-owner. CCR-2026-0057's attended procedure stays valid. Shape: role
|
||||
`net-kingdom-bind-executor-syn-journey-read`, policy `read` on the data path only, native login, 15m.
|
||||
Draft it when T10 is scheduled and the ServiceAccount exists.
|
||||
|
||||
## Q2. Can the executor pod receive an ESO-projected audit-core sender bearer?
|
||||
|
||||
**Yes, with the CCR-2026-0021/0022 shape, once audit-core admits the sender.** The receipt is an event to
|
||||
audit-core; per RPF-WP-0067-T01 the wire is `POST /v1/events` to
|
||||
`http://audit-core.audit-core.svc.cluster.local:8080`, with a per-sender bearer from the registry at
|
||||
`platform/workloads/audit-core/senders`. `Idempotency-Key` is the envelope `id` and `correlation_id` is the
|
||||
`run_id`. The envelope `source` equals the sender name.
|
||||
|
||||
Shape (as `docs/credential-lane-designs/factory-audit-senders-review.md`):
|
||||
|
||||
- custody `platform/workloads/net-kingdom/bounded-executor/audit-sender` (`AUDIT_TOKEN`), generated CAS=0 in
|
||||
the attended platform process and merged into the registry as one exact sender (proposed name
|
||||
`nk-bounded-executor`, `tenant:platform`, write true, read false, `evidence_kind: load-bearing`,
|
||||
`secret_policy: redact`); no existing sender changes;
|
||||
- exact-path policy, Kubernetes role for `external-secrets/external-secrets` only, ClusterSecretStore
|
||||
restricted to namespace `sso`; ExternalSecret into `sso/nk-bind-executor-audit` key `audit-token`,
|
||||
mounted as a file in the executor pod;
|
||||
- coding-agent fence on the new data and metadata paths.
|
||||
|
||||
The bearer lets the executor append its own receipts only. It is not an OpenBao identity. The ESO
|
||||
manifests belong in the `rapp-*` package, as in CCR-2026-0053, which points to net-kingdom
|
||||
`bind-eso.yaml`. Prerequisites: audit-core says yes to its Q1 (net-kingdom request §audit-core) and
|
||||
names the sender. Then a `workload-kv-read` CCR follows here.
|
||||
|
||||
## Open items for the founder and other owners
|
||||
|
||||
1. Review and approve or change CCR-2026-0058 (platform-owner, net-kingdom-owner). It stays `proposed`.
|
||||
2. Standing vs window-scoped write role (recommendation: standing for the pilot).
|
||||
3. Accept that a journey-read CCR widens CCR-2026-0057's `runtime_access: false`.
|
||||
4. secrets-engine: the LLDAP credential lane shape (its Q2) before a custody CCR can be drafted.
|
||||
5. audit-core: sender admission and name, before the sender CCR.
|
||||
6. railiance-apps / rapp package: the ServiceAccount `sso/nk-bind-executor` must exist before
|
||||
`bound_claims_confirmed` can be set.
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
path "platform/data/workloads/net-kingdom/directory-binds/svc-authelia" {
|
||||
capabilities = ["read", "update"]
|
||||
required_parameters = ["options"]
|
||||
allowed_parameters = {
|
||||
"data" = []
|
||||
"options" = []
|
||||
}
|
||||
}
|
||||
|
||||
path "auth/token/lookup-self" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "auth/token/revoke-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
|
@ -520,6 +520,8 @@ def validate_ccr(path: Path) -> tuple[dict[str, Any], list[str], list[str]]:
|
|||
errors.extend(owner.validate_contract(ccr))
|
||||
elif request_type == "attended-service-credential-delivery":
|
||||
errors.extend(attended_service_module().validate_contract(ccr))
|
||||
elif request_type == "workload-kv-write-next-version":
|
||||
errors.extend(write_next_version_module().validate_contract(ccr))
|
||||
elif request_type in LANE_MODULES:
|
||||
errors.extend(lane_module(ccr).validate(ccr, REPO_DIR))
|
||||
else:
|
||||
|
|
@ -728,6 +730,8 @@ def render_plan(ccr: dict[str, Any]) -> str:
|
|||
return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created."
|
||||
if ccr.get("request_type") == "attended-service-credential-delivery":
|
||||
return attended_service_module().procedure_note(ccr)
|
||||
if ccr.get("request_type") == "workload-kv-write-next-version":
|
||||
return write_next_version_module().procedure_note(ccr)
|
||||
openbao = ccr["openbao"]
|
||||
auth = openbao["auth"]
|
||||
payload = auth_payload(ccr)
|
||||
|
|
@ -761,6 +765,8 @@ def render_operator_commands(ccr: dict[str, Any]) -> str:
|
|||
return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created."
|
||||
if ccr.get("request_type") == "attended-service-credential-delivery":
|
||||
return attended_service_module().procedure_note(ccr)
|
||||
if ccr.get("request_type") == "workload-kv-write-next-version":
|
||||
return write_next_version_module().procedure_note(ccr)
|
||||
openbao = ccr["openbao"]
|
||||
auth = openbao["auth"]
|
||||
auth_path = f"auth/{auth['mount']}/role/{auth['role']}"
|
||||
|
|
@ -850,6 +856,15 @@ def attended_service_module():
|
|||
return module
|
||||
|
||||
|
||||
def write_next_version_module():
|
||||
"""Owner contract for workload-kv-write-next-version (bounded executor, NK-WP-0056)."""
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("workload_kv_write_next_version", REPO_DIR / "scripts/workload_kv_write_next_version.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def lane_module(ccr: dict[str, Any]):
|
||||
"""Lazy-load the applier module for a lane request type, or None."""
|
||||
name = LANE_MODULES.get(ccr.get("request_type"))
|
||||
|
|
@ -1132,6 +1147,8 @@ def runbook_readiness_blockers(ccr: dict[str, Any]) -> list[str]:
|
|||
return ["use the attended owner procedure in docs/credential-lane-designs/railiance-clock-host-delivery.md"]
|
||||
if ccr.get("request_type") == "attended-service-credential-delivery":
|
||||
return ["use the attended procedure: " + attended_service_module().procedure_note(ccr)]
|
||||
if ccr.get("request_type") == "workload-kv-write-next-version":
|
||||
return ["use the attended procedure: " + write_next_version_module().procedure_note(ccr)]
|
||||
blockers: list[str] = []
|
||||
status = ccr.get("status")
|
||||
if status not in RUNBOOK_ALLOWED_STATUSES:
|
||||
|
|
|
|||
119
scripts/workload_kv_write_next_version.py
Normal file
119
scripts/workload_kv_write_next_version.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env python3
|
||||
"""workload-kv-write-next-version CCRs: a workload writes the next version of one KV entry.
|
||||
|
||||
NetKingdom NK-WP-0056 (bounded executor, hub request 9d08288d). A non-human
|
||||
executor rotates one existing value under a founder admission: it reads the
|
||||
current version, writes the next version with CAS on that version, and may
|
||||
revert by writing the prior value as a further version. It never creates a
|
||||
path, reads metadata, deletes, destroys or lists.
|
||||
|
||||
The contract is source-only: validation, the exact policy HCL and an attended
|
||||
procedure note. The policy, Kubernetes auth role and key-level ``cas_required``
|
||||
are applied by the attended platform-admin from a reviewed commit, as with
|
||||
attended-service-credential-delivery. Nothing here calls OpenBao or Kubernetes,
|
||||
or reads, prints or accepts a secret value.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
REQUEST_TYPE = "workload-kv-write-next-version"
|
||||
DOC = "docs/credential-lane-designs/bounded-executor.md"
|
||||
APPROVED = {"approved", "applied", "verified", "active"}
|
||||
KV_RE = re.compile(r"^platform/workloads/[a-z0-9][a-z0-9-]*(/[a-z0-9][a-z0-9-]*)+$")
|
||||
NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,62}$")
|
||||
FIELD_RE = re.compile(r"^[A-Z][A-Z0-9_]*$")
|
||||
OPENBAO_KEYS = {"mount", "kv_path", "fields", "policy_name", "policy_file", "write", "auth"}
|
||||
WRITE = {"mode": "next-version", "cas_required": True, "create": False, "metadata_read": False,
|
||||
"delete": False, "token_self_lifecycle": True}
|
||||
TTL = "15m"
|
||||
AUTH_FIXED = {"method": "kubernetes", "mount": "kubernetes", "audience": "openbao", "ttl": TTL,
|
||||
"token_max_ttl": TTL, "token_explicit_max_ttl": TTL, "token_no_default_policy": True}
|
||||
DISALLOWED_POLICIES = {"root", "default", "platform-admin"}
|
||||
|
||||
|
||||
def policy_hcl(ccr: dict[str, Any]) -> str:
|
||||
"""Exact policy: read and update of one data path; no create, metadata, delete or list."""
|
||||
openbao = ccr["openbao"]
|
||||
suffix = openbao["kv_path"][len("platform/"):]
|
||||
return (f'path "platform/data/{suffix}" {{\n'
|
||||
' capabilities = ["read", "update"]\n'
|
||||
' required_parameters = ["options"]\n'
|
||||
' allowed_parameters = {\n "data" = []\n "options" = []\n }\n'
|
||||
"}\n\n"
|
||||
'path "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n'
|
||||
'path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n')
|
||||
|
||||
|
||||
def _approvals(ccr: dict[str, Any]) -> set[str]:
|
||||
comments = (ccr.get("review") or {}).get("comments") or []
|
||||
return {str(c.get("reviewer", "")).removeprefix("User (").removesuffix(")")
|
||||
for c in comments if c.get("decision") == "approved"}
|
||||
|
||||
|
||||
def validate_contract(ccr: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
if ccr.get("request_type") != REQUEST_TYPE:
|
||||
errors.append("unsupported write-next-version contract")
|
||||
openbao = ccr.get("openbao")
|
||||
if not isinstance(openbao, dict):
|
||||
return errors + ["openbao must be an object"]
|
||||
extra = set(openbao) - OPENBAO_KEYS
|
||||
if extra:
|
||||
errors.append("openbao declares unsupported keys: " + ", ".join(sorted(extra)))
|
||||
kv_path = str(openbao.get("kv_path", ""))
|
||||
if openbao.get("mount") != "platform" or not KV_RE.match(kv_path):
|
||||
errors.append("openbao.kv_path must be one exact platform/workloads/... path on mount platform")
|
||||
fields = openbao.get("fields")
|
||||
if not isinstance(fields, list) or not fields or not all(isinstance(f, str) and FIELD_RE.match(f) for f in fields):
|
||||
errors.append("openbao.fields must list upper-case field names")
|
||||
policy = openbao.get("policy_name")
|
||||
if not isinstance(policy, str) or not NAME_RE.match(policy) or policy in DISALLOWED_POLICIES:
|
||||
errors.append("openbao.policy_name must be a dedicated lower-case policy name")
|
||||
elif openbao.get("policy_file") != f"openbao/policies/{policy}.hcl":
|
||||
errors.append("openbao.policy_file must be openbao/policies/<policy_name>.hcl")
|
||||
if openbao.get("write") != WRITE:
|
||||
errors.append("openbao.write must be next-version with cas_required, no create, metadata or delete, "
|
||||
"and token self-lifecycle")
|
||||
auth = openbao.get("auth")
|
||||
if not isinstance(auth, dict):
|
||||
errors.append("openbao.auth must be an object")
|
||||
else:
|
||||
for key, value in AUTH_FIXED.items():
|
||||
if auth.get(key) != value:
|
||||
errors.append(f"openbao.auth.{key} must be {value}")
|
||||
if not NAME_RE.match(str(auth.get("role", ""))):
|
||||
errors.append("openbao.auth.role must be a dedicated role name")
|
||||
if auth.get("policies") != [policy]:
|
||||
errors.append("openbao.auth.policies must contain exactly openbao.policy_name")
|
||||
claims = auth.get("bound_claims") or {}
|
||||
for key in ("service_account_names", "service_account_namespaces"):
|
||||
values = claims.get(key)
|
||||
if not (isinstance(values, list) and len(values) == 1 and NAME_RE.match(str(values[0]))):
|
||||
errors.append(f"openbao.auth.bound_claims.{key} must name exactly one value")
|
||||
admission = ccr.get("admission")
|
||||
if not isinstance(admission, dict) or not admission.get("standard") or not admission.get("executor_identity"):
|
||||
errors.append("admission must name the admission standard and executor identity")
|
||||
if (ccr.get("access_frontdoor") or {}).get("resolvable") is not False:
|
||||
errors.append("runtime credential frontdoor must remain disabled")
|
||||
if (ccr.get("agent_fence") or {}).get("data_and_metadata_denied") is not True:
|
||||
errors.append("coding agents must be fenced from the data and metadata paths")
|
||||
if ccr.get("status") in APPROVED:
|
||||
approved = _approvals(ccr)
|
||||
for role in (ccr.get("review") or {}).get("required_approvers") or []:
|
||||
if role not in approved:
|
||||
errors.append("explicit role approval missing: " + role)
|
||||
for section, keys in {"verification": ["positive", "negative", "activation_conditions"],
|
||||
"lifecycle": ["deactivate", "rotate", "compromised"]}.items():
|
||||
for key in keys:
|
||||
if not (ccr.get(section) or {}).get(key):
|
||||
errors.append(f"{section}.{key} required")
|
||||
return errors
|
||||
|
||||
|
||||
def procedure_note(ccr: dict[str, Any]) -> str:
|
||||
openbao = ccr["openbao"]
|
||||
return (f"Attended platform-admin applies policy {openbao['policy_name']} and Kubernetes role "
|
||||
f"{openbao['auth']['role']} from a reviewed commit, sets cas_required on {openbao['kv_path']} "
|
||||
f"and proves the negatives ({DOC}). The workload may only write the next version with CAS.")
|
||||
60
tests/test_workload_kv_write_next_version.py
Normal file
60
tests/test_workload_kv_write_next_version.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""workload-kv-write-next-version contract (NetKingdom NK-WP-0056 bounded executor); no values."""
|
||||
import copy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location("wkw", ROOT / "scripts/workload_kv_write_next_version.py")
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
CCR = yaml.safe_load((ROOT / "credential-change-requests/CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml").read_text())
|
||||
|
||||
|
||||
def test_checked_in_request_is_valid():
|
||||
assert m.validate_contract(CCR) == []
|
||||
|
||||
|
||||
def test_policy_artifact_matches_contract():
|
||||
path = ROOT / CCR["openbao"]["policy_file"]
|
||||
assert path.read_text() == m.policy_hcl(CCR)
|
||||
hcl = m.policy_hcl(CCR)
|
||||
assert '"read", "update"' in hcl and "create" not in hcl and "metadata" not in hcl
|
||||
assert 'required_parameters = ["options"]' in hcl
|
||||
|
||||
|
||||
def test_write_shape_cannot_be_widened():
|
||||
for change in ({"cas_required": False}, {"create": True}, {"metadata_read": True}, {"delete": True},
|
||||
{"mode": "any"}):
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["write"].update(change)
|
||||
assert m.validate_contract(ccr), change
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["kv_path"] = "platform/workloads/net-kingdom/*"
|
||||
assert m.validate_contract(ccr)
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["metadata_paths"] = ["x"]
|
||||
assert m.validate_contract(ccr)
|
||||
|
||||
|
||||
def test_auth_is_one_serviceaccount_one_policy_15m():
|
||||
for key, value in (("ttl", "1h"), ("token_no_default_policy", False), ("audience", "x")):
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["auth"][key] = value
|
||||
assert m.validate_contract(ccr), key
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["auth"]["policies"].append("other")
|
||||
assert "openbao.auth.policies must contain exactly openbao.policy_name" in m.validate_contract(ccr)
|
||||
ccr = copy.deepcopy(CCR); ccr["openbao"]["auth"]["bound_claims"]["service_account_names"].append("x")
|
||||
assert m.validate_contract(ccr)
|
||||
|
||||
|
||||
def test_fence_frontdoor_and_admission_required():
|
||||
ccr = copy.deepcopy(CCR); ccr["agent_fence"]["data_and_metadata_denied"] = False
|
||||
assert "coding agents must be fenced from the data and metadata paths" in m.validate_contract(ccr)
|
||||
ccr = copy.deepcopy(CCR); ccr["access_frontdoor"]["resolvable"] = True
|
||||
assert "runtime credential frontdoor must remain disabled" in m.validate_contract(ccr)
|
||||
ccr = copy.deepcopy(CCR); del ccr["admission"]
|
||||
assert m.validate_contract(ccr)
|
||||
|
||||
|
||||
def test_approved_stage_needs_every_required_approval():
|
||||
ccr = copy.deepcopy(CCR); ccr["status"] = "approved"
|
||||
ccr["review"]["comments"] = [{"reviewer": "platform-owner", "decision": "approved"}]
|
||||
assert m.validate_contract(ccr) == ["explicit role approval missing: net-kingdom-owner"]
|
||||
99
workplans/RPF-WP-0069-bounded-executor-lanes.md
Normal file
99
workplans/RPF-WP-0069-bounded-executor-lanes.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
id: RPF-WP-0069
|
||||
type: workplan
|
||||
title: "Bounded executor lanes: write-next-version type, journey read and audit sender (NK-WP-0056)"
|
||||
domain: financials
|
||||
repo: railiance-platform
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude
|
||||
topic_slug: railiance
|
||||
created: "2026-10-31"
|
||||
updated: "2026-10-31"
|
||||
related:
|
||||
- NK-WP-0056
|
||||
- NK-WP-0040
|
||||
- RPF-WP-0067
|
||||
---
|
||||
|
||||
# RPF-WP-0069 — Bounded executor lanes (NK-WP-0056-T02 owner answer)
|
||||
|
||||
Receiving workplan for NetKingdom's owner request (State Hub message
|
||||
`9d08288d-e866-4e1c-9c0b-f1cc0968bd4d`; net-kingdom
|
||||
`docs/proposals/2026-10-31-nk56-t02-owner-requests.md`). Owner answer:
|
||||
`docs/credential-lane-designs/bounded-executor.md`. No live, cluster or OpenBao action is
|
||||
part of T01; every CCR stays `proposed` until its approvers act.
|
||||
|
||||
## Answer the request and add the write-next-version request type
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Answer doc written; new contract-only request type `workload-kv-write-next-version`
|
||||
(`scripts/workload_kv_write_next_version.py`, `tests/test_workload_kv_write_next_version.py`,
|
||||
dispatch in `scripts/credential-change.py`); CCR-2026-0058 drafted `proposed` with policy artifact
|
||||
`openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl`.
|
||||
|
||||
## Review CCR-2026-0058 (founder gate)
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T02
|
||||
status: wait
|
||||
priority: high
|
||||
needs_human: true
|
||||
```
|
||||
|
||||
platform-owner and net-kingdom-owner approve or change CCR-2026-0058. The founder chooses
|
||||
standing or window-scoped write role (recommendation: standing for the pilot). Binding is
|
||||
confirmed only after `sso/nk-bind-executor` exists from the reviewed rapp-* package.
|
||||
|
||||
## Attended setup and verification of CCR-2026-0058
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T03
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
After T02: an attended platform-admin (aal2) session sets `cas_required` on the svc-authelia key,
|
||||
re-checks the coding-agent fence, applies policy and role, and records value-free positive and
|
||||
negative receipts. This needs an owner setup script modelled on net-kingdom `bind-lane.py`
|
||||
setup/verify; write it once approval exists. The founder runs it.
|
||||
|
||||
## Journey-secret read lane for the executor (NK-WP-0056-T10)
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T04
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Draft a `workload-kv-read` CCR (native login, data read only, 15m) for
|
||||
`platform/workloads/net-kingdom/synthetic-journey/syn-journey`, stating that it widens
|
||||
CCR-2026-0057's `runtime_access: false`. Waits for T10 scheduling and the ServiceAccount.
|
||||
|
||||
## Executor audit-core sender (NK-WP-0040)
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T05
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
After audit-core admits the sender and names it, draft a `workload-kv-read` CCR in the
|
||||
CCR-2026-0021/0022 shape: custody `platform/workloads/net-kingdom/bounded-executor/audit-sender`,
|
||||
ESO into `sso/nk-bind-executor-audit`, ClusterSecretStore restricted to `sso`.
|
||||
|
||||
## LLDAP credential custody for the executor
|
||||
|
||||
```task
|
||||
id: RPF-WP-0069-T06
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
After secrets-engine answers the lane shape for the executor's LLDAP credential (its Q2),
|
||||
draft the custody CCR here.
|
||||
Loading…
Add table
Add a link
Reference in a new issue