Issues 239, 240, and 238's diagnosis: a module name taken over, a dry run recorded, the operator banned
239: two repositories defined photos; a rebuild to the catalogue's main replaced the app with a stub and nothing refused it. 240: a dry-run build was recorded and its definition reached the machine. 238: the forge refused three ssh logins as the operator's account in 31 seconds; nothing in the mesh's ssh configuration names the forge, and no jail ignores the mesh's own public addresses.
This commit is contained in:
+43
@@ -0,0 +1,43 @@
|
||||
# 238 — Diagnosis
|
||||
|
||||
## 2026-10-04, from the control node
|
||||
|
||||
**What the forge refused, from the operator's uplink, in the ban's last minute** (the forge's ssh log):
|
||||
|
||||
```
|
||||
14:05:28 Invalid user jochen from <uplink> port 38564
|
||||
14:05:31 Accepted publickey for git from <uplink> port 45156 (the laptop's key)
|
||||
14:05:44 Invalid user jochen from <uplink> port 52074
|
||||
14:05:59 Invalid user jochen from <uplink> port 37978
|
||||
```
|
||||
|
||||
Three refusals in thirty-one seconds — `maxretry = 3` — and the `gitea` jail banned the uplink at
|
||||
14:06:00; `recidive` counted it the same second. Between the refusals the same key logged in as `git`:
|
||||
the agent's own git operations were fine, and the refusals were ssh commands that named no user.
|
||||
|
||||
**Why they named the wrong user.** On the laptop and the workstation, `ssh -G <forge's public name>`
|
||||
resolves to the operator's account and port 22: nothing in the ssh configuration the mesh writes
|
||||
(`ssh-client`, to-be 29) names the forge. A bare `ssh <forge>` — or a git URL without `git@` — presents
|
||||
the login name, which the forge does not have.
|
||||
|
||||
**Why the operator's uplink is bannable at all.** The jails' `ignoreip` (the `fail2ban` module's
|
||||
`jail.local`) holds loopback, the mesh's private range and every private range (ADR 0186). A node at
|
||||
home reaches the control node from the home's public address, which is none of those. Nothing the
|
||||
mesh knows puts it there.
|
||||
|
||||
## What would have stopped it
|
||||
|
||||
1. **The forge in the ssh configuration the mesh writes**: a `Host` block for the forge's public and
|
||||
internal names with `User git` and the forge's ssh port. It cannot be written today: the `git`
|
||||
provision serves the forge's http port only, and the controller translates a served `port` to the
|
||||
machine's published port but no other key (`ServedOn`), so an `ssh-port` would reach a consumer as
|
||||
the container's 22, not the machine's 222.
|
||||
2. **The mesh's own public addresses in every jail's ignore list.** Two sources: each node's public
|
||||
egress as the hub sees it (the tunnel's peer endpoints — every node at home shows the uplink there),
|
||||
or an operator setting naming them. The first is derived and stays true when the uplink changes;
|
||||
the second is a value somebody must remember to edit.
|
||||
|
||||
## Status
|
||||
|
||||
Not located further: both remedies are design choices — (1) a served port the controller translates
|
||||
by listen, (2) the open question 1 of the report.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
status: open
|
||||
opened: 2026-10-04
|
||||
located-in: []
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 239 — A module name is taken over by another repository, and nothing refuses it
|
||||
|
||||
## What was observed
|
||||
|
||||
2026-10-04. Five of the photo app's six public names stopped answering on the control node — the API,
|
||||
two client sites and two aliases — while the sixth answered with a different program. Nothing failed:
|
||||
the controller composed, the host applied, every check passed.
|
||||
|
||||
Two definitions held the module name `photos`:
|
||||
|
||||
| | the app's own repository | the catalogue's `modules/photos` |
|
||||
|---|---|---|
|
||||
| built from | `photos.git`, branch `nox-mesh`, until 2026-09-28 | from 2026-10-04 04:14 |
|
||||
| containers | server, admin, two client sites | server, an admin client |
|
||||
| routes | six | one |
|
||||
|
||||
Rebuilding `photos` "to `main`" for [ADR 0202](../../02-DECISIONS/0202-a-provider-declares-what-it-derives-for-each-consumer.md)
|
||||
(see [issue 227](../227-the-photo-apps-admin-client-asks-for-the-port-the-proxy-holds/00-report.md)) built the
|
||||
catalogue's main, not the repository the module had been built from. The controller recorded the new
|
||||
source, the module moved, the next push replaced the app with the stub, and two sites' containers and
|
||||
five routes went with it. The stub had sat in the catalogue since 2026-09-03 without ever being the
|
||||
running definition.
|
||||
|
||||
Restored the same day by building from `photos.git` again and removing the stub (novox/photos#3,
|
||||
mesh-catalog#278).
|
||||
|
||||
## Why it is an issue and not an incident
|
||||
|
||||
**A module's source is a fact the mesh records, and any build may overwrite it.** The build history
|
||||
showed the switch plainly — `photos.git at nox-mesh` on one line, `mesh-catalog.git at main` on the
|
||||
next — and nothing asked whether a module built from one repository should now come from another.
|
||||
"The last build wins" is the rule in practice; it is written nowhere, and it lets a stale or unrelated
|
||||
definition replace a working one silently.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Should a build whose source differs from the module's recorded source be refused unless it says so
|
||||
explicitly (a `--move-source`, or the operator's confirmation)?
|
||||
2. Should the catalogue's check refuse a module whose name another registered repository already
|
||||
defines?
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
status: open
|
||||
opened: 2026-10-04
|
||||
located-in: []
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 240 — A dry-run build is recorded, and what it built is applied
|
||||
|
||||
## What was observed
|
||||
|
||||
2026-10-04, restoring the photo app ([issue 239](../239-a-module-name-is-taken-over-by-another-repository-and-nothing-refuses/00-report.md)).
|
||||
`mesh-controller build <repository> --ref <unmerged branch> --dry-run` was run to prove the branch built
|
||||
before it was merged. The command's help says *"build and print the manifest, recording nothing"*.
|
||||
|
||||
Afterwards:
|
||||
|
||||
- `builds photos` listed the dry run as a build — `photos.git at <the unmerged branch>`, with its three
|
||||
images — beside the real ones.
|
||||
- On the control node, the module's state directory was rewritten at a time between the dry run and
|
||||
the merged build: the secret files and environment files the branch's definition declares, which the
|
||||
definition then running did not.
|
||||
|
||||
The content happened to equal what was merged a few minutes later, so nothing broke. Had the branch
|
||||
been rejected in review, its definition would already have been on the machine.
|
||||
|
||||
## Why it matters
|
||||
|
||||
A dry run is how a change is proven before a person approves it. If it records the build and the mesh
|
||||
acts on it, review becomes a formality: the unreviewed definition reaches a machine first.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Where does the dry run's outcome enter the record — the build machine's `built` event, consumed as
|
||||
any other build's?
|
||||
2. Did the controller roll the dry run out, or did a later push compose from it?
|
||||
Reference in New Issue
Block a user