Files
hq/02-DECISIONS/0147-a-module-anchors-the-meshs-authority.md
T
jschoubben 6c14d313b8 ADR 0147: a module anchors the mesh's authority on a machine, and takes it away again
Issue 129: every internal HTTPS name fails verification on every machine,
because nothing has ever written the mesh's root into a trust store. The
report proposed the controller inject it the way the private network writes
the registry's trust; this record rejects that — reachability and trust are
not the same fact, and where anchors live is the host's difference, not the
controller's. A module requiring internal-acme-ca does the whole of it, and
being unassigned undoes it.
2026-09-29 15:02:37 +02:00

8.1 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-09-29 jochen false 02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md

147. A module anchors the mesh's authority on a machine, and takes it away again

Context

The mesh runs its own certificate authority and every internal name is served with a certificate from it. No machine trusts it. On an enrolled, adopted workstation — on the private network, resolving through the mesh's resolver — every internal HTTPS name fails verification with unable to get local issuer certificate (issue 129). The certificates are genuine; nothing on the machine has ever been told what issued them.

The authority's only consumer today is a proxy, which fetches the root into a directory of its own and hands it to one program (ADR 0098). That is enough for the proxy and for nothing else: a browser, git over HTTPS, curl, a package manager and every module that calls another module by an internal name read the machine's trust store, which holds the predecessor's authority and a developer tool's local root, and nothing of the mesh's.

The predecessor wrote its root into every machine it set up. Removing it was deliberate — an honest failure beats a name that verifies for the wrong reason — and it leaves the mesh with no answer at all until this one lands. It is also what keeps the predecessor alive on the machines that still speak TLS to a mesh name.

What makes this a decision rather than a patch is where the knowledge goes. Two mechanisms in the mesh already write things onto a machine because it is on the private network: /etc/hosts and the registry's plaintext trust (ADR 0082). Following that precedent, the controller would inject an anchor into every such machine's declaration, and issue 129 proposed exactly that. It would work. It would also put where this operating system keeps trust anchors and which command refreshes its bundles into the control plane, for a fact the control plane does not have (the root does not exist until the authority has run) and a machine that may have no reason to verify a mesh name at all.

Considered Options

  1. The controller injects the anchor into every machine on the private network, the /etc/hosts and insecure-registry shape. Rejected: being on the network is what makes the registry reachable, and that is why network presence is the right trigger there — the trust and the reachability are the same fact. Trusting an authority is not the same fact as being able to reach it, and the anchor's path and the bundle refresh are a property of the machine's operating system, which is the host's half of the mesh, not the controller's.
  2. A new host primitive — a trust-anchor resource type. Rejected for now, not on principle. The host's vocabulary should grow when a shape cannot be said with what exists, and this one can: a file and a service already express it, as the packet filter proves (ADR 0140, whose module writes a unit file and a service and nothing else). The primitive becomes right the moment a second operating system is in play, because the anchor directory and the refresh command are exactly the difference internal/system exists to hold. Until then it would be a vocabulary word with one speaker.
  3. A module that requires the authority, fetches its root, installs it as an anchor and refreshes the machine's bundles — and removes both when it is no longer assigned. Adopted.

Decision

A machine trusts the mesh's authority because a module put its root there, and stops trusting it when that module is taken away.

  1. The module requires internal-acme-ca and reads the provider's bound address and the path it serves its root at. It requires nothing else and provides nothing: it is a consumer of the authority like any other.
  2. It fetches the root over the mesh's own network, without prior trust, because there is no prior trust to have — this is the module that establishes it — and the network is what authenticates the fetch (ADR 0098, the same reasoning that lets the proxy fetch it). What it accepts is checked: a body that is not a certificate fails, and the failure is the module's, not a later handshake's.
  3. It installs the root where this machine's TLS clients look, and refreshes the extracted bundles — the command that does the refresh is an ordinary part of the unit that places the anchor, not a new thing the mesh can be asked to do.
  4. Removal is symmetric and is the same unit's business. Undeclared, the host stops the unit; stopping it removes the anchor and refreshes the bundles again. A machine that leaves the mesh stops trusting the mesh, without anybody remembering to go and look.
  5. It is an ordinary assignment. No machine is given it automatically. A machine that verifies a mesh name is assigned it, and a machine that does not is not — which is the same statement the mesh already makes about every other module, and is why this is not the controller's business.

One operating system, said out loud. The anchor directory and the refresh command in the module today are Arch's. On a machine that is not Arch the unit fails, visibly, rather than writing a file nothing reads. That is the accurate failure, and it is the signal that option 2 above has become right.

How this is checked

  • The verification that could not succeed before. On a machine holding the module, a plain client fetches an internal HTTPS name with no -k and no bundle argument and verifies. On a machine without it, the same fetch fails with unable to get local issuer certificate. Both halves, because only the pair distinguishes "the anchor works" from "something else already trusted it".
  • The removal half, in the same bed: unassign the module, refetch, and the failure returns. Checking only the arrival is how a trust store fills up with authorities nobody can account for.
  • What is deliberately not checked here: that the authority issues, that a name resolves, that the proxy serves. Those have their own beds, and this module's bed passing for those reasons is the failure mode this record is most exposed to — which is why the negative half is not optional.

Consequences

The predecessor's authority can be retired from a machine once this module is assigned to it, which is the first time that has been true. git over HTTPS to the mesh's forge starts working, so the ssh-only clone URL stops being a rule. A module on any machine can call another module's internal name and verify it.

What got harder: one more module to assign to a machine that needs it, and the machine's trust store now changes when an assignment changes — which is the point, and is also a thing an operator can be surprised by. The fetch without prior trust is the same exposure ADR 0098 accepted, now on every machine that holds the module rather than only where a proxy runs: anything that can stand in the middle of the mesh's own network at the moment of the fetch can be believed. The mesh already treats that network as the thing it authenticates.

References

  • issue 129 — the symptom and the evidence.
  • ADR 0098 — a fact made at first start is fetched from its provider; this extends it from one program to the machine.
  • ADR 0082 — the precedent this deliberately does not follow, and why it is right where it is.
  • ADR 0005 — the host is where one operating system's difference lives.
  • 03-DESIGN/01-to-be/08-connectivity.md.