ADR 0191: the mesh resolves only its own names; a public name resolves publicly #320
@@ -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
|
||||
|
||||
> **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
|
||||
|
||||
**[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
|
||||
|
||||
> **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
|
||||
|
||||
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.
|
||||
@@ -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)
|
||||
- **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)
|
||||
- **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
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ code:
|
||||
- mesh-controller internal/catalogue/filtering.go
|
||||
- mesh-controller examples/route-proxy
|
||||
- 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/apply (the service that reflects a rule set)
|
||||
updated: 2026-10-02
|
||||
updated: 2026-10-03
|
||||
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/0170-the-firewall-seat-serves-its-verbs.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 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
|
||||
`<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
|
||||
([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
|
||||
|
||||
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
|
||||
the public internet cannot resolve.
|
||||
|
||||
**And it does not work until the routed name resolves inside the mesh** — the §2 finding above,
|
||||
arriving here because this is what needed it. The authority's challenge reaches the routed name only
|
||||
once that name is in internal resolution; a public authority is handed that dependency by public
|
||||
DNS, and an internal one has to be handed it by the mesh. *Checked by a handshake to a routed name
|
||||
that verifies against the internal root and nothing else — which cannot succeed unless the issuer
|
||||
first reached the name to certify it*
|
||||
([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)).
|
||||
**The internal authority certifies internal names; a public one certifies public names.** The
|
||||
authority's challenge reaches the name it certifies, so each certifies what it can resolve: the
|
||||
internal authority a route's `<label>.<node>.internal`, which the mesh resolves, and a public authority
|
||||
the public name, which public DNS resolves. A proxy holds both, and a public name is never certified
|
||||
by the internal authority. *Checked by a handshake to a route's internal name that verifies against
|
||||
the internal root and nothing else, and one to its public name that verifies against the public
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
- **Publishing route names into internal resolution.** The same ADR requires a granted route to be
|
||||
resolvable inside the mesh, not only routable from outside it; the mechanism that writes
|
||||
`<node>.internal` into containers does not yet also write the routed names, which is why an
|
||||
internal issuer cannot currently validate one without a hand-placed entry.
|
||||
- **Withdrawing public names from internal resolution.** The roster still publishes every routed
|
||||
public name at its serving node's private address, which ADR 0191 forbids; until the controller
|
||||
stops, a resolver that answers a LAN hands that LAN's non-members addresses they cannot reach.
|
||||
|
||||
## The hub adopts the predecessor's tunnel
|
||||
|
||||
|
||||
Reference in New Issue
Block a user