The Cedar policy filter evaluates Cedar permit/forbid statements against each request, in-process, and rejects anything no policy permits. Use it when authorization rules involve who the caller is — not just which header or path — and you want those rules as reviewable policy text rather than code.
Enable it
Add the filter to http_filters before the router, with two inline strings: a Cedar schema (entity types, actions and the context shape) and the policies themselves.
http_filters:
- name: arion.filters.http.cedar
typed_config:
"@type": type.googleapis.com/arion.extensions.filters.http.cedar.cedar_policy.v3.CedarPolicy
schema: |
entity User;
entity HttpPath;
action "GET" appliesTo {
principal: [User],
resource: [HttpPath],
context: { http: { method: String, path: String, query?: String } }
};
policies: |
permit (
principal == User::"anonymous",
action == Action::"GET",
resource == HttpPath::"/health"
);
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Cedar denies by default: the example permits exactly GET /health for unauthenticated callers and every other request gets 403 with body Access denied by policy. Policies and schema are validated at startup, so a typo refuses the config rather than failing requests later.
What a policy sees
Arion maps every request onto a Cedar authorization query:
- Principal —
User::"<sub>", taken from thesubclaim of a validated JWT. Without a JWT the principal isUser::"anonymous". The entity type is configurable viaprincipal_entity_type(defaultUser). - Action —
Action::"<METHOD>", e.g.Action::"GET". The action namespace is alwaysAction. - Resource —
HttpPath::"<path>", the request path. Configurable viaresource_entity_type(defaultHttpPath). - Context — a record with
http: { method, path, query? }and, when a JWT is present,jwt: { sub, iss, aud, exp, iat, nbf, jti }plus any custom claims. Sowhen { context.jwt.tenant == "acme" }works if the token carries atenantclaim.
Declare in the schema exactly the context fields you use; the shapes above are what the filter injects.
Use the JWT principal
Place the filter after JWT authentication and set payload_in_metadata on the JWT provider — that is what hands the validated claims to Cedar. A policy can then name real subjects:
permit (principal == User::"svc-frontend", action == Action::"GET", resource);
If the claims are absent (no JWT filter, or the provider does not set payload_in_metadata), every request evaluates as User::"anonymous".
Modes and entities
Two knobs control behaviour, both defaulting to the strict side:
enforcement_mode: 1 # LOG_ONLY — log denials, let requests through (0 = ENFORCE)
failure_mode: 1 # FAIL_OPEN — pass on evaluation errors (0 = FAIL_CLOSED)
entities accepts a JSON entity list for hierarchies (groups, roles) your policies reference, and validate_schema_per_request: true re-validates each query against the schema at request time.
Verify
With the config above:
curl -i -X GET http://127.0.0.1:8080/health # 200
curl -i -X POST http://127.0.0.1:8080/health # 403 Access denied by policyLimits
schema,policiesandentitiesare inline strings in the filter config; loading them from a file or over xDS is not supported.- No per-route override — one policy set applies to the whole filter chain.
- The principal comes from the JWT
subclaim only; other identity sources need your own filter. - Exact matches on the resource use the raw request path; there is no path normalization or template matching.
The Cedar documentation covers the policy language itself — conditions, hierarchies and schema syntax.