Architecture Decision Record

0021. The admin / management API surface and its authorization

Context

Everything Vord does for relying parties — issue tokens, decide authorization — presupposes that someone first created a tenant, registered a client, defined a realm's policy, and set up the relationship graph. That control surface is itself a first-class part of the system, and two questions about it earn an ADR: its shape (and whether a UI gates it) and, more interestingly, what authorizes it. An identity system that authorizes its own administration with a second, bespoke mechanism has two authorization systems to get right; one that uses its own is simpler and is the strongest possible test of its own engine.

Decision

  1. API-first, mirroring Wyrd ADR-0013. The management surface is a gRPC/REST API: tenant and realm CRUD (ADR-0012), OAuth client registration (ADR-0014), directory administration (ADR-0011), SCIM ingestion (ADR-0018), Forseti relationship and policy management (ADR-0006), and key-lifecycle operations (ADR-0023). A thin CLI wraps the API; a web admin UI is deferred (cheap to add later, carries no architectural risk — the Wyrd ADR-0013 posture).

  2. Vord administers itself with its own Forseti (ADR-0006). Management operations are authorized by Vord's own relationship/policy engine — the same Authorizer that serves relying parties. Administration is just another resource in the graph ("who may manage realm R of tenant T"), so the differentiator dogfoods itself and there is exactly one authorization system to harden.

    Forseti lands at M3, but the admin surface exists from M0 — the interim is named, not improvised: from M0 the admin API is authorized by a static realm-admin policy behind the same Authorizer seam (ADR-0006, ADR-0013) — a fixed role table, no relation graph — swapped for the Forseti-backed implementation at M3 with no caller-visible change, because callers only ever see the trait. The interim policy is deliberately coarse (realm admin or not); fine-grained delegation waits for the real engine.

  3. The bootstrap is a break-glass principal. The circularity — the engine that authorizes access to its own configuration cannot authorize the first configuration — is resolved by a narrowly-scoped, audited break-glass bootstrap principal that can establish the initial tenant and admin grants, after which Forseti governs. The break-glass path is itself an audited event (ADR-0022), and its disablement after bootstrap is a tested property (ADR-0028's abuse cases), not a convention.

  4. A minimal slice is v1. The walking skeleton needs only enough management to create one tenant and register one client so the authorization-code flow can run (the M0 surface). The full management API widens in later milestones; it is API-first from the start so the CLI and the deferred UI are pure clients.

Consequences

  • One authorization engine secures both the product surface and its own administration; a bug class (a second, weaker admin-authz path) is designed out.
  • API-first means the CLI, the future UI, and automation (Terraform-style provisioning) are all equal clients of one contract — the operability posture Wyrd ADR-0013 established, inherited here.
  • Self-administration via Forseti is a continuous, real-world exercise of the authorization engine and its consistency-token semantics (ADR-0004), surfacing problems in Vord's differentiator before relying parties hit them.
  • Management actions are events on the identity event stream (ADR-0022), so the audit trail of "who changed what" is a projection of that stream, not a bolted-on log. Honestly stated: the events are emitted from M0, but the queryable audit projection arrives with the stream milestone (M5) — until then the trail exists in the durable event record without a query surface.
  • Protocol-grade rate limiting on the admin surface (per-caller, per-tenant) is owned by the resource-exhaustion umbrella (proposal 0006), as on the OAuth endpoints (ADR-0014).