ATMs should fetch with per-machine read-only deploy keys, not an operator account key #102

Open
opened 2026-09-22 18:28:20 +00:00 by padreug · 1 comment
Owner

Found while fixing the auto-upgrade failures in #98 / #101.

sintra's /root/.ssh/id_ed25519 is byte-identical to the operator's personal key on bohm (SHA256:0qUvwBVIF7l1l2YkqgoaDLOW3FsWBnfZrnO93MZ/y0I), and Forgejo authenticates it as the account, not as a deploy key:

Hi there, padreug! You've successfully authenticated with the key named padreug@gizmo

That is why sintra's system.autoUpgrade can fetch with nothing registered against the repo: it is acting as padreug, an org admin. So root on that machine carries write access to every repo in aiolabs, not read access to bitspire.

Not urgent: sintra is an in-house development machine, not deployed. Filing it because the pattern should be settled before any machine with this shape ships, and because the fix is the right shape regardless.

batm3 is already closer to correct. It has its own generated key (root@bitspire), unauthorized only because nobody registered it. An updater only ever fetches, so a read-only deploy key scoped to this repo is exactly the right grant.

Proposed:

  • Register batm3's key as a read-only deploy key (already needed for #98)
  • Generate a per-machine key on sintra, register it as its own read-only deploy key, remove the operator key from that machine
  • Make per-machine key generation + deploy-key registration part of provisioning, so a flashed ATM never needs an operator key copied onto it
  • Decide whether to rotate padreug@gizmo on the account

Relates to the operator access plane in ADR-002: authorization should be per-machine and revocable, and a shared operator key is neither.

Found while fixing the auto-upgrade failures in #98 / #101. sintra's `/root/.ssh/id_ed25519` is byte-identical to the operator's personal key on bohm (`SHA256:0qUvwBVIF7l1l2YkqgoaDLOW3FsWBnfZrnO93MZ/y0I`), and Forgejo authenticates it as the account, not as a deploy key: ``` Hi there, padreug! You've successfully authenticated with the key named padreug@gizmo ``` That is why sintra's `system.autoUpgrade` can fetch with nothing registered against the repo: it is acting as `padreug`, an org admin. So root on that machine carries write access to every repo in aiolabs, not read access to bitspire. Not urgent: sintra is an in-house development machine, not deployed. Filing it because the pattern should be settled before any machine with this shape ships, and because the fix is the right shape regardless. batm3 is already closer to correct. It has its own generated key (`root@bitspire`), unauthorized only because nobody registered it. An updater only ever fetches, so a read-only deploy key scoped to this repo is exactly the right grant. Proposed: - [ ] Register batm3's key as a read-only deploy key (already needed for #98) - [ ] Generate a per-machine key on sintra, register it as its own read-only deploy key, remove the operator key from that machine - [ ] Make per-machine key generation + deploy-key registration part of provisioning, so a flashed ATM never needs an operator key copied onto it - [ ] Decide whether to rotate `padreug@gizmo` on the account Relates to the operator access plane in [ADR-002](docs/adr/002-remote-access-and-fleet-management.md): authorization should be per-machine and revocable, and a shared operator key is neither.
Author
Owner

Deploying batm3 surfaced the other half of this: a per-machine deploy key has to be registered on every private flake input, not just on this repo.

batm3's key was added to aiolabs/bitspire, and its first nixos-upgrade run then got past fetching and failed while evaluating:

error: Failed to fetch git repository 'ssh://forgejo@git.atitlan.io/aiolabs/atm-tui.git'

Tested from the machine:

aiolabs/bitspire       ACCESSIBLE
aiolabs/atm-tui        DENIED

Those two are currently the only git+ssh://forgejo@git.atitlan.io/... inputs in flake.nix. Registering the same key on aiolabs/atm-tui should finish it — Forgejo allows one key to be attached to several repos, so no second keypair is needed.

sintra never hit this because it authenticates as the operator account, which can read the whole org. That is the same asymmetry this issue is about, seen from the other side: account-wide access hides the problem, per-machine keys make it explicit.

Two things worth folding into the checklist above:

  • Register each machine's key on every private flake input, not only aiolabs/bitspire
  • Treat "adding a new private flake input" as a fleet action — a new input silently breaks every ATM's 04:00 updater while still working from a laptop, because the laptop has account-wide access

The second is the trap. The failure is invisible until someone reads a machine's journal, which is exactly how #98 went unnoticed for six weeks.

Deploying batm3 surfaced the other half of this: a per-machine deploy key has to be registered on **every private flake input**, not just on this repo. batm3's key was added to `aiolabs/bitspire`, and its first `nixos-upgrade` run then got past fetching and failed while evaluating: ``` error: Failed to fetch git repository 'ssh://forgejo@git.atitlan.io/aiolabs/atm-tui.git' ``` Tested from the machine: ``` aiolabs/bitspire ACCESSIBLE aiolabs/atm-tui DENIED ``` Those two are currently the only `git+ssh://forgejo@git.atitlan.io/...` inputs in flake.nix. Registering the same key on `aiolabs/atm-tui` should finish it — Forgejo allows one key to be attached to several repos, so no second keypair is needed. sintra never hit this because it authenticates as the operator account, which can read the whole org. That is the same asymmetry this issue is about, seen from the other side: account-wide access hides the problem, per-machine keys make it explicit. Two things worth folding into the checklist above: - [ ] Register each machine's key on every private flake input, not only aiolabs/bitspire - [ ] Treat "adding a new private flake input" as a fleet action — a new input silently breaks every ATM's 04:00 updater while still working from a laptop, because the laptop has account-wide access The second is the trap. The failure is invisible until someone reads a machine's journal, which is exactly how #98 went unnoticed for six weeks.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/bitspire#102
No description provided.