ADR 0191: the mesh resolves only its own names; a public name resolves publicly #320

Merged
jschoubben merged 1 commits from decision/0191-the-mesh-resolves-only-its-own-domain into main 2026-10-03 13:16:13 +00:00
5 changed files with 160 additions and 13 deletions
Showing only changes of commit e5e6e56ecf - Show all commits
@@ -9,6 +9,13 @@ extends: 0007-connectivity.md
# 66. Public routing is name-agnostic, its names are resolved inside the mesh, and an internal authority can certify them # 66. Public routing is name-agnostic, its names are resolved inside the mesh, and an internal authority can certify them
> **Narrowed, not replaced — 2026-10-03.** One clause of the decision below no longer holds: *publishing
> a granted name into internal resolution, mesh-wide*. A public name now resolves publicly, and only
> names under the mesh's own suffix get a private answer — [ADR 0191](0191-the-meshs-resolver-holds-only-the-meshs-own-names.md).
> Inside the mesh a route is reached and certified by its internal name
> ([ADR 0151](0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md)). The label, the
> node's public domain and their composition stand as decided here.
## Context ## Context
**[ADR 0007](0007-connectivity.md) and [connectivity §3](../03-DESIGN/01-to-be/08-connectivity.md) **[ADR 0007](0007-connectivity.md) and [connectivity §3](../03-DESIGN/01-to-be/08-connectivity.md)
@@ -9,6 +9,11 @@ extends: 02-DECISIONS/0066-public-routing-is-name-agnostic.md
# 151. A route's internal name is composed under the node that serves it # 151. A route's internal name is composed under the node that serves it
> **Narrowed, not replaced — 2026-10-03.** *"The roster publishes it as itself, once, at the serving
> node's address"* no longer holds: a public name is never given a private answer, and resolves publicly
> ([ADR 0191](0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)). The internal name this record
> composes is what that rests on, and stands.
## Context ## Context
A module that requires a route is given two names from one label: a public one, `<label>.<public A module that requires a route is given two names from one label: a public one, `<label>.<public
@@ -0,0 +1,109 @@
---
topic: the tiers
status: accepted
date: 2026-10-03
deciders: jochen
reconstructed: false
supersedes-in-part:
- 0066-public-routing-is-name-agnostic.md
- 0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md
---
# 191. The mesh's resolver holds only the mesh's own names; a public name resolves publicly
## Context
**[ADR 0066](0066-public-routing-is-name-agnostic.md) published every routed name into internal
resolution, mesh-wide, at the address of the node that serves it.** The reason was an internal
certificate authority in the lab: it validates by connecting to the name it certifies, and a routed
public name that nothing inside the mesh resolved could not be certified.
[ADR 0151](0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md) kept it:
*the roster publishes it as itself, once, at the serving node's address.*
**So every machine's resolver answered public names with private-network addresses.** On a
production mesh on 2026-10-03, each machine's hosts region carried 47 lines of the form
`<private address> <label>.<public domain>` — every public name of the control-node at its tunnel
address, every public name of the home server at its own. For the machines themselves this is merely
a detour: their traffic to a public name goes through the tunnel instead of the internet.
**For anything that is not a member it is an outage.** The home server's resolver also answers its
LAN — a listen address added as a setting on 2026-10-02. A phone on that LAN asked for the mail
server's public name, was given the control-node's tunnel address, and could not connect:
*couldn't connect to host, port: 10.10.0.1:143*. Every public name of the mesh failed the same way for
every non-member on that LAN — a phone, a television, a guest — while every check the mesh runs
reported success, because every check runs from a member.
**And the reason for publishing them is gone.** ADR 0151 gave every route an internal name,
`<label>.<serving node>.internal`, under the node's own name. It resolves inside the mesh without any
entry of its own, the proxy serves it, and the internal authority certifies it — the proxy has two
authorities since 2026-09-25: a public one for public names, the internal one for internal names.
Measured the same day: `drive.<control-node>.internal` resolves to the control-node's tunnel address
and answers 200 with a certificate that verifies against the internal root. Nothing the mesh runs
needs a public name to resolve to a private address. The one consumer that did — an internal
authority validating a public name — is the case the second authority removed.
The predecessor's resolver held exactly this and no more: an address per machine under `.internal`,
and everything else forwarded to public resolvers.
## Considered Options
**1. Keep publishing public names; stop the resolver answering the LAN.** Fixes the phone and
nothing else. The mesh would still hold a second, private answer for names the public DNS already
answers — two answers for one name, which disagree by design and are correct in different places.
And it forbids a reasonable setup: a home server's resolver serving its own LAN.
**2. Answer per source: private addresses to members, public ones to everyone else.** Split-horizon
by client. It is what a resolver serving two audiences would need *if* the private answer were worth
giving. It is not — option 3 shows nothing needs it — and it makes a name's address depend on who
asks, which is the hardest kind of fault to see from a member.
**3. The mesh's resolver holds only the mesh's own domain.** Names under the mesh suffix — machines,
and routes' internal names under them — resolve to private addresses. Every other name, including
every public name the mesh serves, is forwarded and resolves publicly. Chosen.
## Decision
**The mesh publishes into internal resolution only names under its own suffix.** A machine's name,
and through it every `<label>.<node>.internal`, resolve to that machine's private address. **A public
name is never given a private answer by the mesh**: it resolves through public DNS to the public
address, from members and non-members alike.
This replaces ADR 0066's clause *"when the proxy is granted a name, the mesh publishes that name →
the node that serves it into internal resolution, mesh-wide"*, and ADR 0151's *"the roster publishes
it as itself, once, at the serving node's address."* Everything else in both stands: the label, the
node's public domain, the composition, and the internal name under the serving node.
**Inside the mesh, a route is reached by its internal name.** A container or a validator that must
reach a routed service inside the mesh uses `<label>.<node>.internal`; the internal authority
certifies that name, and a public authority certifies the public one. A mesh with no public
reachability — the lab — certifies its internal names and has no public names to resolve.
## Consequences
- **A resolver serving a LAN is safe.** What it adds to public resolution is the mesh's own domain,
which no public resolver answers.
- **A member reaches a public name over the internet, as anyone does.** A route the proxy restricts
to the private network is reached by its internal name, never by its public one — a public name
is, by this decision, public.
- **The internal authority certifies internal names only.** It was the only consumer of a public
name's private answer; the proxy's second authority already took that role away from it.
- **Public names leave every machine's hosts region** on the first push after the change.
Containers do not move with it: the roster is not part of a container's identity
([ADR 0148](0148-the-meshs-names-are-resolved-not-copied-into-containers.md)).
**How each is checked:**
- **The roster:** the controller's catalogue tests assert that every name the roster carries ends in
the mesh suffix — a routed public name in it fails the build.
- **On a machine:** asking the machine's resolver for a public name the mesh serves returns the
public address, and asking it for that route's internal name returns the private one. Asked from a
non-member on a LAN the resolver answers, the first must hold as well.
## References
- [ADR 0066 — public routing is name-agnostic](0066-public-routing-is-name-agnostic.md), whose
propagation clause this replaces.
- [ADR 0151 — a route's internal name is composed under the node that serves it](0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md),
which made the private answer unnecessary.
- [Connectivity design §2 and §5](../03-DESIGN/01-to-be/08-connectivity.md), amended alongside this
record.
+1
View File
@@ -222,6 +222,7 @@ python3 00-META/checks/index.py fail if stale
- **0126** — [A module declares its own seats; the mesh reserves its own](0126-a-module-declares-its-own-seats.md) - **0126** — [A module declares its own seats; the mesh reserves its own](0126-a-module-declares-its-own-seats.md)
- **0148** — [The mesh's names are resolved, not copied into every container](0148-the-meshs-names-are-resolved-not-copied-into-containers.md) - **0148** — [The mesh's names are resolved, not copied into every container](0148-the-meshs-names-are-resolved-not-copied-into-containers.md)
- **0151** — [A route's internal name is composed under the node that serves it](0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md) - **0151** — [A route's internal name is composed under the node that serves it](0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md)
- **0191** — [The mesh's resolver holds only the mesh's own names; a public name resolves publicly](0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)
### What runs on them, and how it gets there ### What runs on them, and how it gets there
+38 -13
View File
@@ -5,10 +5,12 @@ code:
- mesh-controller internal/catalogue/filtering.go - mesh-controller internal/catalogue/filtering.go
- mesh-controller examples/route-proxy - mesh-controller examples/route-proxy
- mesh-controller internal/identity/authority.go - mesh-controller internal/identity/authority.go
- mesh-controller cmd/mesh-controller/plan.go (the names the roster publishes)
- mesh-host internal/identity/serving.go - mesh-host internal/identity/serving.go
- mesh-host internal/apply (the service that reflects a rule set) - mesh-host internal/apply (the service that reflects a rule set)
updated: 2026-10-02 updated: 2026-10-03
decisions: decisions:
- 02-DECISIONS/0191-the-meshs-resolver-holds-only-the-meshs-own-names.md
- 02-DECISIONS/0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md - 02-DECISIONS/0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md
- 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md - 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
- 02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md - 02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md
@@ -421,7 +423,22 @@ that module and nothing else.
the argument for the table in ADR 0009 being a table: the pattern is only obvious once seen, and the argument for the table in ADR 0009 being a table: the pattern is only obvious once seen, and
the cost of not seeing it is inventing a mechanism that already exists. the cost of not seeing it is inventing a mechanism that already exists.
### And the public names a proxy serves must resolve in the mesh too ### The mesh resolves only its own names; a public name resolves publicly
**The mesh's resolver holds names under the mesh suffix and nothing else** — every machine, and through
it every route's internal name `<label>.<node>.internal`
([ADR 0151](../../02-DECISIONS/0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md)).
**A public name the mesh serves is never given a private answer**: it is forwarded and resolves to the
public address, from a member and from anything else the resolver answers — a resolver may serve a
machine's LAN, and a phone on that LAN must get the address it can reach
([ADR 0191](../../02-DECISIONS/0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)). Inside the
mesh, a routed service is reached, and certified by the internal authority, under its internal name.
*Checked by the controller's catalogue tests — every name the roster carries ends in the mesh suffix —
and on a machine by asking its resolver for a public name the mesh serves: the answer is the public
address.*
*What follows is how the mesh got here, kept because the reasoning it rejects is the expensive half to
rediscover.*
*2026-09-09, found by an internal certificate authority that could not issue.* The mesh writes every *2026-09-09, found by an internal certificate authority that could not issue.* The mesh writes every
`<node>.internal` name into every declared container and treats the public names a proxy serves as a `<node>.internal` name into every declared container and treats the public names a proxy serves as a
@@ -444,6 +461,13 @@ would go stale the day one changes. The mesh propagates the names it was told to
knows nothing about what they mean knows nothing about what they mean
([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)). ([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)).
*2026-10-03, withdrawn.* Publishing public names with private answers turned every resolver that also
serves a LAN into an outage for that LAN's non-members — a phone was handed the control-node's tunnel
address for the mail server — while every check, run from a member, passed. Its reason had gone: routes
have internal names since ADR 0151, and the proxy certifies public names from a public authority and
internal names from the internal one. Superseded by the rule at the head of this section
([ADR 0191](../../02-DECISIONS/0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)).
## 3 — Exposure ## 3 — Exposure
Settled by [ADR 0007](../../02-DECISIONS/0007-connectivity.md); summarised here because Settled by [ADR 0007](../../02-DECISIONS/0007-connectivity.md); summarised here because
@@ -871,13 +895,15 @@ is unchanged. **The same code path certifies against an internal authority as ag
only the issuer differs.** That is what makes trusted certificates possible for a mesh whose names only the issuer differs.** That is what makes trusted certificates possible for a mesh whose names
the public internet cannot resolve. the public internet cannot resolve.
**And it does not work until the routed name resolves inside the mesh** — the §2 finding above, **The internal authority certifies internal names; a public one certifies public names.** The
arriving here because this is what needed it. The authority's challenge reaches the routed name only authority's challenge reaches the name it certifies, so each certifies what it can resolve: the
once that name is in internal resolution; a public authority is handed that dependency by public internal authority a route's `<label>.<node>.internal`, which the mesh resolves, and a public authority
DNS, and an internal one has to be handed it by the mesh. *Checked by a handshake to a routed name the public name, which public DNS resolves. A proxy holds both, and a public name is never certified
that verifies against the internal root and nothing else — which cannot succeed unless the issuer by the internal authority. *Checked by a handshake to a route's internal name that verifies against
first reached the name to certify it* the internal root and nothing else, and one to its public name that verifies against the public
([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)). roots* ([ADR 0191](../../02-DECISIONS/0191-the-meshs-resolver-holds-only-the-meshs-own-names.md)).
Until 2026-10-03 this paragraph had the internal authority certify public names, which needed them
resolved inside the mesh ([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)).
## 6 — One statement behind exposure, filtering and certificates ## 6 — One statement behind exposure, filtering and certificates
@@ -983,10 +1009,9 @@ The list is worth having in one place, because it is most of the argument:
operator's to move between meshes, but the manifest layer still stores it as a literal — so today operator's to move between meshes, but the manifest layer still stores it as a literal — so today
the composition is a per-node override rather than the design. The interpolation that would let a the composition is a per-node override rather than the design. The interpolation that would let a
module carry a label and a node carry the domain, and the mesh join them, does not yet exist. module carry a label and a node carry the domain, and the mesh join them, does not yet exist.
- **Publishing route names into internal resolution.** The same ADR requires a granted route to be - **Withdrawing public names from internal resolution.** The roster still publishes every routed
resolvable inside the mesh, not only routable from outside it; the mechanism that writes public name at its serving node's private address, which ADR 0191 forbids; until the controller
`<node>.internal` into containers does not yet also write the routed names, which is why an stops, a resolver that answers a LAN hands that LAN's non-members addresses they cannot reach.
internal issuer cannot currently validate one without a hand-placed entry.
## The hub adopts the predecessor's tunnel ## The hub adopts the predecessor's tunnel