net-kingdom/docs/nk49-phase-b-plan.md
tegwick 095019d134
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0049-T43: record founder answers; owner requests sent
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 628290@bnt-lap001
Assistant-Session: 70de878b-b4e0-4110-a1ec-bbd553a5f483
2026-10-27 19:05:27 +01:00

131 lines
8.5 KiB
Markdown

# NK49 phase B: isolated KeyCape and restored OpenBao on one internal network
NK-WP-0049-T43. Drafted 2026-10-24. **Preparation only. This grants no authority,
starts nothing and is not T18 acceptance.** Every number marked *proposed* is a
suggestion for the owners, not a fact.
## Purpose
T18 needs a live consumer login against restored dependencies. The consumer is
secrets-engine's `secrets-engine-openbao` workload (SECRETS-WP-0015-T04): it obtains
a token from KeyCape, then logs in to OpenBao with the JWT role. Today the two
recovered pieces cannot meet:
| Piece | State | Network today |
| --- | --- | --- |
| Isolated original KeyCape (NK49 T18 attempt) | Verified 2026-10-23, receipt `16d72f7c…1651`, then destroyed | `--network none`; TLS served on its own loopback |
| Restored original OpenBao (RPF-WP-0064-T07) | Verified 2026-10-24, receipt `4e048dee…579d`, then destroyed | `--network none`; listener on `127.0.0.1:8200` only |
OpenBao's `keycape-services` JWT auth validates against `https://kc.coulomb.social/jwks`.
A networkless OpenBao proves configuration but cannot perform the login. Phase B is the
shared network that lets it.
## Proposed topology
```
docker network create --internal (no gateway, no published ports)
┌───────────────────────────────────────────────────────────────────────┐
│ keycape-worker alias kc.coulomb.social :443 TLS ─▶ issuer :8080 │
│ (+ issuer sharing its namespace, as in the T18 attempt) │
│ │
│ restored OpenBao alias bao-restored :8200 (unsealed, same window)│
│ │
│ consumer runner (secrets-engine owned; the only client) │
└───────────────────────────────────────────────────────────────────────┘
no other attachment · egress shown refused before admission
```
## Who owns what
| Step | Owner | Notes |
| --- | --- | --- |
| Create and tear down the internal network | *Proposed:* railiance-platform | Its design stub already says phase B is "a single `docker network create --internal` network". One creator avoids a double teardown. |
| Restore and unseal original OpenBao, keep it up for the window | railiance-platform | Adapter runs phase A, then does **not** tear down when phase B is admitted in the same window (its doc). Needs a listener on the internal address, not loopback only. |
| Start the isolated original KeyCape on that network | NetKingdom (this plan) | Needs a new variant of the T18 machinery; see below. |
| Consumer login: KeyCape exchange, OpenBao JWT login, `openbao:admin` refusal, session revocation | secrets-engine | SECRETS-WP-0015-T04. |
| ACME issuance for `kc.coulomb.social` | railiance-cluster | Existing owner of the T33 reissue. |
| Window, abort, and every signature | founder | One window, several admissions. |
## What NetKingdom must change (verified from current source)
The T18 machinery is pinned: `prepare-isolated-keycape.py` lists `isolated-keycape.py`,
`isolated-keycape-session.py`, `run-isolated-keycape.sh`, `isolated-lldap.py`,
`identity-bundle.py`, `verify-identity.py` and others as the reviewed source closure,
and the T38 review and T40 admission bind their hashes. **They must stay unedited.**
Phase B is therefore a new module beside them, with its own review.
Facts read from the code that force changes:
1. `isolated-keycape-session.py` launches the worker with `--network none` and
`--add-host kc.coulomb.social:127.0.0.1`. Phase B needs the worker on the internal
network with `--network-alias kc.coulomb.social`.
2. `isolated-keycape.py` `inspect_session` refuses any worker whose `NetworkMode` is
not `none`, and checks that dependent containers have `container:<worker>`. A phase B
variant must accept exactly one named internal network and verify, by inspection,
that it is `Internal=true`, that its attached set is exactly the admitted containers,
and that the worker has a subnet route but **no default route**.
3. The probes assert that only `lo` exists and `/proc/net/route` has one line. In phase B
an interface and a subnet route exist by design, so "no external route" must be
proved differently: no default route, `Internal=true`, and refused egress probes
(to a public address and to a name outside the admitted aliases).
4. `verify-keycape-attempt-receipt.py` expects the `none` topology and must have a
phase B counterpart, so the receipt can still be verified independently.
5. The T40 admission window ended 2026-10-24T05:00Z. Phase B needs a **new founder
signature**; nothing signed before carries over.
Proposed new files (names are suggestions): `isolated-keycape-phase-b.py`,
`run-isolated-keycape-phase-b.sh`, `verify-keycape-phase-b-receipt.py`, tests, and a
packet section. Effort is moderate to large and touches the most sensitive machinery
here, so it should get the same review depth as T38, not a quick patch.
## Preconditions and ordering (proposed)
1. Owners each pass their own preflight (RPF-WP-0064 adapter, the secrets-engine
selector with `--admission`, NK49 `prepare`-style validation).
2. One founder window, at most 24 h *(owner admission rules)*. OpenBao's own deadline is
45 minutes from its first custody input, so **OpenBao is started last among the
long-lived pieces** and the consumer run follows immediately.
3. railiance-platform creates the network and shows egress refusal.
4. NK49 starts the isolated KeyCape (age key, TLS key and trust in the founder's
terminal, as in the T18 ceremony).
5. railiance-platform restores and unseals OpenBao onto the same network.
6. secrets-engine runs the consumer login.
7. Teardown in reverse; each owner returns a safe receipt. The network creator removes
the network last and shows it gone.
## Evidence and claims
Returned: NK49 phase B receipt (verified independently), railiance-platform's phase B
receipt, secrets-engine's consumer result. Together they would show, with no secret
values: the original issuer and signing key served over TLS on the internal network;
`openbao:login` accepted; `openbao:admin` refused; wrong secret refused; a session
revoked; egress refused; cleanup of containers, tmpfs and network.
Not claimed: production continuity or redirect, new grants, a live OpenBao read of
secret data (`platform/workloads/keycape/secrets-engine-openbao` stays metadata only),
human login through Authelia, or owner acceptance. T18 also needs its owner acceptance.
## Decisions (answered 2026-10-27; decision `e4db6c9f`) and remaining owner items
Founder answers: 1 railiance-platform creates the network; 2 reissue via `letsencrypt-prod` once railiance-cluster confirms the duplicate count (no self-signed, no retained key); 5 one workload consumer suffices, no Authelia. Item 3 (secret custody) is secrets-engine's to propose and item 4 (ISRG trust) is confirmed in the owner rehearsal. The answers authorize nothing; windows and signatures stay separate.
1. **Network creator.** railiance-platform (proposed) or NetKingdom?
2. **TLS identity.** The T18 attempt reissued `kc.coulomb.social` through
`letsencrypt-prod` and destroyed the key. Phase B needs a certificate again: reissue
(consumes the duplicate-certificate allowance, limit 5 per week; railiance-cluster
should confirm the count) or retain the key from the earlier attempt (a custody
decision). No self-signed substitute is allowed (decision `9f9d0d88`).
3. **Consumer secret custody.** The consumer's client secret
`keycape-secrets-engine-openbao-client` is in the backup's KeyCape secrets. How does
the consumer runner receive it without the agent, argv or files seeing it? This is
secrets-engine's design to propose; NetKingdom must not hand it over implicitly.
4. **Trust in OpenBao.** The restored OpenBao must trust the ISRG Root X1 chain to
fetch `/jwks`. Confirm in the owner's rehearsal rather than assume.
5. **Scope.** Is this one workload consumer enough for T18's "selected current
consumer", or must a human-login consumer also be admitted? This plan assumes the
workload consumer and does not require Authelia in phase B.
## Unsigned admission
`docs/nk49-phase-b-admission.example.json` is a template with placeholders. It is not
signed, names no real hashes for unwritten files and authorizes nothing.