swarm-bao: reach the store through a TLS passthrough, not a vhost
An agent container cannot dial the store's loopback listener: the bridge to-loopback DROP rule is there precisely to stop that, and the store authenticates every reader by client certificate, so the usual answer — a gateway vhost — is the one shape that cannot work. A terminating proxy strips the certificate and bao sees nginx as the client for every hive. nginx's stream module does not terminate. `ssl_preread` reads the SNI off the ClientHello and splices the rest of the connection through byte for byte, so bao completes the handshake itself and authenticates the client it actually has. That is the no-vhost rule kept, not bent. The listener binds the bridge IP rather than every address, because bao already holds `127.0.0.1:<port>` in the same netns and a wildcard bind there is EADDRINUSE — nginx would fail to start, taking the gateway with it. Nothing moves as a result: the name already resolves two ways, so a host-side reader still goes straight to loopback and an agent goes through the passthrough, both on one `BAO_ADDR`. Renders only inside the store's own `deploy.bao.enable` region; a host that runs no store grows no listener and opens no port. Per-agent certificates and per-agent policy are separate work. Refs #4386
This commit is contained in:
parent
cab6910cbb
commit
ef2dfbfb31
3 changed files with 145 additions and 2 deletions
|
|
@ -238,6 +238,33 @@ A hive that reads from a store on **another** machine names the reader's half
|
|||
It's the one credential that can't come out of the store, being what opens it;
|
||||
everything else a hive needs does.
|
||||
|
||||
## How a reader reaches the store
|
||||
|
||||
Every reader dials the same URL — `https://bao.<swarm domain>:<port>` — and on
|
||||
the host that runs the store that name resolves twice. `/etc/hosts` answers a
|
||||
host-side unit with loopback, where openbao binds; the hive's dnsmasq answers a
|
||||
container with the bridge IP, where nginx holds the same port. One `BAO_ADDR`,
|
||||
and the network namespace that asked decides which half of it answers.
|
||||
|
||||
The container's half goes through nginx and **still doesn't terminate TLS**. It
|
||||
is an nginx `stream` server with `ssl_preread on`: it reads the SNI off the
|
||||
ClientHello and splices the rest of the connection through byte for byte, so
|
||||
openbao completes the handshake itself. A vhost would decrypt here instead, and
|
||||
the client certificate — the store's whole authentication — would stop at nginx,
|
||||
leaving openbao seeing one client for every hive in the swarm. That's why the
|
||||
store has no vhost and gets a passthrough: not an exception to the no-vhost
|
||||
rule, the one shape that keeps it.
|
||||
|
||||
The stream listener binds the bridge IP rather than every address, because
|
||||
openbao already holds loopback on that port in the same network namespace and a
|
||||
wildcard bind would fail with `EADDRINUSE` — taking nginx, and with it every
|
||||
hive domain behind the gateway, down. `network.exposeHostPorts` opens the port
|
||||
on the bridge firewall and nowhere else.
|
||||
|
||||
Reaching the port grants nothing by itself: openbao answers nothing without a
|
||||
client certificate signed by `deploy.bao.clientCaFile`. The passthrough carries
|
||||
whichever certificate the reader presents, unchanged.
|
||||
|
||||
## The constraint that decides where the root lives
|
||||
|
||||
A hive CA carries `nameConstraints=permitted;DNS:<hive domain>`, and **a swarm
|
||||
|
|
|
|||
Loading…
Reference in a new issue