AUDIT-WP-0018-T04: founder-run seed of the nk-bounded-executor bearer; T05 live activation waits
- scripts/seed-nk-bounded-executor-sender.py (+ silent attended wrapper): one attended process mints once, writes CCR-2026-0063 custody (platform/workloads/net-kingdom/bounded-executor/audit-sender, cas 0) and appends the sender to the audit-core registry (cas), syncs audit-core-senders and compares by hash, restarts the receiver, probes the live scope without writing an event, and checks sso/nk-bind-executor-audit by hash when it exists. Receipt only; no value printed or stored. - Refuses unless CCR-2026-0063 is approved by its three owners, cluster is railiance01, receiver image and live scope ConfigMap match, the ingress policy exists and the coding-agent boundary covers the custody path. - tests/test_seed_nk_bounded_executor_sender.py: 16 synthetic tests, including the probe run against the real receiver over HTTP. - docs/bounded-executor-receipts.md: 2026-11-04 live state and founder runbook (single-object ingress apply, preflight, seed, verify). - Read-only check: ingress policy not live; other nine policies match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 523928@bnt-lap001 Assistant-Session: 6c737afb-9c14-4152-9f02-477214b9c350
This commit is contained in:
parent
06d0029f1e
commit
876c3f6647
6 changed files with 855 additions and 1 deletions
|
|
@ -115,6 +115,8 @@
|
|||
| task | AUDIT-WP-0018-T01 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T02 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T03 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T04 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T05 | wait | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| intake | AUDIT-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | AUDIT-IN-0002 | closed | — | intakes/intakes.md |
|
||||
| intake | AUDIT-IN-0003 | closed | — | intakes/intakes.md |
|
||||
|
|
|
|||
|
|
@ -229,3 +229,114 @@ Released 2026-11-03: receiver `sha256:5f3d01c8…` (built from 2b8c909) and
|
|||
the scope ConfigMap carrying `closing_classes` are live on railiance01. The
|
||||
detection is inert until the `nk-bounded-executor` token is seeded and the
|
||||
executor emits.
|
||||
|
||||
## Status 2026-11-04 — what is still needed live (NK-WP-0056-T15/T16)
|
||||
|
||||
Checked read-only on 2026-11-04 with the default (limited) identity:
|
||||
|
||||
- **Ingress: not live.** `audit-core-nk-bounded-executor-ingress` is the only
|
||||
object in `deploy/networkpolicies.yaml` that is absent on railiance01. The
|
||||
nine other policies in that file match the live specs exactly. The
|
||||
executor's own egress (`sso/bounded-executor`, to `audit-core` pods on 8080) is
|
||||
already live.
|
||||
- **Scope ConfigMap:** the limited identity cannot read ConfigMaps in
|
||||
`audit-core`. The release on 2026-11-03 is recorded in 06d0029. The seed
|
||||
script below refuses unless the live entry equals `deploy/senders-scope.json`.
|
||||
- **Bearer: not minted.** The registry is OpenBao
|
||||
`platform/workloads/audit-core/senders` (field `senders.json`). ExternalSecret
|
||||
`audit-core/audit-core-senders` projects it into the receiver. The same
|
||||
pattern served tenant-engine and the CCR-2026-0021/0022 senders.
|
||||
|
||||
How the token reaches both sides. One founder-run process,
|
||||
`scripts/seed-nk-bounded-executor-sender.py`, does the following:
|
||||
|
||||
1. It mints one bearer.
|
||||
2. It writes the bearer with cas 0 to
|
||||
`platform/workloads/net-kingdom/bounded-executor/audit-sender` (`AUDIT_TOKEN`,
|
||||
`CUSTODY_REQUEST=CCR-2026-0063`).
|
||||
3. It appends the `nk-bounded-executor` entry to the registry. The entry is the
|
||||
declared scope plus the token, written with cas on the version read. Every
|
||||
other sender stays byte-equal.
|
||||
4. It force-syncs `audit-core-senders` and compares the token by SHA-256 only.
|
||||
5. It restarts the receiver.
|
||||
6. It proves the bearer without writing an event. Inside the receiver pod, with
|
||||
the bearer on stdin:
|
||||
- a read gets 403 (write-only);
|
||||
- reconciliation for its own source and tenant `platform` gets 200;
|
||||
- tenant `tenant:platform` gets 403;
|
||||
- another source gets 403.
|
||||
An unknown bearer gets 401.
|
||||
7. It force-syncs `sso/nk-bind-executor-audit` and compares `audit-token` by
|
||||
hash, but only if that ExternalSecret exists.
|
||||
|
||||
The script is silent. The receipt holds versions, booleans and a 12-hex
|
||||
fingerprint. A rerun reuses its own custody and writes nothing new. It refuses
|
||||
foreign or drifted custody, a sender registered without custody, a token
|
||||
collision and a concurrent registry write. Tests:
|
||||
`tests/test_seed_nk_bounded_executor_sender.py`.
|
||||
|
||||
The script refuses before any write unless all of the following hold:
|
||||
|
||||
- CCR-2026-0063 is `approved` or later, with all three owner reviews;
|
||||
- the cluster is railiance01 (kube-system UID);
|
||||
- the receiver runs the pinned image;
|
||||
- the live scope ConfigMap matches;
|
||||
- the ingress policy exists;
|
||||
- the coding-agent boundary already denies the custody data and metadata paths.
|
||||
|
||||
It does not create the reader policy, the role or the boundary
|
||||
(railiance-platform). It also does not create the `sso` ClusterSecretStore and
|
||||
ExternalSecret, which belong to the rapp-bounded-executor package (CCR-2026-0063
|
||||
`delivery.target`).
|
||||
|
||||
## Founder runbook — nk-bounded-executor sender
|
||||
|
||||
Run these in order. Steps 1 and 2 can run now. Step 3 depends on CCR-2026-0063:
|
||||
railiance-platform must have it approved by platform-owner, audit-core-owner and
|
||||
net-kingdom-owner, and must have applied its reader policy, role and
|
||||
coding-agent boundary entries.
|
||||
|
||||
**1. Ingress (NK-WP-0056-T16).** This applies exactly one object. Run the server
|
||||
dry-run first, then the apply:
|
||||
|
||||
```bash
|
||||
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl apply --dry-run=server -f -
|
||||
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl apply -f -
|
||||
```
|
||||
|
||||
**2. Read-only preflight** (prints no secrets):
|
||||
|
||||
```bash
|
||||
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get configmap audit-core-senders-scope -o jsonpath='{.data.senders-scope\.json}' | python3 -c 'import sys,json; print([e for e in json.load(sys.stdin) if e["name"]=="nk-bounded-executor"])'
|
||||
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get deploy audit-core -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
|
||||
bridge status
|
||||
```
|
||||
|
||||
The image must end in
|
||||
`sha256:5f3d01c8a1f90411a1429451b46c66a9ac9262fc7fce68790c16e22eeb7871a6`.
|
||||
The `openbao-ui-railiance01` tunnel must be up.
|
||||
|
||||
**3. Seed and reload (NK-WP-0056-T15). Needs CCR-2026-0063 approved and applied.**
|
||||
This is one silent command. Read the result from the receipt, not from the
|
||||
terminal:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- /home/worsch/audit-core/scripts/attended-seed-nk-bounded-executor-sender.sh /home/worsch/audit-core/docs/evidence/nk-bounded-executor-sender-seed-r1.json
|
||||
```
|
||||
|
||||
The receipt is created exclusively. If you rerun, use `-r2.json`. The receiver
|
||||
restart is part of this step. No separate rollout is needed.
|
||||
|
||||
**4. Verify** (read-only):
|
||||
|
||||
```bash
|
||||
python3 -c 'import json; r=json.load(open("/home/worsch/audit-core/docs/evidence/nk-bounded-executor-sender-seed-r1.json")); print(r["status"], r.get("error"), r.get("receiver_probe"), r.get("registry_secret_hash_match"), r.get("sso_secret_hash_match"), r["credential_values_emitted"])'
|
||||
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get networkpolicy audit-core-nk-bounded-executor-ingress
|
||||
```
|
||||
|
||||
The expected status is `seeded_pending_sso_delivery`, or `seeded_and_delivered`
|
||||
once the rapp package's ExternalSecret exists. The expected probe is
|
||||
`{"read": 403, "own_tenant": 200, "other_tenant": 403, "other_source": 403}`.
|
||||
After the rapp package applies `sso/nk-bind-executor-audit`, rerun step 3 with
|
||||
`-r2.json`. It writes no custody or registry and confirms the `sso` copy by
|
||||
hash.
|
||||
|
|
|
|||
12
scripts/attended-seed-nk-bounded-executor-sender.sh
Executable file
12
scripts/attended-seed-nk-bounded-executor-sender.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# Silent child for the attended nk-bounded-executor sender seed (AUDIT-WP-0018-T04,
|
||||
# NK-WP-0056-T15, CCR-2026-0063). Founder-run under
|
||||
# warden access openbao-platform-admin-login --exec -- <this script> <receipt>
|
||||
# Results go to the receipt only; any output would fail the warden envelope closed.
|
||||
# Inside the lane HOME is the warden envelope, so the kubeconfig default is absolute.
|
||||
set -euo pipefail
|
||||
receipt="${1:?receipt path required}"
|
||||
exec python3 "$(dirname "$0")/seed-nk-bounded-executor-sender.py" \
|
||||
--kubeconfig "${RAILIANCE01_KUBECONFIG:-/home/worsch/.kube/config-railiance01}" \
|
||||
--confirm 'SEED nk-bounded-executor CCR-2026-0063' \
|
||||
--receipt "$receipt" >/dev/null 2>&1
|
||||
427
scripts/seed-nk-bounded-executor-sender.py
Executable file
427
scripts/seed-nk-bounded-executor-sender.py
Executable file
|
|
@ -0,0 +1,427 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Attended seed of the nk-bounded-executor sender bearer (AUDIT-WP-0018-T04).
|
||||
|
||||
NetKingdom NK-WP-0056-T15. railiance-platform CCR-2026-0063 holds the custody
|
||||
contract. One process mints one bearer and writes it to the two places that need it:
|
||||
|
||||
custody: platform/data/workloads/net-kingdom/bounded-executor/audit-sender
|
||||
fields AUDIT_TOKEN, CUSTODY_REQUEST=CCR-2026-0063 (cas 0)
|
||||
registry: platform/data/workloads/audit-core/senders
|
||||
one appended entry "nk-bounded-executor" (cas on the version read)
|
||||
|
||||
It then syncs ExternalSecret audit-core/audit-core-senders, restarts the receiver,
|
||||
and proves without writing an event that the live receiver knows the bearer
|
||||
with the exact registered scope. If railiance-platform's ExternalSecret
|
||||
sso/nk-bind-executor-audit exists, the script syncs it and compares the token by hash.
|
||||
|
||||
This script is silent. The wrapper discards stdout and stderr, so `warden access --exec`
|
||||
fails closed on any output. The token is never printed, never passed on a command line
|
||||
and never written to the receipt. The receipt holds versions, booleans and a
|
||||
12-hex SHA-256 fingerprint.
|
||||
|
||||
Re-running is safe. Existing custody that this request wrote (version 1,
|
||||
CUSTODY_REQUEST matching) is reused. A registry entry that equals the desired
|
||||
entry is not rewritten. Anything else refuses and changes nothing: a sender
|
||||
without custody, a drifted entry, another request's custody, a token
|
||||
collision or a concurrent registry write.
|
||||
|
||||
Founder-run only. The exact command is in docs/bounded-executor-receipts.md, section
|
||||
"Founder runbook". The script does not create OpenBao policies or roles, the coding-agent
|
||||
boundary, the ClusterSecretStore or the ExternalSecret in sso. Those belong to
|
||||
railiance-platform and the rapp-bounded-executor package.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SENDER = "nk-bounded-executor"
|
||||
SOURCE = "net-kingdom.bounded-executor"
|
||||
TENANT = "platform"
|
||||
CCR_ID = "CCR-2026-0063"
|
||||
CCR_FILE = Path("/home/worsch/railiance-platform/credential-change-requests/"
|
||||
"CCR-2026-0063-nk-bind-executor-audit-sender.yaml")
|
||||
CCR_APPROVERS = ("platform-owner", "audit-core-owner", "net-kingdom-owner")
|
||||
CONFIRM = "SEED nk-bounded-executor CCR-2026-0063"
|
||||
|
||||
REGISTRY = "platform/data/workloads/audit-core/senders"
|
||||
CUSTODY_KV = "platform/workloads/net-kingdom/bounded-executor/audit-sender"
|
||||
CUSTODY_DATA = "platform/data/workloads/net-kingdom/bounded-executor/audit-sender"
|
||||
CUSTODY_META = "platform/metadata/workloads/net-kingdom/bounded-executor/audit-sender"
|
||||
BOUNDARY = "sys/policies/acl/agent-high-risk-boundary"
|
||||
|
||||
CLUSTER_UID = "a553c742-0115-43d4-99a4-a5ca56fe0786" # railiance01 kube-system
|
||||
SSO_SECRET = "nk-bind-executor-audit"
|
||||
SSO_KEY = "audit-token"
|
||||
TOKEN_SHAPE = re.compile(r"[A-Za-z0-9_-]{64}")
|
||||
|
||||
# Runs inside the receiver pod. The bearer arrives on stdin and leaves only as
|
||||
# HTTP status codes. No request writes an event.
|
||||
RECEIVER_PROBE = r'''import json,sys,urllib.request,urllib.error
|
||||
tok=sys.stdin.read().strip()
|
||||
def get(p,auth=True):
|
||||
r=urllib.request.Request("http://127.0.0.1:8080"+p)
|
||||
if auth: r.add_header("Authorization","Bearer "+tok)
|
||||
try:
|
||||
with urllib.request.urlopen(r,timeout=10) as x: return x.status
|
||||
except urllib.error.HTTPError as e: return e.code
|
||||
w="since=2026-01-01T00:00:00%2B00:00&until=2026-01-02T00:00:00%2B00:00"
|
||||
print(json.dumps({
|
||||
"read":get("/v1/events/probe-not-an-id"),
|
||||
"own_tenant":get("/v1/reconciliation?source=net-kingdom.bounded-executor&tenant=platform&"+w),
|
||||
"other_tenant":get("/v1/reconciliation?source=net-kingdom.bounded-executor&tenant=tenant:platform&"+w),
|
||||
"other_source":get("/v1/reconciliation?source=tenant-engine&tenant=platform&"+w)}))
|
||||
'''
|
||||
PROBE_EXPECTED = {"read": 403, "own_tenant": 200, "other_tenant": 403, "other_source": 403}
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
"""A named refusal. The message is a reason code, never a value."""
|
||||
|
||||
|
||||
class NotFound(Exception):
|
||||
"""The OpenBao path holds no value."""
|
||||
|
||||
|
||||
def require(condition: bool, code: str) -> None:
|
||||
if not condition:
|
||||
raise Refused(code)
|
||||
|
||||
|
||||
def fingerprint(token: str) -> str:
|
||||
return hashlib.sha256(token.encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
# --- contract ---------------------------------------------------------------
|
||||
|
||||
def scope_entry(path: Path = ROOT / "deploy" / "senders-scope.json") -> dict:
|
||||
rows = json.loads(path.read_text())
|
||||
entry = next((r for r in rows if r.get("name") == SENDER), None)
|
||||
require(entry is not None, "scope_entry_missing")
|
||||
require(entry.get("sources") == [SOURCE] and entry.get("tenants") == [TENANT]
|
||||
and entry.get("may_write") is True and entry.get("may_read") is False
|
||||
and entry.get("secret_policy") == "reject"
|
||||
and entry.get("evidence_kind") == "load-bearing", "scope_entry_drift")
|
||||
return entry
|
||||
|
||||
|
||||
def desired(token: str, scope: dict) -> dict:
|
||||
"""Registry entry: exactly the declared scope plus the bearer."""
|
||||
entry = {"name": SENDER, "tokens": [token]}
|
||||
entry.update({k: copy.deepcopy(v) for k, v in scope.items() if k != "name"})
|
||||
return entry
|
||||
|
||||
|
||||
def check_ccr(doc: dict) -> None:
|
||||
require(doc.get("id") == CCR_ID, "ccr_identity_mismatch")
|
||||
require(doc.get("status") in {"approved", "applied", "verified", "active"}, "approved_ccr_required")
|
||||
comments = (doc.get("review") or {}).get("comments") or []
|
||||
for owner in CCR_APPROVERS:
|
||||
require(any(isinstance(c, dict) and c.get("decision") == "approved"
|
||||
and owner in str(c.get("reviewer", "")) for c in comments),
|
||||
"named_owner_reviews_required")
|
||||
ob = doc.get("openbao") or {}
|
||||
require(ob.get("kv_path") == CUSTODY_KV and ob.get("fields") == ["AUDIT_TOKEN", "CUSTODY_REQUEST"]
|
||||
and ob.get("metadata_read") is False, "ccr_custody_contract_mismatch")
|
||||
delivery = doc.get("delivery") or {}
|
||||
require(delivery.get("namespace") == "sso", "ccr_delivery_mismatch")
|
||||
|
||||
|
||||
def load_ccr(path: Path = CCR_FILE) -> tuple[dict, str]:
|
||||
import yaml # system PyYAML; only needed for the CCR file
|
||||
|
||||
raw = path.read_bytes()
|
||||
return yaml.safe_load(raw), hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
# --- process boundaries -----------------------------------------------------
|
||||
|
||||
def bao(args: list[str], *, payload: dict | None = None) -> dict:
|
||||
env = os.environ.copy()
|
||||
for name in ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN"):
|
||||
env.pop(name, None)
|
||||
proc = subprocess.run(["bao", *args],
|
||||
input=None if payload is None else json.dumps(payload).encode(),
|
||||
capture_output=True, env=env, check=False, timeout=60)
|
||||
if proc.returncode != 0:
|
||||
err = proc.stderr.decode(errors="replace").lower() # inspected, never emitted
|
||||
if "no value found" in err or "404" in err:
|
||||
raise NotFound()
|
||||
raise Refused("bao_failed")
|
||||
return json.loads(proc.stdout.decode() or "{}")
|
||||
|
||||
|
||||
class Kube:
|
||||
def __init__(self, kubeconfig: str):
|
||||
self.base = ["kubectl", "--kubeconfig", kubeconfig, "--request-timeout=30s"]
|
||||
|
||||
def run(self, args: list[str], *, stdin: bytes | None = None) -> subprocess.CompletedProcess:
|
||||
return subprocess.run([*self.base, *args], input=stdin, capture_output=True,
|
||||
check=False, timeout=90)
|
||||
|
||||
def json(self, args: list[str], code: str = "kubectl_failed") -> dict:
|
||||
proc = self.run([*args, "-o", "json"])
|
||||
require(proc.returncode == 0, code)
|
||||
return json.loads(proc.stdout.decode())
|
||||
|
||||
def exists(self, args: list[str]) -> bool:
|
||||
return self.run([*args, "-o", "name"]).returncode == 0
|
||||
|
||||
|
||||
# --- OpenBao ----------------------------------------------------------------
|
||||
|
||||
def snapshot(call=bao) -> tuple[int, dict, list, set]:
|
||||
raw = call(["read", "-format=json", REGISTRY])["data"]
|
||||
version = raw["metadata"]["version"]
|
||||
require(type(version) is int and version > 0, "registry_version_missing")
|
||||
body = raw["data"]
|
||||
require(isinstance(body, dict) and isinstance(body.get("senders.json"), str), "registry_shape_invalid")
|
||||
try:
|
||||
rows = json.loads(body["senders.json"])
|
||||
except ValueError:
|
||||
raise Refused("registry_shape_invalid") from None
|
||||
require(isinstance(rows, list) and bool(rows), "registry_must_preserve_existing_senders")
|
||||
names: set = set()
|
||||
tokens: set = set()
|
||||
for row in rows:
|
||||
require(isinstance(row, dict) and isinstance(row.get("name"), str) and row["name"]
|
||||
and row["name"] not in names, "registry_identity_invalid_or_duplicate")
|
||||
names.add(row["name"])
|
||||
values = row.get("tokens") or ([row["token"]] if row.get("token") else [])
|
||||
require(isinstance(values, list) and bool(values), "registry_tokens_invalid")
|
||||
for value in values:
|
||||
require(isinstance(value, str) and value and value not in tokens, "registry_token_collision")
|
||||
tokens.add(value)
|
||||
return version, body, rows, tokens
|
||||
|
||||
|
||||
def read_custody(call=bao) -> dict | None:
|
||||
try:
|
||||
return call(["read", "-format=json", CUSTODY_DATA])["data"]
|
||||
except NotFound:
|
||||
return None
|
||||
|
||||
|
||||
def seed(scope: dict, receipt: dict, save, call=bao) -> str:
|
||||
"""Mint or reuse the bearer, then merge it into the registry. Returns the token."""
|
||||
version, body, rows, used = snapshot(call)
|
||||
originals = copy.deepcopy(rows)
|
||||
found = next((r for r in rows if r["name"] == SENDER), None)
|
||||
receipt.update(registry_version_before=version, other_senders=len(rows) - (found is not None))
|
||||
existing = read_custody(call)
|
||||
if existing is None:
|
||||
require(found is None, "registry_sender_without_custody")
|
||||
token = secrets.token_urlsafe(48)
|
||||
require(TOKEN_SHAPE.fullmatch(token) is not None and token not in used, "sender_token_collision")
|
||||
result = call(["write", "-format=json", CUSTODY_DATA, "-"],
|
||||
payload={"options": {"cas": 0},
|
||||
"data": {"AUDIT_TOKEN": token, "CUSTODY_REQUEST": CCR_ID}})
|
||||
require((result.get("data") or {}).get("version") == 1, "initial_custody_version_mismatch")
|
||||
receipt.update(custody="created", custody_version=1)
|
||||
else:
|
||||
data = existing.get("data") or {}
|
||||
require(existing["metadata"]["version"] == 1 and set(data) == {"AUDIT_TOKEN", "CUSTODY_REQUEST"}
|
||||
and data["CUSTODY_REQUEST"] == CCR_ID, "custody_provenance_mismatch")
|
||||
token = data["AUDIT_TOKEN"]
|
||||
require(isinstance(token, str) and TOKEN_SHAPE.fullmatch(token) is not None, "custody_token_shape")
|
||||
if found is None:
|
||||
require(token not in used, "sender_token_collision")
|
||||
receipt.update(custody="reused", custody_version=1)
|
||||
receipt["token_sha256_12"] = fingerprint(token)
|
||||
want = desired(token, scope)
|
||||
if found is not None:
|
||||
require(found == want, "registered_sender_drift")
|
||||
receipt["registry"] = "already_present"
|
||||
else:
|
||||
receipt["phase"] = "registry_cas"
|
||||
save()
|
||||
updated = [*rows, want]
|
||||
payload = dict(body, **{"senders.json": json.dumps(updated, separators=(",", ":"))})
|
||||
call(["write", "-format=json", REGISTRY, "-"], payload={"options": {"cas": version}, "data": payload})
|
||||
receipt["registry"] = "appended"
|
||||
after_version, after_body, after_rows, _ = snapshot(call)
|
||||
require(all(r in after_rows for r in originals) and want in after_rows
|
||||
and len(after_rows) == len(originals) + (found is None)
|
||||
and all(after_body.get(k) == v for k, v in body.items() if k != "senders.json"),
|
||||
"registry_readback_mismatch")
|
||||
receipt.update(registry_version_after=after_version, unchanged_other_senders=True)
|
||||
save()
|
||||
return token
|
||||
|
||||
|
||||
# --- Kubernetes -------------------------------------------------------------
|
||||
|
||||
def secret_entry_sha(kube: Kube) -> str | None:
|
||||
import base64
|
||||
|
||||
secret = kube.json(["-n", "audit-core", "get", "secret", "audit-core-senders"])
|
||||
raw = (secret.get("data") or {}).get("senders.json")
|
||||
if not raw:
|
||||
return None
|
||||
rows = json.loads(base64.b64decode(raw))
|
||||
row = next((r for r in rows if isinstance(r, dict) and r.get("name") == SENDER), None)
|
||||
if not row or not row.get("tokens"):
|
||||
return None
|
||||
return hashlib.sha256(row["tokens"][-1].encode()).hexdigest()
|
||||
|
||||
|
||||
def wait_for(check, timeout: int, interval: int = 3) -> bool:
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
if check():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(interval)
|
||||
return False
|
||||
|
||||
|
||||
def receiver_pod(kube: Kube, image: str) -> str | None:
|
||||
dep = kube.json(["-n", "audit-core", "get", "deployment", "audit-core"])
|
||||
if dep["spec"]["template"]["spec"]["containers"][0]["image"] != image:
|
||||
return None
|
||||
if dep.get("status", {}).get("readyReplicas") != 1 or \
|
||||
dep["status"].get("observedGeneration", 0) < dep["metadata"]["generation"]:
|
||||
return None
|
||||
pods = kube.json(["-n", "audit-core", "get", "pods", "-l",
|
||||
"app.kubernetes.io/name=audit-core,app.kubernetes.io/component=receiver"])["items"]
|
||||
if len(pods) != 1 or pods[0]["metadata"].get("deletionTimestamp"):
|
||||
return None
|
||||
statuses = pods[0].get("status", {}).get("containerStatuses") or []
|
||||
if not statuses or not all(s.get("ready") for s in statuses) \
|
||||
or pods[0]["spec"]["containers"][0]["image"] != image:
|
||||
return None
|
||||
return pods[0]["metadata"]["name"]
|
||||
|
||||
|
||||
def live_scope_matches(kube: Kube, scope: dict) -> bool:
|
||||
cm = kube.json(["-n", "audit-core", "get", "configmap", "audit-core-senders-scope"], "scope_configmap_unreadable")
|
||||
rows = json.loads((cm.get("data") or {}).get("senders-scope.json") or "[]")
|
||||
return next((r for r in rows if r.get("name") == SENDER), None) == scope
|
||||
|
||||
|
||||
def expected_image() -> str:
|
||||
text = (ROOT / "deploy" / "audit-core.yaml").read_text()
|
||||
match = re.search(r"image:\s*(forgejo\.coulomb\.social/coulomb/audit-core@sha256:[0-9a-f]{64})", text)
|
||||
require(match is not None, "receiver_digest_required")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def probe(kube: Kube, pod: str, token: str) -> dict:
|
||||
proc = kube.run(["-n", "audit-core", "exec", "-i", pod, "-c", "audit-core", "--",
|
||||
"python", "-c", RECEIVER_PROBE], stdin=token.encode())
|
||||
require(proc.returncode == 0, "receiver_probe_failed")
|
||||
return json.loads(proc.stdout.decode())
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--kubeconfig", required=True)
|
||||
parser.add_argument("--confirm", required=True)
|
||||
parser.add_argument("--receipt", type=Path, required=True)
|
||||
args = parser.parse_args(argv)
|
||||
receipt: dict = {"schema": "audit-core.sender-seed.v1", "sender": SENDER, "ccr": CCR_ID,
|
||||
"custody_path": CUSTODY_KV, "status": "refused",
|
||||
"credential_values_emitted": False,
|
||||
"started_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat()}
|
||||
fd = os.open(args.receipt, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
||||
|
||||
def save() -> None:
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
os.ftruncate(fd, 0)
|
||||
os.write(fd, (json.dumps(receipt, indent=2) + "\n").encode())
|
||||
os.fsync(fd)
|
||||
|
||||
try:
|
||||
save()
|
||||
require(args.confirm == CONFIRM, "exact_confirmation_required")
|
||||
scope = scope_entry()
|
||||
ccr, ccr_sha = load_ccr()
|
||||
check_ccr(ccr)
|
||||
receipt["ccr_sha256"] = ccr_sha
|
||||
require(Path.home().parent.name == ".warden-attended-login", "attended_warden_envelope_required")
|
||||
require(not any(os.environ.get(n) for n in ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")),
|
||||
"token_env_must_be_unset")
|
||||
identity = bao(["token", "lookup", "-format=json"])["data"]
|
||||
policies = identity.get("policies") or []
|
||||
require("platform-admin" in policies and "root" not in policies, "attended_platform_admin_required")
|
||||
|
||||
kube = Kube(args.kubeconfig)
|
||||
ns = kube.json(["get", "namespace", "kube-system"], "not_railiance01")
|
||||
require(ns.get("metadata", {}).get("uid") == CLUSTER_UID, "not_railiance01")
|
||||
image = expected_image()
|
||||
require(receiver_pod(kube, image) is not None, "receiver_not_current")
|
||||
require(live_scope_matches(kube, scope), "live_scope_configmap_mismatch")
|
||||
require(kube.exists(["-n", "audit-core", "get", "networkpolicy",
|
||||
"audit-core-nk-bounded-executor-ingress"]), "ingress_policy_not_applied")
|
||||
boundary = bao(["read", "-format=json", BOUNDARY])["data"]["policy"]
|
||||
require(all(f'"{p}"' in boundary for p in (CUSTODY_DATA, CUSTODY_META)),
|
||||
"coding_agent_boundary_missing")
|
||||
receipt.update(phase="preflight_ok", receiver_image=image)
|
||||
save()
|
||||
|
||||
token = seed(scope, receipt, save)
|
||||
want = hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
receipt["phase"] = "registry_sync"
|
||||
save()
|
||||
require(kube.run(["-n", "audit-core", "annotate", "externalsecret", "audit-core-senders",
|
||||
f"force-sync={int(time.time())}", "--overwrite"]).returncode == 0,
|
||||
"externalsecret_annotate_failed")
|
||||
require(wait_for(lambda: secret_entry_sha(kube) == want, 120), "registry_secret_sync_timeout")
|
||||
receipt["registry_secret_hash_match"] = True
|
||||
|
||||
receipt["phase"] = "receiver_reload"
|
||||
save()
|
||||
require(kube.run(["-n", "audit-core", "rollout", "restart", "deployment/audit-core"]).returncode == 0,
|
||||
"receiver_restart_failed")
|
||||
time.sleep(5)
|
||||
holder: dict = {}
|
||||
require(wait_for(lambda: holder.update(pod=receiver_pod(kube, image)) or holder["pod"], 240, 5),
|
||||
"receiver_not_ready_after_restart")
|
||||
result = probe(kube, holder["pod"], token)
|
||||
receipt["receiver_probe"] = result
|
||||
require(result == PROBE_EXPECTED, "receiver_probe_mismatch")
|
||||
|
||||
receipt["phase"] = "sso_delivery"
|
||||
save()
|
||||
if kube.exists(["-n", "sso", "get", "externalsecret", SSO_SECRET]):
|
||||
kube.run(["-n", "sso", "annotate", "externalsecret", SSO_SECRET,
|
||||
f"force-sync={int(time.time())}", "--overwrite"])
|
||||
|
||||
def sso_sha() -> bool:
|
||||
import base64
|
||||
secret = kube.json(["-n", "sso", "get", "secret", SSO_SECRET])
|
||||
value = base64.b64decode((secret.get("data") or {})[SSO_KEY]).decode()
|
||||
return hashlib.sha256(value.encode()).hexdigest() == want
|
||||
|
||||
require(wait_for(sso_sha, 120), "sso_secret_hash_mismatch")
|
||||
receipt.update(sso_secret_hash_match=True, status="seeded_and_delivered")
|
||||
else:
|
||||
receipt.update(sso_secret_hash_match=None, status="seeded_pending_sso_delivery")
|
||||
receipt["phase"] = "done"
|
||||
receipt["finished_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||
save()
|
||||
return 0
|
||||
except Exception as exc:
|
||||
receipt["status"] = "refused"
|
||||
receipt["error"] = str(exc) if isinstance(exc, Refused) else "contained_operation_failed"
|
||||
save()
|
||||
return 1
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
250
tests/test_seed_nk_bounded_executor_sender.py
Normal file
250
tests/test_seed_nk_bounded_executor_sender.py
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
"""AUDIT-WP-0018-T04. Seed script for the nk-bounded-executor bearer, on fixtures.
|
||||
|
||||
Uses a fake OpenBao and obviously fake token strings. No real credential, no
|
||||
cluster.
|
||||
"""
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import threading
|
||||
from wsgiref.simple_server import WSGIRequestHandler, make_server
|
||||
|
||||
import pytest
|
||||
|
||||
from audit_core.ingestion import IngestionApplication
|
||||
from audit_core.senders import SenderRegistry
|
||||
from audit_core.sqlite_backend import SQLiteAuditBackend
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / "scripts" / "seed-nk-bounded-executor-sender.py"
|
||||
SPEC = importlib.util.spec_from_file_location("seed_nk_bounded_executor_sender", SCRIPT)
|
||||
SEED = importlib.util.module_from_spec(SPEC)
|
||||
sys.modules[SPEC.name] = SEED
|
||||
SPEC.loader.exec_module(SEED)
|
||||
|
||||
OTHER = [
|
||||
{"name": "tenant-engine", "tokens": ["fake-tenant-engine-not-a-secret"],
|
||||
"sources": ["tenant-engine"], "tenants": ["*"]},
|
||||
{"name": "operator", "tokens": ["fake-operator-not-a-secret"], "sources": ["*"],
|
||||
"tenants": ["*"], "may_write": False, "may_read": True},
|
||||
]
|
||||
FAKE = "f" * 64 # matches the minted shape, obviously not a secret
|
||||
|
||||
|
||||
class FakeBao:
|
||||
"""KV v2 with cas enforcement for the registry and the custody path."""
|
||||
|
||||
def __init__(self, rows=None, custody=None, extra_fields=None):
|
||||
self.kv = {
|
||||
SEED.REGISTRY: [1, dict({"senders.json": json.dumps(rows if rows is not None else OTHER)},
|
||||
**(extra_fields or {}))],
|
||||
}
|
||||
if custody is not None:
|
||||
self.kv[SEED.CUSTODY_DATA] = [1, custody]
|
||||
self.writes = []
|
||||
|
||||
def __call__(self, args, *, payload=None):
|
||||
if args[0] == "read":
|
||||
path = args[-1]
|
||||
if path not in self.kv:
|
||||
raise SEED.NotFound()
|
||||
version, data = self.kv[path]
|
||||
return {"data": {"metadata": {"version": version}, "data": copy.deepcopy(data)}}
|
||||
assert args[0] == "write"
|
||||
path = args[-2]
|
||||
current = self.kv.get(path, [0, None])[0]
|
||||
assert payload["options"]["cas"] == current, "cas must match the version read"
|
||||
self.writes.append(path)
|
||||
self.kv[path] = [current + 1, copy.deepcopy(payload["data"])]
|
||||
return {"data": {"version": current + 1}}
|
||||
|
||||
|
||||
class _Quiet(WSGIRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
def run_seed(fake):
|
||||
receipt = {}
|
||||
token = SEED.seed(SEED.scope_entry(), receipt, lambda: None, call=fake)
|
||||
return token, receipt
|
||||
|
||||
|
||||
def registry_rows(fake):
|
||||
return json.loads(fake.kv[SEED.REGISTRY][1]["senders.json"])
|
||||
|
||||
|
||||
def test_first_run_mints_once_into_custody_and_registry():
|
||||
fake = FakeBao(extra_fields={"unrelated": "kept"})
|
||||
token, receipt = run_seed(fake)
|
||||
assert SEED.TOKEN_SHAPE.fullmatch(token)
|
||||
assert fake.writes == [SEED.CUSTODY_DATA, SEED.REGISTRY]
|
||||
assert fake.kv[SEED.CUSTODY_DATA][1] == {"AUDIT_TOKEN": token, "CUSTODY_REQUEST": "CCR-2026-0063"}
|
||||
rows = registry_rows(fake)
|
||||
assert rows[:2] == OTHER
|
||||
assert rows[2] == SEED.desired(token, SEED.scope_entry())
|
||||
assert fake.kv[SEED.REGISTRY][1]["unrelated"] == "kept"
|
||||
assert receipt["custody"] == "created" and receipt["registry"] == "appended"
|
||||
assert receipt["token_sha256_12"] == SEED.fingerprint(token)
|
||||
assert token not in json.dumps(receipt)
|
||||
|
||||
|
||||
def test_rerun_reuses_and_writes_nothing():
|
||||
fake = FakeBao()
|
||||
token, _ = run_seed(fake)
|
||||
fake.writes.clear()
|
||||
again, receipt = run_seed(fake)
|
||||
assert again == token and fake.writes == []
|
||||
assert receipt["custody"] == "reused" and receipt["registry"] == "already_present"
|
||||
|
||||
|
||||
def test_resume_after_custody_written_but_registry_not():
|
||||
fake = FakeBao(custody={"AUDIT_TOKEN": FAKE, "CUSTODY_REQUEST": "CCR-2026-0063"})
|
||||
token, receipt = run_seed(fake)
|
||||
assert token == FAKE and fake.writes == [SEED.REGISTRY]
|
||||
assert receipt["custody"] == "reused" and receipt["registry"] == "appended"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("custody, code", [
|
||||
({"AUDIT_TOKEN": FAKE, "CUSTODY_REQUEST": "CCR-2026-0099"}, "custody_provenance_mismatch"),
|
||||
({"AUDIT_TOKEN": FAKE}, "custody_provenance_mismatch"),
|
||||
({"AUDIT_TOKEN": "short", "CUSTODY_REQUEST": "CCR-2026-0063"}, "custody_token_shape"),
|
||||
])
|
||||
def test_foreign_or_malformed_custody_refuses(custody, code):
|
||||
fake = FakeBao(custody=custody)
|
||||
with pytest.raises(SEED.Refused, match=code):
|
||||
run_seed(fake)
|
||||
assert fake.writes == []
|
||||
|
||||
|
||||
def test_registered_sender_without_custody_refuses():
|
||||
rows = OTHER + [{"name": "nk-bounded-executor", "tokens": [FAKE]}]
|
||||
fake = FakeBao(rows=rows)
|
||||
with pytest.raises(SEED.Refused, match="registry_sender_without_custody"):
|
||||
run_seed(fake)
|
||||
assert fake.writes == []
|
||||
|
||||
|
||||
def test_drifted_registry_entry_refuses():
|
||||
entry = SEED.desired(FAKE, SEED.scope_entry())
|
||||
entry["tenants"] = ["*"]
|
||||
fake = FakeBao(rows=OTHER + [entry],
|
||||
custody={"AUDIT_TOKEN": FAKE, "CUSTODY_REQUEST": "CCR-2026-0063"})
|
||||
with pytest.raises(SEED.Refused, match="registered_sender_drift"):
|
||||
run_seed(fake)
|
||||
assert fake.writes == []
|
||||
|
||||
|
||||
def test_custody_token_already_used_by_another_sender_refuses():
|
||||
rows = OTHER + [{"name": "someone", "tokens": [FAKE]}]
|
||||
fake = FakeBao(rows=rows, custody={"AUDIT_TOKEN": FAKE, "CUSTODY_REQUEST": "CCR-2026-0063"})
|
||||
with pytest.raises(SEED.Refused, match="sender_token_collision"):
|
||||
run_seed(fake)
|
||||
|
||||
|
||||
def test_empty_or_duplicate_registry_refuses():
|
||||
with pytest.raises(SEED.Refused, match="registry_must_preserve_existing_senders"):
|
||||
run_seed(FakeBao(rows=[]))
|
||||
with pytest.raises(SEED.Refused, match="registry_identity_invalid_or_duplicate"):
|
||||
run_seed(FakeBao(rows=OTHER + [OTHER[0]]))
|
||||
|
||||
|
||||
def test_concurrent_registry_removal_is_caught_on_readback():
|
||||
class Lossy(FakeBao):
|
||||
def __call__(self, args, *, payload=None):
|
||||
result = super().__call__(args, payload=payload)
|
||||
if args[0] == "write" and args[-2] == SEED.REGISTRY:
|
||||
rows = registry_rows(self)[1:] # someone else dropped tenant-engine
|
||||
self.kv[SEED.REGISTRY][1]["senders.json"] = json.dumps(rows)
|
||||
return result
|
||||
|
||||
with pytest.raises(SEED.Refused, match="registry_readback_mismatch"):
|
||||
run_seed(Lossy())
|
||||
|
||||
|
||||
# --- contract checks ----------------------------------------------------------
|
||||
|
||||
def approved_ccr():
|
||||
doc, _ = SEED.load_ccr()
|
||||
doc = copy.deepcopy(doc)
|
||||
doc["status"] = "approved"
|
||||
doc["review"]["comments"] = [
|
||||
{"reviewer": owner, "decision": "approved"} for owner in SEED.CCR_APPROVERS
|
||||
]
|
||||
return doc
|
||||
|
||||
|
||||
@pytest.mark.skipif(not SEED.CCR_FILE.exists(), reason="railiance-platform checkout absent")
|
||||
def test_ccr_contract_as_drafted_matches_and_refuses_until_approved():
|
||||
pytest.importorskip("yaml")
|
||||
SEED.check_ccr(approved_ccr())
|
||||
proposed, _ = SEED.load_ccr()
|
||||
with pytest.raises(SEED.Refused, match="approved_ccr_required"):
|
||||
SEED.check_ccr(proposed)
|
||||
partial = approved_ccr()
|
||||
partial["review"]["comments"] = partial["review"]["comments"][:2]
|
||||
with pytest.raises(SEED.Refused, match="named_owner_reviews_required"):
|
||||
SEED.check_ccr(partial)
|
||||
moved = approved_ccr()
|
||||
moved["openbao"]["kv_path"] = "platform/workloads/net-kingdom/other"
|
||||
with pytest.raises(SEED.Refused, match="ccr_custody_contract_mismatch"):
|
||||
SEED.check_ccr(moved)
|
||||
|
||||
|
||||
def test_wrong_confirmation_refuses_before_any_call(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(SEED, "bao", lambda *a, **k: pytest.fail("bao called"))
|
||||
receipt = tmp_path / "r.json"
|
||||
rc = SEED.main(["--kubeconfig", "/nonexistent", "--confirm", "yes", "--receipt", str(receipt)])
|
||||
assert rc == 1
|
||||
doc = json.loads(receipt.read_text())
|
||||
assert doc["error"] == "exact_confirmation_required" and doc["credential_values_emitted"] is False
|
||||
# Receipt is never overwritten: a second run needs a fresh path.
|
||||
with pytest.raises(FileExistsError):
|
||||
SEED.main(["--kubeconfig", "/nonexistent", "--confirm", SEED.CONFIRM, "--receipt", str(receipt)])
|
||||
|
||||
|
||||
def test_wrapper_is_silent_and_pins_the_confirmation():
|
||||
text = (ROOT / "scripts" / "attended-seed-nk-bounded-executor-sender.sh").read_text()
|
||||
assert ">/dev/null 2>&1" in text and SEED.CONFIRM in text
|
||||
|
||||
|
||||
# --- the seeded entry and the live probe, against the real receiver -----------
|
||||
|
||||
def test_seeded_entry_yields_the_declared_identity_with_the_live_overlay():
|
||||
entry = SEED.desired(FAKE, SEED.scope_entry())
|
||||
reg = SenderRegistry.from_env({
|
||||
"AUDIT_CORE_SENDERS": json.dumps(OTHER + [entry]),
|
||||
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(ROOT / "deploy" / "senders-scope.json"),
|
||||
})
|
||||
identity = reg.authenticate(f"Bearer {FAKE}")
|
||||
assert identity.name == "nk-bounded-executor"
|
||||
assert identity.sources == frozenset({"net-kingdom.bounded-executor"})
|
||||
assert identity.tenants == frozenset({"platform"})
|
||||
assert identity.may_write and not identity.may_read
|
||||
assert identity.secret_policy == "reject" and identity.is_load_bearing
|
||||
|
||||
|
||||
def test_probe_expectations_match_the_receiver(tmp_path):
|
||||
"""Runs the probe source itself against the real receiver over HTTP."""
|
||||
entry = SEED.desired(FAKE, SEED.scope_entry())
|
||||
reg = SenderRegistry.from_env({"AUDIT_CORE_SENDERS": json.dumps(OTHER + [entry])})
|
||||
app = IngestionApplication(SQLiteAuditBackend(str(tmp_path / "p.db")), reg)
|
||||
server = make_server("127.0.0.1", 0, app, handler_class=_Quiet)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
source = SEED.RECEIVER_PROBE.replace("127.0.0.1:8080", f"127.0.0.1:{server.server_port}")
|
||||
|
||||
def run(token):
|
||||
proc = subprocess.run([sys.executable, "-c", source], input=token.encode(),
|
||||
capture_output=True, check=True, timeout=30)
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
assert run(FAKE) == SEED.PROBE_EXPECTED
|
||||
# An unknown bearer gets 401 everywhere, so the expected pattern proves registration.
|
||||
assert set(run("fake-unknown").values()) == {401}
|
||||
finally:
|
||||
server.shutdown()
|
||||
|
|
@ -9,7 +9,7 @@ flavor: implementation
|
|||
owner: audit-core
|
||||
topic_slug: audit-evidence-dependencies
|
||||
created: "2026-10-31"
|
||||
updated: "2026-11-03"
|
||||
updated: "2026-11-04"
|
||||
related:
|
||||
- NK-WP-0056
|
||||
- NK-WP-0040
|
||||
|
|
@ -129,6 +129,58 @@ Code: `audit_core/stream_findings.py` (`evaluate_closing`) and
|
|||
completed. The finding stays inert until `nk-bounded-executor` has a seeded
|
||||
token and emits.
|
||||
|
||||
## Prepare the founder-run seed of the sender bearer
|
||||
|
||||
```task
|
||||
id: AUDIT-WP-0018-T04
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
NK-WP-0056-T15 needs a bearer for `nk-bounded-executor`. Audit-core's registry
|
||||
must hold the bearer, and so must railiance-platform's CCR-2026-0063 custody path
|
||||
`platform/workloads/net-kingdom/bounded-executor/audit-sender` (`AUDIT_TOKEN`).
|
||||
ESO projects that path to `sso/nk-bind-executor-audit:audit-token`.
|
||||
|
||||
Done 2026-11-04. The script
|
||||
`scripts/seed-nk-bounded-executor-sender.py` has a silent wrapper,
|
||||
`scripts/attended-seed-nk-bounded-executor-sender.sh`, and runs in one attended
|
||||
process:
|
||||
|
||||
- mint once;
|
||||
- custody written with cas 0;
|
||||
- registry merged with cas;
|
||||
- `audit-core-senders` synced and checked by hash;
|
||||
- receiver restarted;
|
||||
- a non-writing probe of the live scope;
|
||||
- the `sso` copy checked by hash, when its ExternalSecret exists.
|
||||
|
||||
Tests: `tests/test_seed_nk_bounded_executor_sender.py` (16, synthetic). Founder
|
||||
commands: `docs/bounded-executor-receipts.md`, "Founder runbook". A read-only
|
||||
check on 2026-11-04 found the ingress policy not live. The other nine policies
|
||||
in `deploy/networkpolicies.yaml` match live.
|
||||
|
||||
## Founder-run live activation of the sender and ingress
|
||||
|
||||
```task
|
||||
id: AUDIT-WP-0018-T05
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Run the runbook steps in order:
|
||||
|
||||
1. **Ingress apply (NK-WP-0056-T16):** runnable now.
|
||||
2. **Read-only preflight.**
|
||||
3. **Seed and reload (NK-WP-0056-T15):** CCR-2026-0063 must be approved by its
|
||||
three owners. Railiance-platform must also have applied the reader policy,
|
||||
the role and the coding-agent boundary entries.
|
||||
4. **Verify from the receipt.**
|
||||
|
||||
`sso` delivery is complete only when rapp-bounded-executor carries the
|
||||
ClusterSecretStore and the ExternalSecret `nk-bind-executor-audit`, and mounts
|
||||
key `audit-token`. Today it mounts `bounded-executor-tokens:audit-core`.
|
||||
|
||||
## Founder decision 2026-10-31
|
||||
|
||||
Approved by the founder (`proposed` -> `ready`) after the NK-WP-0056-T02 owner answers (hub decision 19afd15e). Founder adopted the `execution.run.started` event plus one receipt per run with an admission-level target/action, `steps[]` detail and labelled `decision_refs`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue