|
|
|
@@ -0,0 +1,155 @@
|
|
|
|
|
---
|
|
|
|
|
topic: the tiers
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-10-06
|
|
|
|
|
deciders: jochen
|
|
|
|
|
reconstructed: false
|
|
|
|
|
supersedes-in-part:
|
|
|
|
|
- 0009-modules-and-the-graph.md
|
|
|
|
|
extends: 02-DECISIONS/0007-connectivity.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 226. The private network is assigned by its own name, and the proxy names its public issuer
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
**The operator, 2026-10-06: "too many network-related modules … we simply have a private network,
|
|
|
|
|
some docker stuff, a firewall and a public and internal certificate resolver."** Counted on the
|
|
|
|
|
production mesh that day, from the controller's module listing and each machine's plan:
|
|
|
|
|
|
|
|
|
|
| module | on | what it is |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `networking` | all four machines | requirements only: `private-network`, nothing else. Ships nothing |
|
|
|
|
|
| `mesh-wireguard` | on none directly; on all four through `networking` | the private network; its resources are computed by the controller |
|
|
|
|
|
| `public-acme` | the anchor and the home server | runs nothing; offers `acme-ca`, pointing at Let's Encrypt's production directory |
|
|
|
|
|
| `route-proxy` | the anchor and the home server | the public front door; the only consumer of `acme-ca` |
|
|
|
|
|
| `dhcpcd` | none | a third holder of `node-uplink`, for a machine whose link is dhcpcd's |
|
|
|
|
|
| `cloudflare-dns` | none | the only provider of `public-dns`; nothing in the catalogue requires `public-dns` |
|
|
|
|
|
|
|
|
|
|
**`networking` is the domain module [ADR 0009](0009-modules-and-the-graph.md) argued for in its
|
|
|
|
|
"what a domain module turns out to be" section**: a module holding nothing, so `assign networking`
|
|
|
|
|
finds one VPN and takes it, and choosing another is assigning that instead. It has never been more
|
|
|
|
|
than a second name for one module: one VPN exists, every machine runs it, and the name resolution it
|
|
|
|
|
once also required became a fact (ADR 0199). Its cost is real: every machine carries two modules for
|
|
|
|
|
one network, genesis assigns a module that is not the network, and the resolver's hint for a machine
|
|
|
|
|
off the network names the bundle rather than what it installs.
|
|
|
|
|
|
|
|
|
|
**`public-acme` is a provision with one consumer and one answer.** It was introduced so a lab without
|
|
|
|
|
a public issuer could answer `acme-ca` from `step-ca` instead (ADR 0066); `step-ca` now offers only
|
|
|
|
|
`internal-acme-ca`, and the mesh itself is the test bed (ADR 0149). What remained was a module
|
|
|
|
|
assigned beside every proxy to say "Let's Encrypt", and a pin when two answers stood on one machine
|
|
|
|
|
(issue 258).
|
|
|
|
|
|
|
|
|
|
**The proxy's account directory is named after the issuer as rendered.** `route-proxy` keeps each
|
|
|
|
|
authority's ACME account and certificates under `/var/lib/route-proxy/acme`, in a directory named by
|
|
|
|
|
a digest of the directory URL and of the root bundle its `trust` step copies in (the proxy's
|
|
|
|
|
`forThisAuthority`). The binding rendered the URL as `https://acme-v02.api.letsencrypt.org:443/directory`
|
|
|
|
|
— with the port — and the root as the system bundle of the pinned `trust` image. Spelled any other
|
|
|
|
|
way, the proxy sees a new authority, registers a new account and orders every routed name again. On
|
|
|
|
|
2026-10-06 that is 25 names under one registered domain on the home server, all issued on 2026-10-01,
|
|
|
|
|
and 26 on the anchor: a reissue of the home server's would reach Let's Encrypt's 50-per-week limit for that
|
|
|
|
|
domain inside the week.
|
|
|
|
|
|
|
|
|
|
## Considered Options
|
|
|
|
|
|
|
|
|
|
1. **The private network a default of every machine**, with no assignment. Rejected: "a machine is
|
|
|
|
|
on the private network because it was assigned the module" is what made the network a module at
|
|
|
|
|
all (connectivity §1, 2026-08-29); a default is a second path to the same state, and a machine
|
|
|
|
|
that should stay off would need an exception mechanism that does not exist.
|
|
|
|
|
2. **Keep `networking`, document it better.** Rejected: it answers a question — which VPN — that has
|
|
|
|
|
had one answer since it was written, and costs a module on every machine to do so. Choosing
|
|
|
|
|
another VPN is still assigning it; the bundle never added anything to that.
|
|
|
|
|
3. **Assign `mesh-wireguard` directly on every machine and retire the bundle.** Chosen.
|
|
|
|
|
4. **`route-proxy` provides `acme-ca` itself**, keeping the provision. Rejected: nothing else
|
|
|
|
|
consumes it, and a provision whose only consumer is its provider is a field, not an edge.
|
|
|
|
|
5. **The public issuer as a setting of `route-proxy`.** Rejected: a setting has no default (ADR
|
|
|
|
|
0112), so every mesh would need it set before the proxy resolves, and the one value that must not
|
|
|
|
|
change — the URL as rendered, port included — would become a value anybody can change with
|
|
|
|
|
`settings set`. Changing the public issuer of a live proxy is a reissue of every certificate it
|
|
|
|
|
holds; it should take an edit of the module and a decision, not a command.
|
|
|
|
|
6. **`route-proxy` states Let's Encrypt in its own `acme.env`, byte for byte what the binding
|
|
|
|
|
rendered, and `public-acme` retires.** Chosen.
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
**1. The private network is assigned by its own name.** Every machine is assigned `mesh-wireguard`
|
|
|
|
|
directly. The `networking` module is retired: the controller no longer ships it, genesis assigns
|
|
|
|
|
`mesh-wireguard`, and the resolver's refusal for two machines that share no private network names
|
|
|
|
|
`mesh-wireguard`. Another VPN is still chosen by assigning it instead, and the node-scoped claim
|
|
|
|
|
`the-private-network` still refuses two. This supersedes ADR 0009's section *what a domain module
|
|
|
|
|
turns out to be*: the mechanism — a module with requirements and no files — stands, and the
|
|
|
|
|
catalogue no longer holds one.
|
|
|
|
|
|
|
|
|
|
**2. A module the controller stops shipping is retired at its next start, never from under a
|
|
|
|
|
machine.** `module forget` refuses a module the controller ships, because the next start would put
|
|
|
|
|
it back; so a retired one could be removed by nothing. At start the controller removes every module
|
|
|
|
|
it recorded as its own and no longer ships — unless a machine is still assigned it, or the mesh still
|
|
|
|
|
holds settings, secrets or ports for it, in which case it is kept and the start says why.
|
|
|
|
|
|
|
|
|
|
**3. The proxy names its public issuer.** `route-proxy` no longer requires `acme-ca`; its `acme.env`
|
|
|
|
|
states Let's Encrypt's production directory exactly as the binding rendered it. `public-acme` leaves
|
|
|
|
|
the catalogue and the mesh. The internal issuer stays a provision (`internal-acme-ca`, from
|
|
|
|
|
`step-ca`): it is a module that runs, held on one machine and consumed by every proxy and by
|
|
|
|
|
`ca-trust`. The proxy binary's own default stays Let's Encrypt *staging*, for anything that runs it
|
|
|
|
|
without the module.
|
|
|
|
|
|
|
|
|
|
**4. `dhcpcd` and `cloudflare-dns` leave the catalogue.** Neither is assigned anywhere; nothing
|
|
|
|
|
requires `public-dns`. `node-uplink` keeps two holders (NetworkManager, systemd-networkd), and the
|
|
|
|
|
`public-dns` interface of [ADR 0044](0044-a-public-name-is-provisioned-like-any-capability.md) has no
|
|
|
|
|
provider until a mesh needs one.
|
|
|
|
|
|
|
|
|
|
`avahi` and `netcheck` are not decided here.
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
- **The rollout changes no machine's network or certificates.** Assigning `mesh-wireguard` beside
|
|
|
|
|
`networking` and then unassigning `networking` leaves every machine's declaration of the private
|
|
|
|
|
network as it was; the proxy's `acme.env` is unchanged, so its `trust` step does not run again and
|
|
|
|
|
its account directory is the one it has. The one file that goes is the proxy's binding to
|
|
|
|
|
`acme-ca`, which nothing reads.
|
|
|
|
|
- **The order is assign, then unassign.** Unassigning `networking` first takes `mesh-wireguard` off
|
|
|
|
|
every machine that does not also run `dnsmasq` (which requires the mesh's addressing) at the next
|
|
|
|
|
push — the private network down. The controller's retirement refuses nothing here; it only keeps
|
|
|
|
|
the bundle while it is assigned.
|
|
|
|
|
- **The public issuer moves only with a plan for the account.** The proxy's account directory depends
|
|
|
|
|
on the URL as spelled and on the pinned `trust` image's root bundle. Moving that image's digest
|
|
|
|
|
reorders every certificate on both proxies; a test holds both values and says so.
|
|
|
|
|
- **The lab's beds that assigned `networking`, or pointed the proxy at `step-ca` for public names,
|
|
|
|
|
need changing before they run again.** A lab proxy now orders public names from Let's Encrypt's
|
|
|
|
|
production directory, so a bed that routes public names must not assign the module as it stands.
|
|
|
|
|
- **Genesis from an older host binary assigns `networking`, which a newer controller refuses.** The
|
|
|
|
|
host's change is merged before the controller's.
|
|
|
|
|
- **A mesh that wants another public issuer edits `route-proxy`**, rather than assigning a different
|
|
|
|
|
provider. That is deliberate (option 5).
|
|
|
|
|
|
|
|
|
|
## How it is checked
|
|
|
|
|
|
|
|
|
|
| Rule | Checked by |
|
|
|
|
|
|---|---|
|
|
|
|
|
| The controller ships one network module and no bundle; it resolves on its own | mesh-controller `internal/catalogue/provided_test.go` |
|
|
|
|
|
| A refusal for want of the private network names `mesh-wireguard` | the same file, `TestARefusalForWantOfThePrivateNetworkNamesItsModule` |
|
|
|
|
|
| Another VPN is chosen by assigning it, and WireGuard is not dragged in; two VPNs collide | the same file |
|
|
|
|
|
| A module no longer shipped is retired at start; kept while assigned or holding anything; a registered module is never touched | mesh-controller `internal/inventory/retire_test.go`, against a live database |
|
|
|
|
|
| Genesis assigns `mesh-wireguard`, never `networking` | mesh-host `internal/bootstrap/network_module_test.go` |
|
|
|
|
|
| `route-proxy` requires no `acme-ca`; its `acme.env` is byte for byte what the binding rendered; its `trust` image is the pinned one | mesh-controller `internal/catalogue/public_issuer_test.go` against the sibling catalogue |
|
|
|
|
|
| `public-acme`, `dhcpcd`, `cloudflare-dns` are not in the catalogue; nothing in it requires `acme-ca` or `public-dns` | the same file |
|
|
|
|
|
| Live: every machine still on the private network; every public name's certificate serial and expiry unchanged; no new file in a proxy's account directory | the rollout's checks, before and after each step: the controller's network view, `wg show` through each machine's tools, `openssl s_client` against every routed public name, and a listing of `/var/lib/route-proxy/acme` |
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [ADR 0009](0009-modules-and-the-graph.md) — its section on a domain module is superseded; the rest
|
|
|
|
|
stands.
|
|
|
|
|
- [ADR 0007](0007-connectivity.md), [connectivity](../03-DESIGN/01-to-be/08-connectivity.md) §1 and §5,
|
|
|
|
|
amended alongside.
|
|
|
|
|
- [ADR 0066](0066-public-routing-is-name-agnostic.md) — the proxy requiring an issuer; the public one
|
|
|
|
|
is now its own fact.
|
|
|
|
|
- [ADR 0044](0044-a-public-name-is-provisioned-like-any-capability.md) — `public-dns`, without a
|
|
|
|
|
provider in the catalogue.
|
|
|
|
|
- [ADR 0117](0117-a-machines-uplink-is-a-seat.md) — `node-uplink`, now with two holders.
|
|
|
|
|
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0149](0149-the-live-mesh-is-the-test-bed.md).
|
|
|
|
|
- [Issue 258](../04-ISSUES/258-every-machine-bound-the-resolver-to-itself/00-report.md) — the pin two
|
|
|
|
|
issuers on one machine needed.
|
|
|
|
|
- mesh-controller `internal/overlay/generator.go`, `cmd/mesh-controller/modules.go`, `stores.go`,
|
|
|
|
|
`internal/inventory/catalogue.go`, `internal/catalogue/resolve.go`; mesh-host
|
|
|
|
|
`internal/bootstrap/phase2.go`; mesh-catalog `modules/route-proxy`, and the removal of
|
|
|
|
|
`modules/public-acme`, `modules/dhcpcd`, `modules/cloudflare-dns`.
|