ADR 0082: the registry is reached by name, and the overlay is its security
https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
topic: building it
|
||||||
|
status: accepted
|
||||||
|
date: 2026-09-17
|
||||||
|
deciders: jochen
|
||||||
|
reconstructed: false
|
||||||
|
extends: 0072-two-graphs-and-the-build-chain.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 82. The registry is reached by name, and the overlay is its security
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Delivery ends at a node pulling an image, and for every node but the one that built it, that step
|
||||||
|
has never worked. Three facts conspired, each recorded separately:
|
||||||
|
|
||||||
|
- Artifact references are written under `127.0.0.1:5000` — a deliberate parking (the catalogue
|
||||||
|
says so in the commit that reverted the mesh-reachable name: *"the runtime refused it: 'http:
|
||||||
|
server gave HTTP response to HTTPS client' … the binding expression returns then"*), so a joined
|
||||||
|
node is told to pull from its own loopback
|
||||||
|
([issue 048](../04-ISSUES/048-nothing-makes-a-machine-trust-the-mesh-registry/00-report.md)).
|
||||||
|
- A container runtime treats any non-loopback registry as HTTPS, and the mesh's registry serves
|
||||||
|
plain HTTP; nothing the mesh writes tells any runtime otherwise — the one place that file
|
||||||
|
existed was the lab's, which is why this never failed in a bed (issue 048).
|
||||||
|
- Nothing gives a node an account for the registry
|
||||||
|
([issue 042](../04-ISSUES/042-nothing-gives-a-node-an-account-for-a-registry/00-report.md)) —
|
||||||
|
and nothing has ever said whether one is required.
|
||||||
|
|
||||||
|
The tempting fix is TLS from the mesh's own authority. The machinery even exists — the mesh CA
|
||||||
|
issues leaves for `<node>.internal` names, and a `certificate:` manifest field delivers them. But
|
||||||
|
the registry is reached **over the overlay**, and the overlay is WireGuard: every byte is already
|
||||||
|
encrypted and already authenticated to a peer the mesh admitted. The mesh's own code has carried
|
||||||
|
this position for weeks: *"the registry is reached over the mesh's own private network … a second
|
||||||
|
layer inside it would be certificates to issue and rotate for no property the first does not
|
||||||
|
have."* TLS inside the tunnel would also re-order genesis (a certificate needs the network, the
|
||||||
|
registry precedes it) and put CA handling into three clients (the runtime, the archive fetcher,
|
||||||
|
the builder) — cost with no new property.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**The overlay is the registry's transport security, and the mesh writes the trust it means.**
|
||||||
|
|
||||||
|
1. **References name the registry by its mesh name.** The builder publishes to
|
||||||
|
`<provider>.internal:<port>` (the binding's `at` and `port`) — the parked one-line change
|
||||||
|
lands. Genesis still publishes to loopback on the first machine, before any overlay exists;
|
||||||
|
references minted at genesis stay loopback and are valid where they matter — on that machine.
|
||||||
|
A rebuild re-pins to the mesh name.
|
||||||
|
2. **Every node on the private network is told the mesh's registry speaks plain HTTP.** The
|
||||||
|
controller injects, into every such node's declaration, a merged `/etc/docker/daemon.json`
|
||||||
|
naming `<provider>.internal:<port>` under `insecure-registries`, and a service resource that
|
||||||
|
restarts the runtime when that file changes — the `/etc/hosts` pattern for the content, the
|
||||||
|
nftables pattern for the reload. No module author is involved; being on the network is what
|
||||||
|
grants the trust, because being on the network is what the trust *is*.
|
||||||
|
3. **No accounts (issue 042), recorded as the position it always was.** Reading and pushing
|
||||||
|
require presence on the overlay and nothing else. The boundary is enforced, not assumed: the
|
||||||
|
registry's `listens` is `from: mesh`, the firewall derives from it, and the overlay admits only
|
||||||
|
peers holding mesh-issued keys. An operator's *external* registry credential is an ordinary
|
||||||
|
operator-supplied secret (`secret accept`), owned by whichever module names that registry.
|
||||||
|
Per-node accounts return as a decision, not a patch, if the boundary assumption ever changes —
|
||||||
|
the shape would be the existing provision flow.
|
||||||
|
|
||||||
|
## How this is checked
|
||||||
|
|
||||||
|
- The no-fake lab bed: a joined node pulls a mesh-built image by the registry's `.internal` name
|
||||||
|
over the overlay — an address outside every range the lab's own runtime configuration trusts,
|
||||||
|
so the mesh-written trust is what makes it work or nothing does.
|
||||||
|
- The firewall half is generated from `listens` and visible in the node's ruleset (`from: mesh`).
|
||||||
|
- The plaintext-inside-tunnel position holds exactly as long as the registry is unreachable off
|
||||||
|
the overlay; the `listens` stanza and the derived ruleset are the check.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
A runtime restart when the trust first lands on a node — at joining, before workloads, where it
|
||||||
|
is free. The registry's HTTP is exposed to whatever stands on the overlay, which is the stated
|
||||||
|
boundary; a mesh that wants defence in depth inside its own tunnel reopens this record rather
|
||||||
|
than bolting certificates on quietly. Issues 042 and 048 close on this record.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [issue 042](../04-ISSUES/042-nothing-gives-a-node-an-account-for-a-registry/00-report.md),
|
||||||
|
[issue 048](../04-ISSUES/048-nothing-makes-a-machine-trust-the-mesh-registry/00-report.md).
|
||||||
|
- [ADR 0072](0072-two-graphs-and-the-build-chain.md) — the build chain this completes.
|
||||||
|
- [ADR 0029](0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md) — the overlay as a
|
||||||
|
boundary.
|
||||||
@@ -149,6 +149,7 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0068** — [The lab takes requests, one at a time, and runs each from its own copy](0068-the-lab-takes-requests.md) *(proposed)*
|
- **0068** — [The lab takes requests, one at a time, and runs each from its own copy](0068-the-lab-takes-requests.md) *(proposed)*
|
||||||
- **0069** — [A module is a repository and a path within it](0069-a-module-is-a-repository-and-a-path.md)
|
- **0069** — [A module is a repository and a path within it](0069-a-module-is-a-repository-and-a-path.md)
|
||||||
- **0076** — [The SDK is a published package, and the toolchain resolves it by version](0076-the-sdk-is-a-published-package.md)
|
- **0076** — [The SDK is a published package, and the toolchain resolves it by version](0076-the-sdk-is-a-published-package.md)
|
||||||
|
- **0082** — [The registry is reached by name, and the overlay is its security](0082-the-registry-is-reached-by-name-and-trusted-by-the-overlay.md)
|
||||||
|
|
||||||
### How it is checked
|
### How it is checked
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user