feat(ops): device-audit script + canonical devices.yaml registry — closes recurrence vector of #5 #15

Open
opened 2026-05-27 23:46:23 +00:00 by jmz · 0 comments
Owner

Background

Closing #5 (the specific mba-726dadit "ghost device" report) tonight surfaced a recurrence vector: tonight's investigation only confirmed the device was legitimate after cryptographic comparison of the curve25519 in the local store. If that key hadn't been available, the only evidence would have been "the device looks idle but might be live or might not" — exactly the same place #5's filer was in May.

Host-prefixed device ids (mba-…, nas-…, rpi5-…) — landed in the rotation runbook after #5 was filed — help, but only when an operator is looking. We have no automated detection of:

  • (a) devices registered in Synapse that shouldn't be there (rotation-orphans, abandoned hosts, leaked tokens)
  • (b) devices in our canonical fleet that are missing from Synapse (host died, key got purged, etc.)
  • (c) devices that have been idle long enough to flag for retirement

The same diagnostic dance from #5 will recur every time a Mac is unused for >2 days, every time a rotation is half-completed, every time a test bridge instance is forgotten. We should make the data legible without manual sleuthing.

Proposed shape

1. Canonical device registry

A small devices.yaml (in this repo) listing the expected fleet:

devices:
  - mxid: "@claudius-maximus:matrix.ziefle.org"
    device_id: "mba-726dadit"          # explicit, since these don't rotate often
    host: "mba"
    role: "Maximus bridge (MacBook Air)"
    last_audit: "2026-05-27"
  - mxid: "@claudius-maximus:matrix.ziefle.org"
    device_id: "nas-gfw5uy4w"
    host: "nas"
    role: "Maximus bridge (Docker on NAS)"
    last_audit: "2026-05-27"
  - mxid: "@claudius-maximus:matrix.ziefle.org"
    device_id: "rpi5-0o3kuj3y"
    host: "rpi5"
    role: "Maximus bridge (Raspberry Pi 5)"
    last_audit: "2026-05-27"
  # @claudius-felinus, @jmz, @kitty similarly...

Lives in the repo so the device list is git-tracked. Rotation procedure (in docs/bridge-client-setup.md) gains a final step: "update devices.yaml with the new id".

2. scripts/audit-devices.py (or sh)

Diff the registry against live Synapse:

$ paperless-curator audit-devices  # OK, different repo — but the shape is similar
# Or just:
$ ./scripts/audit-devices.py --config devices.yaml
─── unexpected devices in Synapse (not in registry) ─────────────────
  @claudius-maximus:matrix.ziefle.org
    test-bridge-q3a8z  display="Test Bridge"  last_seen=2026-05-12 (15 days ago)

─── registered devices missing from Synapse ─────────────────────────
  (none)

─── registered devices idle >30 days ────────────────────────────────
  (none)

─── summary ──────────────────────────────────────────────────────────
  3 registered + 0 missing + 1 unexpected + 0 idle = ACTION REQUIRED

Exits non-zero when ACTION REQUIRED so it can drive a cron alert.

3. Scheduling

Run weekly via a .forgejo/workflows/audit-devices.yml cron, posting results to the bridge matrix room when ACTION REQUIRED. Or trivially via crontab on rpi5 / nas with curl matrix-room-webhook on failure.

Acceptance criteria

  • devices.yaml committed with current fleet (~3 Maximus + Felinus/jmz/kitty devices)
  • scripts/audit-devices.py (or equivalent) implements the diff + exits non-zero on mismatch
  • docs/bridge-client-setup.md rotation procedure gains a "update devices.yaml" step
  • Scheduled run (cron or Forgejo Actions) — fires alert on mismatch
  • Unit tests for the audit logic (mock Synapse API + registry → expected output)

Out of scope (defer to follow-up tickets)

  • Auto-deleting unexpected devices (audit just reports; human decides)
  • OTK / cross-signing health checks (different concern, separate audit)
  • Idle-device auto-retirement (could be a future opt-in; this audit is read-only)
## Background Closing #5 (the specific `mba-726dadit` "ghost device" report) tonight surfaced a recurrence vector: tonight's investigation only confirmed the device was legitimate after cryptographic comparison of the curve25519 in the local store. If that key hadn't been available, the only evidence would have been "the device looks idle but might be live or might not" — exactly the same place #5's filer was in May. Host-prefixed device ids (`mba-…`, `nas-…`, `rpi5-…`) — landed in the rotation runbook after #5 was filed — help, but only when an operator is looking. We have no automated detection of: - (a) devices registered in Synapse that shouldn't be there (rotation-orphans, abandoned hosts, leaked tokens) - (b) devices in our canonical fleet that are missing from Synapse (host died, key got purged, etc.) - (c) devices that have been idle long enough to flag for retirement The same diagnostic dance from #5 will recur every time a Mac is unused for >2 days, every time a rotation is half-completed, every time a test bridge instance is forgotten. We should make the data legible without manual sleuthing. ## Proposed shape ### 1. Canonical device registry A small `devices.yaml` (in this repo) listing the expected fleet: ```yaml devices: - mxid: "@claudius-maximus:matrix.ziefle.org" device_id: "mba-726dadit" # explicit, since these don't rotate often host: "mba" role: "Maximus bridge (MacBook Air)" last_audit: "2026-05-27" - mxid: "@claudius-maximus:matrix.ziefle.org" device_id: "nas-gfw5uy4w" host: "nas" role: "Maximus bridge (Docker on NAS)" last_audit: "2026-05-27" - mxid: "@claudius-maximus:matrix.ziefle.org" device_id: "rpi5-0o3kuj3y" host: "rpi5" role: "Maximus bridge (Raspberry Pi 5)" last_audit: "2026-05-27" # @claudius-felinus, @jmz, @kitty similarly... ``` Lives in the repo so the device list is git-tracked. Rotation procedure (in `docs/bridge-client-setup.md`) gains a final step: "update `devices.yaml` with the new id". ### 2. `scripts/audit-devices.py` (or sh) Diff the registry against live Synapse: ```sh $ paperless-curator audit-devices # OK, different repo — but the shape is similar # Or just: $ ./scripts/audit-devices.py --config devices.yaml ─── unexpected devices in Synapse (not in registry) ───────────────── @claudius-maximus:matrix.ziefle.org test-bridge-q3a8z display="Test Bridge" last_seen=2026-05-12 (15 days ago) ─── registered devices missing from Synapse ───────────────────────── (none) ─── registered devices idle >30 days ──────────────────────────────── (none) ─── summary ────────────────────────────────────────────────────────── 3 registered + 0 missing + 1 unexpected + 0 idle = ACTION REQUIRED ``` Exits non-zero when ACTION REQUIRED so it can drive a cron alert. ### 3. Scheduling Run weekly via a `.forgejo/workflows/audit-devices.yml` cron, posting results to the bridge matrix room when ACTION REQUIRED. Or trivially via crontab on rpi5 / nas with `curl matrix-room-webhook` on failure. ## Acceptance criteria - [ ] `devices.yaml` committed with current fleet (~3 Maximus + Felinus/jmz/kitty devices) - [ ] `scripts/audit-devices.py` (or equivalent) implements the diff + exits non-zero on mismatch - [ ] `docs/bridge-client-setup.md` rotation procedure gains a "update devices.yaml" step - [ ] Scheduled run (cron or Forgejo Actions) — fires alert on mismatch - [ ] Unit tests for the audit logic (mock Synapse API + registry → expected output) ## Out of scope (defer to follow-up tickets) - Auto-deleting unexpected devices (audit just reports; human decides) - OTK / cross-signing health checks (different concern, separate audit) - Idle-device auto-retirement (could be a future opt-in; this audit is read-only)
Sign in to join this conversation.
No description provided.