Flow
Decisions

0007 — Cedar for authorization

Authorization is evaluated in-process from Cedar policy files.

Decided. Each service authorizes its own requests using Cedar policies, held as .cedar files, compiled at startup, evaluated in process. The edge authenticates; the service authorizes.

Superseded in part by 0013. Authentication (JWT validation) moves into each service; Cedar evaluation in process is unchanged.

Superseded in part by 0047 (Ory Kratos and Hydra for authentication; identity stays first-party, docs/decisions/0047-ory-for-authentication-identity-stays-first-party.md; not yet mirrored here). The local copy is principals, principal_grants (explicit scoped grants), and endpoint_access, not principal_roles and principal_groups. Which roles may call an action moves from policy files into endpoint_access, so that part no longer needs a release; the action and its scoping still do. Evaluation in process is unchanged.

Alternatives

  • Hand-written checks in Go. Simpler to read, worse to audit, and impossible to present as a coherent list of who may do what.
  • Authorization at the gateway. Puts domain knowledge in the wrong place and makes every service reachable-and-unprotected from inside.
  • OPA / Rego. Comparable capability; more operational surface if run as a sidecar, and a less pleasant language for this shape of rule.

Why

Cedar policies are data with a defined evaluation semantics: default deny, explicit permits, forbid overriding, and a diagnostic explaining which policy decided. It costs no infrastructure — no sidecar, no network call — and it already exists in the legacy system, so the policies and the team's familiarity carry over.

The decisive property for our purposes: because policies are parseable files, the generator can extract them and put the required action and its condition into the OpenAPI document, the handler comment, and a generated authorization matrix. An engineer sees what an endpoint requires without reading handler code.

Why local evaluation with replicated identity is not an accident

This is a well-established pattern: a local Policy Decision Point (PDP) with a replicated identity store. The alternative — calling a central authorization service per request — makes that service a hard dependency of every service on every request: its latency adds to everything, and its downtime takes the whole platform down with it.

Cedar was designed specifically for this model. The Cedar engine evaluates policies in-process against a local entity store. The design expectation is that you bring identity data to the evaluator, not the other way around. AWS built Cedar for exactly this topology.

In practice: each service keeps a local copy of principals, principal_roles, and principal_groups, refreshed from the identity service every ~60s in a single replacing transaction. Authorization is a local SQL read in the same transaction as the command — no network hop, no new failure mode, no window where a disabled principal slips through between the auth check and the write.

The cost is revocation lag bounded to the refresh interval. That is a deliberate, named tradeoff, not an oversight.

How it is wired

  • The entity spec names a rule (a Cedar policy id) per command and query. The action name is conventional: <service>::<Command>.
  • authz: none is allowed but requires a reason:.
  • Policies carry @id and @doc annotations; both end up in the docs.
  • The generator reads resource.* references out of the policy condition and emits the handler code that supplies those attributes.

Consequences

  • Adding people, buildings, and group memberships is data, no release. Adding a new command or a new kind of rule needs a release, because the action must be listed in a policy.
  • Ownership rules need attribute-bearing resource entities, not a bare type and id — CheckWithResource rather than Check. It arrives with the first scoping rule; the chart of accounts only needs role-based checks.
  • Policy files are a reviewable security surface. Every policy needs at least one allow and one deny test.

On this page