---
title: "Cedar policies"
url: "/docs/security/cedar-policies/"
kind: "feature"
summary: "Authorize HTTP requests with Cedar policy text evaluated in-process against the caller's JWT and the request's method, path and query."
---

> Arion site index: [/llms.txt](/llms.txt)

# Cedar policies

Authorize HTTP requests with Cedar policy text evaluated in-process against the caller's JWT and the request's method, path and query.

The Cedar policy filter evaluates [Cedar](https://docs.cedarpolicy.com/) `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.

```yaml,name=arion.yaml,arion=fragment
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 the `sub` claim of a validated JWT. Without a JWT the principal is `User::"anonymous"`. The entity type is configurable via `principal_entity_type` (default `User`).
- **Action** — `Action::"<METHOD>"`, e.g. `Action::"GET"`. The action namespace is always `Action`.
- **Resource** — `HttpPath::"<path>"`, the request path. Configurable via `resource_entity_type` (default `HttpPath`).
- **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. So `when { context.jwt.tenant == "acme" }` works if the token carries a `tenant` claim.

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](@/docs/security/jwt.md) 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:

```text
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:

```yaml,arion=fragment
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:

```bash
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 policy
```

## Limits

- `schema`, `policies` and `entities` are 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 `sub` claim only; other identity sources need [your own filter](@/docs/extend/overview.md).
- Exact matches on the resource use the raw request path; there is no path normalization or template matching.

The [Cedar documentation](https://docs.cedarpolicy.com/) covers the policy language itself — conditions, hierarchies and schema syntax.
