Commit Graph
2 Commits
Author SHA1 Message Date
jschoubben 2c322cb2fb mesh-control: the control plane could not read its own connections
Its image is FROM scratch and runs as 65534. The host writes a sealed own-secret 0600,
owned by root, which is right — but the module then bind-mounted those three files into
the container and told the process to open them. It cannot:

  $ docker run --rm -v <0600 root file>:/run/secrets/inventory:ro \
      -e MESH_STORE_INVENTORY_FILE=/run/secrets/inventory mesh-control:development status
  MESH_STORE_INVENTORY_FILE names /run/secrets/inventory ... and it cannot be read:
  open /run/secrets/inventory: permission denied

Measured on a workstation, not reasoned about. Every other module in this catalogue gets
away with the same mount because its runtime container runs as root; this one does not,
and genesis (novox/hq ADR 0067) would have stopped at step 9 with a control-plane module
that starts and cannot open a context.

The connections go through the env file this module already has instead. That file is
mode 0600 and is read by the container runtime's client, which is root — the same reason
the broker's URL has always reached the process this way. It also sidesteps the inode
that a file bind mount pins (290be37, step-ca): --env-file is read afresh at create, and
restart-on names it.

The own-secrets stay exactly as they were, because the installer delivers the substrate's
real connection strings into them with `secret accept` before the first push — the mesh
did not make those credentials and cannot invent them.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-11 11:38:28 +02:00
jschoubben 71f8012c5b The control plane as an ordinary module
novox/hq ADR 0067 pivots genesis through a temporary control plane and then
reinstalls the control plane as an ordinary module pinned to a digest the mesh's
own registry assigned. That record notes the one thing missing: a control-plane
module manifest, which did not exist.

It could not be written honestly before now. The control plane read its store
connection from MESH_STORE_<CONTEXT>, that connection string carries a password,
and a manifest can put a sealed value into a file's `content` but has nothing
that substitutes into a container's `env`. So the manifest could carry the
password in the clear, or omit the setting. mesh-control now also accepts
MESH_STORE_<CONTEXT>_FILE, which is how every other module here is given secret
material, and the manifest follows.

What the substrate bundle gives the control-plane container today, and where
each part has gone:

  MESH_STORE_INVENTORY   own-secret `inventory`, mounted, named by _FILE
  MESH_STORE_IDENTITY    own-secret `identity`,  mounted, named by _FILE
  MESH_STORE_LICENCES    own-secret `licences`,  mounted, named by _FILE
  MESH_BROKER_AMQP       own-secret `broker`, through an env-file hole
  MESH_BROKER_MANAGEMENT own-secret `broker-management`, likewise
  MESH_BROKER_ADDRESS    ${machine:at}:5671 in that same env-file
  MESH_BROKER_CERTIFICATE  plain env; the path is not a secret
  network host, args ["serve"], the broker's TLS volume  unchanged

The two broker URLs go through an env-file rather than a file of their own
because mesh-control has no MESH_BROKER_AMQP_FILE. That is the same fault one
layer over, and the same remedy would fix it; it is out of this change's scope
and is written down rather than papered over.

None of these values is in the manifest. Each is an own-secret the operator
supplies with `secret accept` — the mesh cannot invent a connection string — and
the container restarts when any of them changes.

The module claims `the-control-plane` at mesh scope, which the bundle has no way
to say: two control planes writing one inventory is a fault worth refusing at
assignment. It carries no `listens`, because `serve` dials the broker and binds
nothing. The image is the catalogue's placeholder digest for a mesh-built image,
which the installer replaces with what the registry assigned.

Checked with the real parser: all 67 manifests through catalogue.ParseManifest
and every module's CheckIdentity against all four node names — 0 problems — and
this manifest rendered through Resolution.Declaration, so the ${secret:…} names,
${machine:at}, the restart-on ids and the image pin are exercised rather than
merely parsed. Slug `control`: mesh_shanks_control is 19 of the 20 an S3 access
key keeps.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 23:43:07 +02:00