net-kingdom/sso-mfa/k8s/keycape/openbao-negative-node-route.md
tegwick 7ceac4372d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Publish reviewed bounded JWT ready handoff and retained owner history
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 18:31:04 +02:00

217 lines
13 KiB
Markdown

# KEY39 node-local OpenBao route prerequisite
Source-only successor `KEY-WP-0039-T02/attended-lifecycle-v9-ready-input` of frozen
v5. No native preflight, port-forward, login, Secret access or execution has run.
Previous v5 source and every review remain unchanged. The new executor and
health endpoint need fresh exact source-owner acknowledgments and a founder
native decision; previous v5 agreement is historical compatibility only.
The child runs on railiance01 and uses the explicit node k3s kubeconfig.
The environment orientation's port 18200 is a **workstation** address. The
installed `openbao-ui-railiance01` routing metadata is `direction: local`, local
port 18200, node destination `10.43.5.152:8080`. OpsBridge's supported local
forward builds `-L 127.0.0.1:18200:10.43.5.152:8080`; its listener belongs to the
machine invoking Bridge. It does not establish a node-local listener. No current
node DNS evidence establishes that `openbao.openbao.svc` resolves there. Do not
infer node reachability from workstation role observations or Bridge status.
OpsBridge supports named `up`, `down`, `status`, `check`, and local/reverse
configurations. There is no existing named KEY39 node-local reverse route in the
inspected routing metadata. Do not invent `bridge forward`, silently modify the
shared workstation tunnel, repurpose its port, or request a new SSH certificate
for this preparation. A new Bridge-managed route needs its own owner-reviewed
configuration and authority; this source does not create one.
## Proposed founder-held route on the child node
The Platform source publishes Service `openbao/openbao-ui-gateway`, port 8080,
which proxies the OpenBao API. The proposed attended route uses this existing
service and node loopback; it requires no pod DNS or hardcoded ClusterIP:
```bash
# Proposed only: run in a dedicated founder terminal ON railiance01, after exact
# route/session authority and current cluster/service identity are confirmed.
# Lifetime is capped at 25 minutes, including preparation/action/cleanup.
timeout --foreground 1500s kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml --request-timeout=20s -n openbao port-forward --address=127.0.0.1 --pod-running-timeout=20s service/openbao-ui-gateway 18200:8080 >/dev/null 2>&1
```
Keep that exact foreground process alive through issuer restoration, removed
client refusal and Secret absence verification. Stop only the new process with
Ctrl-C when those obligations are verified; timeout also stops it. Do not kill
an arbitrary existing listener, run `bridge down` against shared tunnels, or
restart a shared gateway. Port conflict, unavailable service, route loss or
non-ready OpenBao means stop and retain any pending custody for separately
approved recovery. Recovery requires re-establishing the same admitted node
route under its own founder recovery decision; no automatic tunnel is opened.
The lifetime estimate is a finite preparation ceiling, not an authority grant.
## Public preflight and exact envelope pin
The separate diagnostic command reads only the exact unauthenticated health
endpoint, never Kubernetes, Secrets, a token producer or a credential. Run from
the same founder-owned node execution context as the intended child:
```bash
python3 /OWNER-PINNED-PATH/openbao-negative-session.py route-preflight --bao-addr http://127.0.0.1:18200 --receipt /OWNER-PRIVATE-PATH/key39-node-route.json
```
The receipt is exclusive and mode 0600. It records only booleans and SHA256 of
public `cluster_id`; `probe_location=current_process_only` expressly avoids
claiming that an arbitrary workstation run proves node readiness. It accepts
only HTTP 200 with strictly boolean initialized=true, sealed=false,
standby=false, performance_standby=false when supplied, and a nonempty bounded
cluster identity. The official endpoint defines these health states:
[OpenBao health API](https://openbao.org/api-docs/system/health/).
Then run the existing founder preflight with the explicit same Bao origin:
```bash
python3 /OWNER-PINNED-PATH/openbao-negative-session.py founder-preflight --confirm 'FOUNDER ATTENDED KEY39 PREFLIGHT' --bao-addr http://127.0.0.1:18200 --receipt /OWNER-PRIVATE-PATH/key39-preflight.json
```
This retains all previous runtime/registry/headroom/quiet-window obligations.
Copy its `bao_health_identity_sha256` into the exact reviewed execute/recovery
envelope. The attending founder/Platform owner must verify the route target and
provider identity against their authoritative facts. Public health is not
cryptographic service authentication, role/policy proof or execution admission;
its hash detects accidental provider changes and does not replace that review.
Execute probes this pinned health identity before protected token input/native
object reads, then again after dry-runs immediately before the first temporary
Secret create. Recovery probes it before native object/Secret reads. A missing
pin, changed identity, redirect, unreachable/oversized/malformed response or
inactive provider refuses. The existing no-proxy/no-redirect/byte/total-elapsed
bounds and exception abort obligations apply to this new GET too. No local
helper opens or repairs the route. Loss after the first mutation still invokes
the existing bounded owner cleanup path; unproved cleanup stays pending.
The admitted human/coding-agent private stdin producer and all Platform/Mason/
Secrets Engine native acceptance prerequisites remain unresolved separately.
This preflight never fetches or produces those tokens and closes no owner task.
## V7 actual entrypoint sequencing correction
V6's statement that health preceded private token input was too broad: its
`main` read private stdin before calling `execute`, though health still preceded
all native reads/mutations. Platform identified that exact source/documentation
mismatch. V6 and its reviews remain immutable history.
V7 `main` verifies the exact approval/source envelope, starts the bounded action
window and checks the approved public health identity before reading private
stdin. On route failure, stdin is never read and no native access occurs. After
input arrives, `execute` revalidates source/authority and expiry, preserving the
original pre-input monotonic deadline rather than restarting it; its existing
health checks before native access and immediately before mutation remain.
Recovery never reads private stdin and still checks the same public health pin
before native Secret/config/deployment reads. Public route-preflight likewise
consumes no private input. These are separately tested through the actual `main`
entrypoint, with synthetic data and zero native/network calls.
## V8 bounded private JSON receive correction
V7 health preceded private stdin but `stdin.read()` had no byte or elapsed bound.
That issuer-owner finding remains history; exporter write bounds do not bound a
consumer read. V8 reads binary chunks of at most 4096 bytes, at most 65536 total
bytes, and requires actual EOF before decoding the exact two-token JSON object.
Each token is capped at 16384 UTF-8 bytes. The entire receive has a Linux main-
thread total timer capped at 15 seconds and clipped to the original remaining
wall-clock/monotonic action window; waiting never renews that window. Expiry is
checked before receiving, after EOF, and after decoding. Empty, oversized,
incomplete, invalid, stalled or late input cannot reach native execution.
Receipts contain only fixed failure codes/booleans, never bytes or parser errors.
This child-side read gate still does not establish producer-side readiness.
Before ANY token bytes enter a pipe/SSH transport, a separately reviewed trusted
launcher/exporter must receive an exact-child source/approval/public-health ready
acknowledgment bound to that pipe session and window. The current producer v1
has no such protocol; the producer consumer review requests it. V8 supplies no
handshake, launcher admission or native readiness and must not be piped to the
old exporter as though its health-before-read check prevents early transmission.
## V9 private ready protocol and concrete node wrapper
The old producer v1 refusal remains history: a child checking health before
reading stdin cannot prevent an exporter from buffering or transmitting tokens
earlier. V9 execute therefore requires `--ready-fd`, `--session-nonce` and
`--ready-envelope-sha256` before it will read private input. The ready FD is a
locally inherited writable FIFO >=3 whose inode/device differs from every stdio
endpoint; stdout/stderr aliases, regular files and missing FDs refuse.
After source/approval validation, original-window start and approved public
health, the child emits one <=4096-byte newline JSON marker to that private FD
and closes it. Emission has a total two-second cap clipped to the original
action window. Child stdout and stderr remain quiet. Exact marker fields are:
```json
{
"schema": "key39-private-input-ready/v1",
"session_nonce": "<fresh 64 lowercase hex characters from trusted launcher>",
"proposal_id": "KEY-WP-0039-T02/attended-lifecycle-v9-ready-input",
"target": "sso/keycape",
"executor_sha256": "<exact reviewed child source hash>",
"envelope_sha256": "<exact approved raw envelope byte hash>",
"bao_health_identity_sha256": "<exact owner provider identity pin>",
"valid_until_epoch": 0,
"receive_budget_seconds": 0
}
```
Zeros/placeholders illustrate keys only and are never a valid marker. Epoch is
the exact finite nonboolean approved envelope expiry, still future; receive
budget is finite positive <=15 and clipped at emission to the original action
window. The exporter clips its already-running deadline to the advertised
budget and *current* approval remainder. It must not reset a deadline or demand
that a budget measured at emission equal the smaller remainder after transport.
`openbao-negative-node-wrapper.py` is the concrete node-local relay. The trusted
launcher supplies nonsecret exact child/envelope paths and SHA256 pins, a new
nonce and exclusive receipt destination. Wrapper envelope must be owner UID,
mode0600, single-link regular file, stable bounded bytes; its child/wrapper
source paths must be included in the exact envelope's native source hash map.
This is reconstruction of the caller's review context, not authenticated human
approval: assertions in JSON cannot create a grant. The founder must separately
admit the exact source/profile, target and SSH peer under the applicable lane.
The wrapper creates an **ordinary local pipe on the node**, starts the pinned
Python child with that write endpoint passed locally, and reads its bounded
marker plus EOF. The child's private stdin is inherited directly from the SSH
input stream: wrapper never reads, copies, logs or writes token bytes. No extra
file descriptor is forwarded by SSH or OpsBridge. Within one five-second total
preparation deadline, the wrapper validates all marker keys/nonce/pins/window and
forwards exactly one value-free marker line on its own stdout control pipe.
The admitted workstation launcher must own/capture that SSH stdout separately
from its private stdin data pipe, never display/tee/log it, validate the exact
marker and then allow the KeyCape adapter to load/consume caller files or write
ANY token bytes. The control stream stays open while the child runs; the
exporter stops marker reception at newline, not SSH EOF. Missing, partial,
wrong-nonce, stale, oversized or pin-mismatched markers mean zero token file
reads/writes and retained caller files. The old v1 adapter is incompatible.
This wrapper emits control stdout and therefore **cannot run inside the
warden attended command lane that rejects all child output**. It requires a
separately admitted founder-owned direct SSH profile with no PTY, captured
stdin/stdout/stderr, pinned peer/host identity, exact wrapper/source/receipt paths
and no agent credential access. No such runtime admission is supplied here.
A bare marker, caller boolean or downloaded ready file cannot authenticate a
peer, source execution or founder decision. A trusted reviewed launcher owning
that transport and verifying the context is a prerequisite.
The wrapper waits only through the original approval remainder plus 300 seconds
cleanup. Any child stdout/stderr is captured with bounded reads and refused,
never forwarded. Failure terminates the exact launched child with bounded five-
second TERM then two-second kill/wait fallback; capture closes have 20 ms caps.
No retry or replay occurs. Interruption, partial private delivery, emitted ready
followed by child death, unconfirmed revoke or interrupted native cleanup remains
unknown/pending owner reconciliation using the separately approved recovery
path. Wrapper exit zero is not owner/native acceptance; the child receipt still
requires actual owner acceptance. No persistent file/copy on the node is added
for token material, and Unix/Python memory cleanup is not secure zeroization.
Human browser issuance and admitted coding-agent custody remain distinct owner
choices. Any new files must be produced by the pinned verified owner CLI under
continuous exclusive tmpfs custody after the separately admitted route preflight.
They must be prepared before launching this short-lived private receive window;
the child cannot wait for a five-minute browser login after emitting ready.
Fresh registered-client/MFA/custody evidence, exact independent KeyCape/Platform
reviews and the founder's complete route/transport/private-file/native decision
remain required. No native handshake, SSH, credential or route was exercised.