Per-key resource scoping: a service key may modify any service #72

Open
opened 2026-10-27 10:15:23 +00:00 by james.oates · 0 comments
Owner

Option 3 from #55, split out when the first two landed in d1bb5a5. Not started, no milestone.

key_type now gates key management, so a service key can no longer mint or revoke keys, and a session carries only the authority of the key that minted it. What remains is that a service key is not scoped to particular resources: any valid key may PATCH or DELETE any service or L4 route.

So the original #55 scenario is narrowed but not closed. Hand a container a key so it can self-register; if that container is compromised, the key still repoints an unrelated service — PATCH /api/services/webmail with the attacker's target_host, and Edge Router keeps presenting the genuine certificate for it, so a browser shows a valid padlock. What it can no longer do is mint a replacement key to survive revocation, or revoke the master key.

Shape, roughly

A key records which resources it owns (most naturally: the ones it created), and the mutating routes check ownership. Decisions it needs:

  • What a key owns — resources it created, or an explicit allow-list set when the key is minted? Creation-based is automatic and needs no new config; explicit is auditable and lets an operator pre-authorise a name before the workload first starts.
  • Does the master key bypass it? Almost certainly yes.
  • Existing rows. Every service currently in the table has no owner. They cannot all become unowned-and-immutable without breaking live deployments, so either unowned means "master only" (safe, may surprise) or "any key" (no change for existing rows, weaker). Needs a migration decision either way.
  • TOML-sourced resources (source=config) arguably should not be API-mutable at all, which may be a cheaper partial answer worth taking first — on Kuruk every one of 18 services and 13 tls_domains is source=config, so there a config/API split alone would remove the whole attack surface.

That last point may be the 80% fix for a fraction of the work, and it is worth evaluating before building ownership tracking.

Why it is unmilestoned

Nothing in the fleet currently hands a key to a workload — Roku serves no API, Kuruk uses the dashboard only, devinfra calls /api/* from nothing. So this guards a deployment pattern that exists in the product but not yet in practice. Worth doing before anyone adopts that pattern, not before.

Option 3 from #55, split out when the first two landed in `d1bb5a5`. **Not started, no milestone.** `key_type` now gates key management, so a service key can no longer mint or revoke keys, and a session carries only the authority of the key that minted it. What remains is that a service key is not scoped to *particular* resources: any valid key may `PATCH` or `DELETE` any service or L4 route. So the original #55 scenario is narrowed but not closed. Hand a container a key so it can self-register; if that container is compromised, the key still repoints an unrelated service — `PATCH /api/services/webmail` with the attacker's `target_host`, and Edge Router keeps presenting the genuine certificate for it, so a browser shows a valid padlock. What it can no longer do is mint a replacement key to survive revocation, or revoke the master key. ## Shape, roughly A key records which resources it owns (most naturally: the ones it created), and the mutating routes check ownership. Decisions it needs: - **What a key owns** — resources it created, or an explicit allow-list set when the key is minted? Creation-based is automatic and needs no new config; explicit is auditable and lets an operator pre-authorise a name before the workload first starts. - **Does the master key bypass it?** Almost certainly yes. - **Existing rows.** Every service currently in the table has no owner. They cannot all become unowned-and-immutable without breaking live deployments, so either unowned means "master only" (safe, may surprise) or "any key" (no change for existing rows, weaker). Needs a migration decision either way. - **TOML-sourced resources** (`source=config`) arguably should not be API-mutable at all, which may be a cheaper partial answer worth taking first — on Kuruk every one of 18 services and 13 tls_domains is `source=config`, so there a config/API split alone would remove the whole attack surface. That last point may be the 80% fix for a fraction of the work, and it is worth evaluating before building ownership tracking. ## Why it is unmilestoned Nothing in the fleet currently hands a key to a workload — Roku serves no API, Kuruk uses the dashboard only, devinfra calls `/api/*` from nothing. So this guards a deployment pattern that exists in the product but not yet in practice. Worth doing before anyone adopts that pattern, not before.
Sign in to join this conversation.
No labels
No milestone
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
IsoHex/edge-router#72
No description provided.