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, orresource— 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:
The
idClaimconfigured in the service metadata for the target action (per-service setting; see Database configuration).The deployment-wide
PRINCIPAL_ID_CLAIM(CLI flag--principal-id-claim), which defaults tosub.The standard
subclaim, 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.subis always set to the principal id used for this request (whatever claim was selected above).All other fields from the caller’s claims / principal
infoare flattened onto the entity, so expressions likeprincipal.groups,principal.email, orprincipal.department.namework when those claims are present.
Action#
The action entity is built from the action in the request:
Type:
Action.Id:
"<service>:<name>"(for exampleAction::"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
typefrom the request (for exampleFile,document).Id: the
idfrom the request.Attributes:
idandtypeare always set (as strings), and any fields in the resource’sdataobject are flattened onto the entity — for exampleresource.pathorresource.ownerwhen 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 APIs on |
|
|
Mutating APIs on |
Resource types:
Type |
Cedar reference |
|---|---|
|
|
|
|
Example:
permit(
principal,
action == Action::"storage-service:read",
resource
)
when { resource is object || resource is folder };
Because object and folder ids are URLs, a policy can also grant access to a whole subtree at once with a hierarchy in — here every object and folder under https://main/docs/:
permit(
principal,
action == Action::"storage-service:read",
resource in folder::"https://main/docs/"
);
See Resource hierarchies for how these subtree matches work and how the folder parent type is configured.
event-consumer-service#
This service registers actions only; requests target no resource.
Actions:
Name |
Cedar reference |
APIs |
|---|---|---|
|
|
APIs that subscribe to or read the global stream of storage “object/folder created” events. |
|
|
APIs that subscribe to or read the global stream of storage “object/folder deleted” events. |
|
|
APIs that read and acknowledge messages from durable event queues. |
|
|
APIs that provision new durable event 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") };
When Permission Service runs its own built-in cross-replica cache-invalidation consumer (notifications.consumer.enabled: true — see Consuming change events for cross-replica cache invalidation), and the guarding deployment enforces authorization on the Consumer API, the Permission Service principal itself needs a permit for the event-consumer-service consume action, exactly like the event-aggregation-service:publish-event permit granted for publishing:
permit(
principal == Principal::"permission-service",
action == Action::"event-consumer-service:consume-durable-queues",
resource
);
event-aggregation-service#
Actions:
Name |
Cedar reference |
APIs |
|---|---|---|
|
|
APIs that publish an event of a given |
Resource types:
Type |
Cedar reference |
|---|---|
|
|
Example:
permit(
principal,
action == Action::"event-aggregation-service:publish-event",
resource == EventType::"storage.object.created"
);
userinfo#
Actions:
Name |
Cedar reference |
APIs |
|---|---|---|
|
|
API that lists |
|
|
API that reads a single |
|
|
API that lists the |
|
|
API that reads a single user-to-group membership entry. |
|
|
API that lists |
|
|
API that reads a single |
|
|
API that lists the members ( |
|
|
API that reads a single group-member entry. |
Resource types:
Type |
Cedar reference |
|---|---|
|
|
|
|
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
whenandunlesson attributes ofprincipal,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 (principalalone,in,is, and so on) leave principal scope unset.Action scope is set for
action == Action::"<service>:<name>", or foraction in [Action::"svc:n"]with exactly one action in the set. Otherwise action scope is unset.Resource scope is set when the head pins the resource with equality —
resource == <Type>::"<id>", where<Type>is the resource kind — or with a hierarchyinon a URL folder —resource in <Type>::"<url>/". The equality form scopes the policy to one resource; theinform scopes it to a whole URL subtree (see Resource hierarchies). Other forms (resourcealone,is, and so on) 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 via the scope patterns above (for example you write unconstrained principal, action, or resource, or you use is / multiple actions, and so on).
Two in forms are not global: principal in Group::"<id>" sets a group scope (see Groups and membership) and resource in <Type>::"<url>/" sets a resource hierarchy scope (see Resource hierarchies).
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 ifwhenevaluates to true (and anyunlessdoes 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.*— includingsuband fields from principalinfo.resource.*— includingid,type, and fields from resourcedata.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#
Group membership is a first-class policy scope. A policy head may target a group directly:
permit(principal in Group::"platform-admins", action, resource);
This is exactly equivalent to the membership condition below, and the two forms evaluate identically:
permit(principal, action, resource)
when { principal.groups.contains("platform-admins") };
At evaluation time the service builds a Group::"<id>" entity for every id in the principal’s groups claim and attaches them as parents of the principal entity, so Cedar evaluates principal in Group::"<id>" natively. The first-class form is preferred because the group scope is structured and client-parseable: it is inferred from the policy head, persisted, exposed in GET/list/diagnostics responses, and usable as a filter (GET /v1beta/policies/?group=<id>).
The claim used for membership is configurable and defaults to groups (see Helm values — openId.principalGroupsClaim / PRINCIPAL_GROUPS_CLAIM). It must be a JSON array of group id strings. Populate it (or your configured claim) in the principal payload your services send to Permission Service so Cedar can resolve the memberships.
Note
The Group::"<id>" scope here models principal group membership. It is distinct from the Group resource type queried by the userinfo service (userinfo), which represents group records being acted upon rather than the caller’s memberships.
Membership conditions on principal.groups remain fully supported and can be combined with other when/unless clauses; use them when you need richer expressions than a single group scope.
Just as principal in Group::"<id>" targets a principal by membership, resource in <Type>::"<url>/" targets a resource by its place in a URL hierarchy; see Resource hierarchies.
Resource hierarchies#
For resource types whose ids are URLs — for example storage object and folder ids such as https://main/docs/report.pdf — a policy head can target a whole subtree instead of a single resource:
permit(
principal,
action == Action::"storage-service:read",
resource in folder::"https://main/docs/"
);
resource in <Type>::"<url>/" matches the folder node itself and every resource nested under it — https://main/docs/report.pdf, https://main/docs/reports/q1.pdf, and so on. The subtree is derived from the URL path: each /-separated segment is a level in the hierarchy, and the scope covers all descendants of the given folder.
How these policies match:
The id after
inmust be a valid absolute URL and is normalized to a trailing/(the folder boundary), soresource in folder::"https://main/docs"andresource in folder::"https://main/docs/"mean the same subtree.Matching is by folder boundary, not raw string prefix:
resource in folder::"https://main/docs/"matcheshttps://main/docs/report.pdfbut not a sibling such ashttps://main/docs-archive/report.pdf.A request whose resource id is itself a folder form (ends in
/) matches inclusively — a policy scoped tofolder::"https://main/docs/"applies to a request on exactlyhttps://main/docs/.<Type>is the parent type of the resources you want to match (see below), not necessarily the request resource’s own type.
Configuring parent relationships#
The <Type> written in a resource in <Type>::"<url>/" head is the resource type’s parent type. Each registered resource type may declare a parent type in its service metadata; when a request targets a resource of that type, the service treats the URL folders above it as entities of that parent type, so resource in <parentType>::"<url>/" matches those containing folders natively.
For example, storage object resources declare folder as their parent type. A policy scoped to folder::"<url>/" therefore matches both objects and folders nested under that URL. When no parent type is configured, a resource’s folders are typed with the resource’s own type instead.
Parent type is configured alongside the resource type in service metadata; see Parent type for resource hierarchies.
See also#
Official Cedar documentation — language reference, schema, and best practices
How authorization requests are evaluated — how policies, scopes,
order, and resource evaluation priority combine at request timeDatabase configuration — policy file format, scope inference details, and examples
Deployment — configuring initial policies in Helm