Writing Cedar policies for Permission Service#

Permission Service evaluates access using the Cedar policy language. Policies are plain Cedar text: each stored policy is exactly one Cedar statement (permit or forbid). You add them through Helm database.init.policies, a config-file policy YAML, or the REST/gRPC policy APIs (see Database configuration and Deployment).

This page is a practical introduction for authors who are new to Cedar syntax. For the full language specification, grammar, and advanced topics, see the official Cedar documentation.

Cedar syntax overview#

A Cedar policy says who may or may not do what, optionally refined by extra conditions.

  • permit — if the policy applies and its conditions hold, the request may be allowed (subject to other policies).

  • forbid — if the policy applies and its conditions hold, the request is denied. Forbid overrides permit by default when both match.

The core shape is:

permit(
  <principal constraint>,
  <action constraint>,
  <resource constraint>
);

or the same with forbid. Each constraint can be:

  • The bare name principal, action, or resource — meaning “any” for that slot in the policy head.

  • An equality such as principal == Principal::"alice" — only that entity.

  • Other Cedar constraints (in, is, lists, and so on).

You can append Boolean conditions:

  • when { ... } — the policy applies only when the expression is true.

  • unless { ... } — the policy does not apply when the expression is true.

Examples (patterns used with this service):

// Allow one principal, one action, one resource (all three scoped by equality)
permit(
  principal == Principal::"alice",
  action == Action::"my-service:read",
  resource == document::"doc-1"
);
// Allow any principal for one action; narrow further in "when"
permit(
  principal,
  action == Action::"my-service:read",
  resource
)
when { resource.id like "/public/*" };
// Deny by default path, except for a break-glass principal attribute
forbid(principal, action, resource)
unless { principal.breakglass == true };

Cedar has more operators and types (sets, records, in, is, and so on). Use the official Cedar documentation for complete syntax and semantics.

What principal, action, and resource refer to in a policy#

A policy is evaluated against a single authorization request, and inside the policy the names principal, action, and resource refer to Cedar entities built from that request. Writing useful policies requires knowing what those entities look like in this service.

Principal#

The principal entity id is not hard-coded to the JWT sub claim. For each request the service picks the id from the caller’s claims in this order:

  1. The idClaim configured in the service metadata for the target action (per-service setting; see Database configuration).

  2. The deployment-wide PRINCIPAL_ID_CLAIM (CLI flag --principal-id-claim), which defaults to sub.

  3. The standard sub claim, as a final fallback.

The first of these that is present in the caller’s claims becomes Principal::"<that value>" for the request. Different services in the same deployment can therefore identify the same user by different ids (for example, email for one service and sub for another).

The principal entity also exposes attributes Cedar conditions can read:

  • principal.sub is always set to the principal id used for this request (whatever claim was selected above).

  • All other fields from the caller’s claims / principal info are flattened onto the entity, so expressions like principal.groups, principal.email, or principal.department.name work when those claims are present.

Action#

The action entity is built from the action in the request:

  • Type: Action.

  • Id: "<service>:<name>" (for example Action::"storage:read").

  • No attributes; policies match actions in the policy head, not via action.*.

Resource#

When the request includes a resource, the service builds a resource entity:

  • Type: the type from the request (for example File, document).

  • Id: the id from the request.

  • Attributes: id and type are always set (as strings), and any fields in the resource’s data object are flattened onto the entity — for example resource.path or resource.owner when those fields are sent.

Resource ids are normalized/encoded consistently in policies and requests (see Database configuration); use the same string form your clients send on the API.

If a request has no resource, policies must not constrain resource by equality or reference resource.* in conditions.

Actions and resources for Omniverse services#

Actions and resource types available in policies are defined by each deployment’s service metadata. The tables below list the actions and resource types registered for the standard Omniverse services in a reference (development) deployment; your environment may register a different set.

Use these values directly in the policy head — Action::"<service>:<name>" for actions and <resource type>::"<id>" for resources.

storage-service#

Actions:

Name

Cedar reference

APIs

read

Action::"storage-service:read"

Read APIs on object and folder — retrieving object content, listing folder contents, and reading object/folder metadata.

write

Action::"storage-service:write"

Mutating APIs on object and folder — creating, updating, moving, and deleting objects and folders.

Resource types:

Type

Cedar reference

object

object::"<id>"

folder

folder::"<id>"

Example:

permit(
  principal,
  action == Action::"storage-service:read",
  resource
)
when { resource is object || resource is folder };

event-consumer-service#

This service registers actions only; requests target no resource.

Actions:

Name

Cedar reference

APIs

consume-all-storage-create-events

Action::"event-consumer-service:consume-all-storage-create-events"

APIs that subscribe to or read the global stream of storage “object/folder created” events.

consume-all-storage-delete-events

Action::"event-consumer-service:consume-all-storage-delete-events"

APIs that subscribe to or read the global stream of storage “object/folder deleted” events.

consume-durable-queues

Action::"event-consumer-service:consume-durable-queues"

APIs that read and acknowledge messages from durable event queues.

create-durable-queues

Action::"event-consumer-service:create-durable-queues"

APIs that provision new durable event queues.

delete-durable-queues

Action::"event-consumer-service:delete-durable-queues"

APIs that remove durable event queues.

Resource types: none.

Because no resource is attached to these requests, write policies with an unconstrained resource slot and do not reference resource.* in conditions:

permit(
  principal,
  action in [
    Action::"event-consumer-service:consume-durable-queues",
    Action::"event-consumer-service:create-durable-queues",
    Action::"event-consumer-service:delete-durable-queues"
  ],
  resource
)
when { principal.groups.contains("event-consumers") };

event-aggregation-service#

Actions:

Name

Cedar reference

APIs

publish-event

Action::"event-aggregation-service:publish-event"

APIs that publish an event of a given EventType to the aggregation service.

Resource types:

Type

Cedar reference

EventType

EventType::"<id>"

Example:

permit(
  principal,
  action == Action::"event-aggregation-service:publish-event",
  resource == EventType::"storage.object.created"
);

userinfo#

Actions:

Name

Cedar reference

APIs

list-users

Action::"userinfo:list-users"

API that lists User records.

get-user

Action::"userinfo:get-user"

API that reads a single User record.

list-user-groups

Action::"userinfo:list-user-groups"

API that lists the Groups a given user belongs to.

get-user-group

Action::"userinfo:get-user-group"

API that reads a single user-to-group membership entry.

list-groups

Action::"userinfo:list-groups"

API that lists Group records.

get-group

Action::"userinfo:get-group"

API that reads a single Group record.

list-group-members

Action::"userinfo:list-group-members"

API that lists the members (Users) of a given group.

get-group-member

Action::"userinfo:get-group-member"

API that reads a single group-member entry.

Resource types:

Type

Cedar reference

User

User::"<id>"

Group

Group::"<id>"

The User and Group resource types here are the resources being queried by userinfo actions — they are not Cedar group-membership entities. See Groups and membership for how to model role or group checks on the principal.

Example:

permit(
  principal,
  action in [
    Action::"userinfo:list-users",
    Action::"userinfo:get-user"
  ],
  resource
)
when { resource is User };

What policies you can add#

You can add permit policies (allow paths) and forbid policies (explicit deny). Together they express your authorization model; Cedar’s default for a request is deny if no permit applies, and forbid can override permit when both match.

Policies may:

  • Constrain principal, action, and/or resource in the policy head (equality, wildcards via unconstrained slots, when / unless, and so on).

  • Use when and unless on attributes of principal, resource, and on context passed in the authorization request (see below).

Operational rules for this service:

  • Each policy record must contain exactly one Cedar statement. Multiple statements in one stored policy are rejected.

  • Service metadata (registered actions, resource types, evaluation order) drives request validation and how policies are combined at runtime; policies should align with the actions and resource types your deployment registers.

Policy scopes (principal, action, resource)#

The service attaches up to three scopes to each stored policy: optional principal, action, and resource scope. Scopes are used to retrieve relevant policies efficiently: on each authorization request, policies whose scopes match (or are unset for that dimension) are candidates for evaluation.

How scopes are inferred from the Cedar text (summary):

  • Principal scope is set only when the head uses equality on the principal: principal == Principal::"<id>". Other forms (principal alone, in, is, and so on) leave principal scope unset.

  • Action scope is set for action == Action::"<service>:<name>", or for action in [Action::"svc:n"] with exactly one action in the set. Otherwise action scope is unset.

  • Resource scope is set only when the head uses equality on the resource: resource == <Type>::"<id>" where <Type> is the resource kind. Other forms leave resource scope unset.

If a scope is unset for a dimension, any request value on that dimension can still match this policy for retrieval purposes. The full Cedar text (including when / unless) still determines the final allow/deny decision.

Global and unscoped policies#

A policy is global along a dimension when the service does not infer a scope for that dimension from the policy head. That happens when the Cedar head does not pin that slot to a single value via the strong equality patterns above (for example you write unconstrained principal, action, or resource, or you use in / is / multiple actions, and so on).

A policy that leaves all three scopes unset is fully global: it is a candidate for every authorization request. A common example is:

permit(principal, action, resource);

Fully global policies are useful for “open” test environments; in production you usually combine narrower scoped policies with explicit forbid rules and when / unless conditions.

when and unless conditions#

Use when { expression } to require extra facts for the policy to apply. Use unless { expression } to suppress the policy when the expression is true.

  • when — if the head matches the request’s entities, the policy contributes allow/deny only if when evaluates to true (and any unless does not block it).

  • unless — if the expression is true, the policy does not apply for that request.

You can combine them on the same policy. Expressions may reference:

  • principal.* — including sub and fields from principal info.

  • resource.* — including id, type, and fields from resource data.

  • Context — attributes from the optional context object on the authorization request, if your deployment supplies schema-valid context Cedar can see (see Cedar’s context documentation in the official guide).

Examples:

permit(principal, action == Action::"docs:read", resource)
when { principal.sub like "svc-*" && resource.type == "document" };
forbid(principal, action, resource)
when { context.ipRange == "10.0.0.0/8" }
unless { principal.mfa == true };

Groups and membership#

Permission Service does not load a graph of separate Cedar entities for user groups. The principal entity has no parents and there are no Group::"..." entities supplied by the service.

You cannot rely on Cedar patterns such as:

// NOT supported — no Group entities are provided
principal in Group::"admins"

Do model group membership (or roles) using attributes on the principal, typically from token claims or principal.info. Example:

permit(principal, action == Action::"admin:configure", resource)
when { principal.groups.contains("platform-admins") };

Populate groups (or your own claim names) in the principal payload your services send to Permission Service so Cedar can evaluate these conditions.

See also#