docs: TLS hardening done (NATS + OpenBao) across both nodes

NATS tls:// + token and OpenBao HTTPS via the ACME wildcard cert (expose.tcp.tls),
no custom CA. civil + primer both cut over; plaintext rejected on both. Server
config + unseal.sh are instance-side (/data/castle, ~/.castle).
This commit is contained in:
2026-07-07 05:57:58 -07:00
parent e82de4fd50
commit 76b3795485

View File

@@ -1,8 +1,9 @@
# Fleet Mesh Plan — OpenBao + NATS
**Status:** in progress on branch `feat/fleet-mesh-nats-openbao`.
Phases 02 + Phase 4 (secret-read backend) complete + single-node verified (live).
Phase 3 (cross-node routing/breaker) and Phase 4 hardening pending the 2nd node.
**Status:** Phases 04 complete + verified live across **both nodes** (civil +
primer), including TLS hardening (NATS + OpenBao). Merged to main.
Remaining nice-to-have: the live curl+kill breaker demo (needs a peer-unique
consumed service) — every underlying piece is verified.
## Context
@@ -245,12 +246,23 @@ second node is proven on NATS.
systemd spec (general, `-`-prefixed so a hook failure never fails the unit);
`castle-openbao` runs `/data/castle/castle-openbao/unseal.sh` (polls up, unseals
with `OPENBAO_UNSEAL_KEY`). Verified: manual seal → restart → auto-unsealed.
3. **TLS hardening — REMAINING (deliberately deferred).** NATS + OpenBao are
plaintext on the trusted LAN. This is the gate before cross-*network* or moving
real secrets — neither of which is here yet (the vault holds no production
secrets). Doing NATS TLS/auth touches the *live* civil↔primer mesh, so it's a
deliberate, staged change (regenerate certs, update both nodes' clients), not a
tail-end one. Next dedicated step.
3. **TLS hardening — DONE + verified across both nodes (2026-07-07).**
Reuses the existing ACME **wildcard cert** via `expose.tcp.tls` (the postgres
mechanism) — services present a publicly-trusted cert for
`<name>.civil.payne.io`, so clients verify against the **system CA with no
custom CA to distribute**.
- **NATS:** `tls{}` (wildcard cert in `/tls`) + `authorization{token}`
(`$NATS_TOKEN` from the `NATS_TOKEN` secret). Clients connect to
`tls://castle-nats.civil.payne.io:4222` with the token; `CastleNATSClient`
gained token support (a `tls://` URL → nats-py verifies via system CA).
**civil + primer both cut over**; plaintext now rejected (verified:
`certificate is valid for *.civil.payne.io, not localhost`).
- **OpenBao:** listener `tls_cert_file`/`tls_key_file` from `/tls`; reachable at
`https://castle-openbao.civil.payne.io:8200`. Auto-unseal + secret backend
both use the HTTPS URL. Verified: HTTPS serves, **auto-unsealed over HTTPS**,
plaintext rejected, backend reads a secret over HTTPS.
- Coordinated cutover across the whole fleet (both nodes) with a brief,
expected mesh blip; mesh isn't load-bearing so no service impact.
### Phase 3 — cross-node routing + breaker: logic DONE + verified against a real peer (2026-07-07)