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
131 lines
8.5 KiB
Markdown
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.
|