Peering trust store + admin surface: keys, peer registry, revocation #101

Closed
opened 2026-08-04 11:03:16 +00:00 by Cordy · 1 comment
Owner

The two lists from the diagram, per instance: (1) keys we generated for named peers ("Generierte Peering Keys: INS-OMEGA: OMEGA-123456…"), (2) registered peers we can send to ("Eingetragene Instanzen: Omega: ALPHA-9876512, Omega-URL"). Key exchange between admins stays out of band by design (email/vault/pastebin — "den Schlüsselaustausch sollen die selber lösen").

Store: JSON file on the data volume (same pattern as holds/settings/app-passwords — no DB). Issued keys are stored hashed only, shown once at generation like app-password secrets. Registered peers store name, URL, and the received key (this one must be plaintext-at-rest — we present it/derive from it when sending; document that in the threat model).

Admin API (admin-gated like /api/v1/admin/*):

  • POST /api/v1/admin/peering/keys {peer} → generates, returns secret ONCE
  • GET /api/v1/admin/peering/keys → list (peer name, hash prefix, created, last-used)
  • DELETE /api/v1/admin/peering/keys/{peer} → revoke (instant: inbound from that peer dies)
  • POST /api/v1/admin/peering/peers {name, url, key} / GET / DELETE /{name} → outbound registry

Admin UI: Settings → Peering panel with both lists, copy-once key display, revoke buttons. i18n en/de/fr/it. Audit events: peering-key-create, peering-key-revoke, peer-register, peer-remove.

Open questions:

  • Key format: proposal CAIRN-PEER1.<base32(32 random bytes)> — versioned prefix like license keys, no instance name baked in (names are local aliases per the ADR). OK, or keep Manuel's <NAME>-<digits> mnemonic style?
  • last-used tracking on issued keys: worth the write-per-transfer, or log-only?
  • URL validation: require https for non-RFC1918 targets, allow http on LAN (mirrors our Basic-auth stance)? Decided in ADR (#100) Decision 2 — this issue implements whatever lands there.
  • Rotation UX: revoke+regenerate is v1; is one-click "rotate" (old key valid for N hours) needed before launch or later?
  • Should registering a peer do an immediate reachability/handshake test with visible result ("peering verified ✓"), or stay lazy until first send? Recommend: test button, not automatic.
The two lists from the diagram, per instance: (1) **keys we generated** for named peers ("Generierte Peering Keys: INS-OMEGA: OMEGA-123456…"), (2) **registered peers** we can send to ("Eingetragene Instanzen: Omega: ALPHA-9876512, Omega-URL"). Key exchange between admins stays out of band by design (email/vault/pastebin — "den Schlüsselaustausch sollen die selber lösen"). **Store:** JSON file on the data volume (same pattern as holds/settings/app-passwords — no DB). Issued keys are stored **hashed only**, shown once at generation like app-password secrets. Registered peers store name, URL, and the received key (this one must be plaintext-at-rest — we present it/derive from it when sending; document that in the threat model). **Admin API** (admin-gated like /api/v1/admin/*): - `POST /api/v1/admin/peering/keys` `{peer}` → generates, returns secret ONCE - `GET /api/v1/admin/peering/keys` → list (peer name, hash prefix, created, last-used) - `DELETE /api/v1/admin/peering/keys/{peer}` → revoke (instant: inbound from that peer dies) - `POST /api/v1/admin/peering/peers` `{name, url, key}` / `GET` / `DELETE /{name}` → outbound registry **Admin UI:** Settings → Peering panel with both lists, copy-once key display, revoke buttons. i18n en/de/fr/it. Audit events: `peering-key-create`, `peering-key-revoke`, `peer-register`, `peer-remove`. **Open questions:** - Key format: proposal `CAIRN-PEER1.<base32(32 random bytes)>` — versioned prefix like license keys, no instance name baked in (names are local aliases per the ADR). OK, or keep Manuel's `<NAME>-<digits>` mnemonic style? - `last-used` tracking on issued keys: worth the write-per-transfer, or log-only? - URL validation: require https for non-RFC1918 targets, allow http on LAN (mirrors our Basic-auth stance)? Decided in ADR (#100) Decision 2 — this issue implements whatever lands there. - Rotation UX: revoke+regenerate is v1; is one-click "rotate" (old key valid for N hours) needed before launch or later? - Should registering a peer do an immediate reachability/handshake test with visible result ("peering verified ✓"), or stay lazy until first send? Recommend: test button, not automatic.
Cordy closed this issue 2026-08-04 23:53:43 +00:00
Author
Owner

Done — PR #119 (store + admin API) and PR #120 (Settings panel).

Shipped as specified, with the open questions from this issue resolved:

  • Key format: CAIRN-PEER1.<base32(32 bytes)> — versioned prefix, no instance name baked in (peer names are local aliases).
  • Storage: issued keys as argon2id verifiers, shown once; registered peers' keys in the clear (we must present them). Atomic JSON, no DB.
  • Sender allow-lists (#100 Q6) landed here rather than later: per-peering users + IdP groups, evaluated against live groups (#96), failing closed on empty. Admin status deliberately does not grant sending.
  • URL policy: plain http refused unless the host is private/loopback, mirroring the Basic-auth-is-LAN-only stance.
  • last-used tracking: not built — log-only for now, revisit if the beta wants it.
  • Reachability test button: not built; there is nothing to test against until #102 exists. Worth adding then.
  • Rotation: revoke + re-issue + re-exchange, as proposed. Overlapping validity remains a later nicety.

Built test-first: nine registry tests written and watched fail before implementation.

Next: #102.

**Done** — PR #119 (store + admin API) and PR #120 (Settings panel). Shipped as specified, with the open questions from this issue resolved: - **Key format:** `CAIRN-PEER1.<base32(32 bytes)>` — versioned prefix, no instance name baked in (peer names are local aliases). - **Storage:** issued keys as argon2id verifiers, shown once; registered peers' keys in the clear (we must present them). Atomic JSON, no DB. - **Sender allow-lists** (#100 Q6) landed here rather than later: per-peering users + IdP groups, evaluated against live groups (#96), failing closed on empty. Admin status deliberately does **not** grant sending. - **URL policy:** plain `http` refused unless the host is private/loopback, mirroring the Basic-auth-is-LAN-only stance. - **`last-used` tracking:** not built — log-only for now, revisit if the beta wants it. - **Reachability test button:** not built; there is nothing to test against until #102 exists. Worth adding then. - **Rotation:** revoke + re-issue + re-exchange, as proposed. Overlapping validity remains a later nicety. Built test-first: nine registry tests written and watched fail before implementation. Next: #102.
Sign in to join this conversation.
No labels
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: Cordy/Cairn#101
No description provided.