Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69a002fce3 | ||
|
|
16a1a52cd8 | ||
|
|
af170e3a67 | ||
|
|
6c2d5f5913 |
@@ -150,6 +150,16 @@ closed by this record, only answered by it.
|
||||
resolvable inside the mesh — holds unchanged and by the same means the machine already uses.
|
||||
- **Issue 110 stops being a container-DNS inconvenience and becomes a prerequisite** for the mesh not
|
||||
restarting itself whenever it learns a name.
|
||||
- **A container that names a resolver of its own has opted out of the machine's**, and the copy this
|
||||
record removes was the only reason such a container could reach anything by a mesh name.
|
||||
|
||||
> **Progressive insight — 2026-09-30, the afternoon this landed. Found the hard way.** The mail
|
||||
> system's admin, behind Mailu's own resolver, lost its database the moment the copy went
|
||||
> ([issue 171](../04-ISSUES/171-a-modules-own-resolver-knows-no-mesh-name/00-report.md)). A `dns` on
|
||||
> a container is a decision about whether mesh names exist inside it, not a preference; the module
|
||||
> was corrected, and whether the controller should refuse the contradiction is that issue's open
|
||||
> question.
|
||||
|
||||
- **A container started by hand gets the mesh's names too**, where before only declared containers did.
|
||||
Design 08 drew that boundary deliberately, on the grounds that reaching into every container is what
|
||||
a nameserver would be for. This record accepts that consequence rather than working around it: a
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
status: resolved
|
||||
opened: 2026-09-30
|
||||
located-in:
|
||||
- mesh-catalog modules/mailu (eight containers name Mailu's own resolver, and one of them binds a mesh name)
|
||||
- mesh-catalog modules/dnsmasq (dropped the DNSSEC bit its upstreams set)
|
||||
fixed-by: mesh-catalog PR 178 (mailu-admin uses the machine's resolver) and PR 179 (the machine's resolver passes the DNSSEC bit down) — 2026-09-30, the same afternoon
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 171 — A module that names its own resolver knows no mesh name
|
||||
|
||||
## What was observed
|
||||
|
||||
The afternoon [ADR 0148](../../02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md)
|
||||
landed — no container is given the mesh's names any more; it asks the machine's resolver — the mail
|
||||
system's admin container began logging, 523 times in three minutes:
|
||||
|
||||
```
|
||||
psycopg2.OperationalError: could not translate host name "novox.internal" to address: Name does not resolve
|
||||
```
|
||||
|
||||
Mail was accepted on every port and the web front answered; the admin and the spam filter beside it
|
||||
were unhealthy, and anything that needed the database — a mailbox change through the API, the spam
|
||||
filter's domain list — failed. Found by the operator asking whether mail was back, forty minutes in.
|
||||
|
||||
Mailu ships its own resolver, an unbound in a container, and every other Mailu container is told to
|
||||
use it — the module carries `dns: [192.168.203.254]` on eight containers. That resolver recurses from the
|
||||
root and knows nothing under `.internal`. Until that afternoon the admin container had the database's
|
||||
name anyway, because the mesh wrote every name into every container at creation; the copy was the only
|
||||
reason a container behind its own resolver could reach anything by a mesh name, and nobody knew it was
|
||||
load-bearing.
|
||||
|
||||
**Removing the override was not enough.** Given the machine's resolver instead, the admin refused to
|
||||
start: `Your DNS resolver at 127.0.0.11 isn't doing DNSSEC validation`. Mailu checks, at start, that
|
||||
its resolver returns the Authenticated Data bit for a signed name. The mesh's resolver forwards to two
|
||||
upstreams that validate and set the bit, and dropped it on the way down — dnsmasq does unless told
|
||||
otherwise. Mailu's own unbound has no hook to forward a zone elsewhere, so it could not be taught the
|
||||
mesh's names either.
|
||||
|
||||
## Why it matters beyond this instance
|
||||
|
||||
**A container with a resolver of its own has opted out of the machine's, and nothing says so.** 0148
|
||||
made the machine's resolver load-bearing for every container; a `dns` on a container is a quiet
|
||||
exception to that, and the exception used to be papered over by the copy the record removed. The
|
||||
manifest field reads like a preference and is a decision about whether mesh names exist inside the
|
||||
container.
|
||||
|
||||
**A resolver that forwards to validating upstreams and hides the fact is less useful than it could
|
||||
be**, and the first program to check found out.
|
||||
|
||||
**The mesh reported nothing.** Every container ran; the failing one accepted connections; the report
|
||||
was about bytes. It is [issue 145](../145-a-machine-reads-healthy-while-its-modules-cannot-reach-each-other/00-report.md)
|
||||
again, and the check that would have caught it is the same unbuilt one.
|
||||
|
||||
## What was done
|
||||
|
||||
- The one Mailu container that binds a mesh name — the admin, through the database it is granted —
|
||||
no longer names Mailu's resolver and uses the machine's, like every container without a `dns` of
|
||||
its own (mesh-catalog PR 178). The other seven keep unbound: the spam filter needs a validating
|
||||
resolver for its blocklist lookups, and none of them asks for a mesh name.
|
||||
- The machine's resolver passes the DNSSEC bit down from its upstreams, `proxy-dnssec` (PR 179). It
|
||||
does not validate itself; the trust is the upstream's and the path to it, as a forwarding resolver's
|
||||
always was, and the configuration says so.
|
||||
|
||||
## What checks it
|
||||
|
||||
The admin container's own start-up check, which is what failed, and the mesh's status once it reads
|
||||
healthy. A container-level check that a mesh name resolves from inside every declared container is
|
||||
the one 110 and 145 both ask for and is not built.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should a container's `dns` be refused, or made to say what it gives up? A module that names its
|
||||
own resolver and binds a mesh name is a contradiction the controller can see at composition — the
|
||||
grant hands it a name its resolver will not answer.
|
||||
- Should the machine's resolver validate rather than proxy? It would cost a trust anchor on every
|
||||
machine and make the resolver slower to start; proxying was enough for the one program that asked.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
status: located
|
||||
opened: 2026-09-30
|
||||
located-in:
|
||||
- the predecessor's terminal module (still generating the operator's ssh client blocks on every workstation)
|
||||
- mesh-controller internal/catalogue (the ssh-client roster, tested and not yet a catalogue module)
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 172 — The ssh client block for a machine matches one spelling of its name, and the other gets the wrong user
|
||||
|
||||
## What was observed
|
||||
|
||||
On a workstation, 2026-09-30, reported by the operator. `ssh home-server` logs in; `ssh
|
||||
home-server.internal` is refused with `Permission denied (publickey)`. The operator expected the
|
||||
opposite, if either: the full mesh name is the one the resolver serves.
|
||||
|
||||
The name is not the fault. Both spellings resolve to the machine's private address — the mesh's roster
|
||||
region in the hosts file carries `<node>.internal <node>` on one line, and the resolver answers
|
||||
anything under the node's name. What differs is the login: the generated client configuration has a
|
||||
`Host home-server` block naming the account to log in as, and `home-server.internal` matches no block,
|
||||
so ssh falls back to the operator's local username, which has no account on that machine. Spelled
|
||||
`account@home-server.internal` it works.
|
||||
|
||||
The file is the predecessor's. `~/.ssh/config.d/mesh` says in its own header that it is generated by
|
||||
the predecessor's terminal module, which only ever wrote the bare name. The mesh's own ssh-client
|
||||
roster — every other machine's Host block, written as a marked region of the operator's `~/.ssh/config`
|
||||
with the account the mesh knows for that machine (to-be 29) — already matches both spellings, and a
|
||||
controller test holds `Host marge marge.internal`. It is composed and tested in the controller and is
|
||||
not a module in the catalogue, so no machine receives it; every workstation still runs the
|
||||
predecessor's generator.
|
||||
|
||||
## Why it matters beyond this instance
|
||||
|
||||
**A name the mesh serves and a name a person can use are not the same set**, and the difference is
|
||||
silent. The resolver, the hosts file and the certificate authority all treat `<node>.internal` as the
|
||||
machine's name; the one file that decides who you log in as does not know it. A person who learns the
|
||||
mesh's name from `status` or from a certificate and types it is refused with an error that says
|
||||
nothing about a missing Host block.
|
||||
|
||||
**It is the migration story for the operator's own tooling, arriving as a symptom.** The mesh has the
|
||||
right file and does not ship it. Until the ssh-client roster is a module and is assigned to the
|
||||
workstations, the predecessor's generator keeps writing a file the mesh has already superseded, and
|
||||
every such file is one the mesh cannot correct.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should the ssh-client roster become a catalogue module now, assigned to every workstation, and take
|
||||
the predecessor's `config.d/mesh` out of the operator's `Include`? Its content is settled; what is
|
||||
not is the takeover of a file in a person's home that another generator still writes.
|
||||
- Should the block match a third spelling — the machine's public name, where it has one — or is that
|
||||
a different key and a different account?
|
||||
- What checks it? A controller test holds the two spellings; nothing checks that the file a workstation
|
||||
actually has is the mesh's rather than the predecessor's.
|
||||
Reference in New Issue
Block a user