Overview
Resource policies define who can do what on a specific type of resource in your application. They are the primary way to specify authorization rules in Cerbos.
A resource policy:
Applies to a resource kind (e.g., document, project, leave_request)
Contains rules that match actions to roles/derived roles
Can include conditions for attribute-based access control
Supports versioning for gradual rollouts and backwards compatibility
Basic Structure
---
apiVersion : api.cerbos.dev/v1
resourcePolicy :
resource : "document" # Resource kind
version : "default" # Policy version
rules : # List of authorization rules
- actions : [ "view" , "edit" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
Required Fields
The resource kind this policy applies to. Should match the kind field in your CheckResources API requests.
Policy version identifier. Use "default" for the primary version, or semantic versions like "v1", "2024-01" for alternatives.
List of rules defining access permissions. At least one rule is required.
Writing Rules
Each rule specifies which actions are allowed or denied for certain roles.
Basic Rule Structure
rules :
- actions : [ "view" , "create" , "edit" ]
effect : EFFECT_ALLOW
roles : [ "user" , "employee" ]
name : "user-basic-access" # Optional but recommended
List of action names this rule applies to. Use ["*"] as a wildcard for all actions. Supports wildcards like "view:*" to match view:public, view:private, etc.
Either EFFECT_ALLOW or EFFECT_DENY. DENY always takes precedence over ALLOW.
Static roles from the principal’s identity. Use ["*"] to match any role.
Dynamic roles computed at runtime. Must be defined in an imported derived roles policy.
Optional identifier for the rule. Helpful for debugging and audit logs.
Optional condition expression that must evaluate to true. See Conditions .
Each rule must specify either roles or derivedRoles (or both).
Complete Example
Here’s a full resource policy for a leave request system:
---
apiVersion : api.cerbos.dev/v1
description : |
Access control rules for leave request resources
variables :
pending_approval : '"PENDING_APPROVAL"'
principal_location : | -
(P.attr.ip_address.inIPAddrRange("10.20.0.0/16") ? "GB" : "")
resourcePolicy :
resource : leave_request
version : "20210210"
# Import derived roles definitions
importDerivedRoles :
- common_roles
# JSON schema validation
schemas :
principalSchema :
ref : cerbos:///principal.json
resourceSchema :
ref : cerbos:///resources/leave_request.json
rules :
# Admins can do everything
- actions : [ "*" ]
effect : EFFECT_ALLOW
roles : [ "admin" ]
name : admin-full-access
# Employees can create leave requests
- actions : [ "create" ]
effect : EFFECT_ALLOW
derivedRoles : [ "employee_that_owns_the_record" ]
# Owners and managers can view
- actions : [ "view:*" ]
effect : EFFECT_ALLOW
derivedRoles :
- employee_that_owns_the_record
- direct_manager
# Public view for any employee
- actions : [ "view:public" ]
effect : EFFECT_ALLOW
derivedRoles : [ "any_employee" ]
name : public-view
# Managers can approve pending requests
- actions : [ "approve" ]
effect : EFFECT_ALLOW
derivedRoles : [ "direct_manager" ]
condition :
match :
expr : request.resource.attr.status == V.pending_approval
# Managers can delete in their geography
- actions : [ "delete" ]
effect : EFFECT_ALLOW
derivedRoles : [ "direct_manager" ]
condition :
match :
expr : request.resource.attr.geography == variables.principal_location
Importing Derived Roles
Resource policies can reference derived roles defined in separate policy files:
resourcePolicy :
resource : document
version : "default"
importDerivedRoles :
- common_roles # Imports from derived_roles/common_roles.yaml
- admin_roles # Imports from derived_roles/admin_roles.yaml
rules :
- actions : [ "*" ]
effect : EFFECT_ALLOW
derivedRoles : [ "owner" ] # Must be defined in imported files
Referenced derived roles must exist in the imported files, otherwise compilation will fail.
Using Conditions
Conditions add attribute-based access control to your rules:
Simple Condition
Multiple Conditions (ALL)
Multiple Conditions (ANY)
rules :
- actions : [ "edit" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
condition :
match :
expr : request.resource.attr.owner == request.principal.id
See Conditions for the complete reference.
Variables and Constants
Top-Level Variables
Define reusable expressions at the top of your policy:
---
apiVersion : api.cerbos.dev/v1
variables :
pending_status : '"PENDING_APPROVAL"'
is_owner : request.resource.attr.owner == request.principal.id
is_weekend : now().getDayOfWeek() > 5
resourcePolicy :
resource : document
version : "default"
rules :
- actions : [ "edit" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
condition :
match :
expr : V.is_owner # Reference with V. prefix
Importing Constants and Variables
Share constants across policies:
resourcePolicy :
resource : document
version : "default"
constants :
import :
- app_constants # From export_constants/app_constants.yaml
local :
max_size : 1000
variables :
import :
- common_vars # From export_variables/common_vars.yaml
local :
is_large : request.resource.attr.size > C.max_size
rules :
# Use C.constant_name or V.variable_name
Schema Validation
Attach JSON schemas to validate principal and resource attributes:
resourcePolicy :
resource : leave_request
version : "default"
schemas :
principalSchema :
ref : cerbos:///principal.json
resourceSchema :
ref : cerbos:///resources/leave_request.json
rules :
# Schemas are validated before rules are evaluated
Schema references use the format:
cerbos:///schema.json - Load from configured schema source
file:///path/to/schema.json - Load from file system
https://example.com/schema.json - Load from URL
Scoped Policies
Create scope-specific variations of resource policies:
# Base policy: resource_policies/document.yaml
apiVersion : api.cerbos.dev/v1
resourcePolicy :
resource : document
version : "default"
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
# Scoped policy: resource_policies/document.acme.yaml
apiVersion : api.cerbos.dev/v1
resourcePolicy :
resource : document
version : "default"
scope : "acme" # Only applies to scope=acme
rules :
- actions : [ "view" , "edit" ] # More permissive
effect : EFFECT_ALLOW
roles : [ "user" ]
When a request includes scope: "acme", Cerbos will use the scoped policy.
Scope Permissions
Control how scoped policies interact with parent scopes:
resourcePolicy :
resource : document
version : "default"
scope : "acme.corp.engineering"
scopePermissions : SCOPE_PERMISSIONS_OVERRIDE_PARENT # Default
rules :
# These rules replace parent scope rules
Options:
SCOPE_PERMISSIONS_OVERRIDE_PARENT - Replace parent rules (default)
SCOPE_PERMISSIONS_MERGE_PARENT - Combine with parent rules
Action Wildcards
Actions support wildcard matching:
rules :
# Match all actions
- actions : [ "*" ]
effect : EFFECT_ALLOW
roles : [ "admin" ]
# Match view:public, view:private, view:internal, etc.
- actions : [ "view:*" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
# Combine wildcards with specific actions
- actions : [ "read:*" , "list" , "create" ]
effect : EFFECT_ALLOW
roles : [ "contributor" ]
Output Expressions
Rules can emit structured output when they match:
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
output :
when :
ruleActivated : | -
{
"message": "User viewed resource",
"timestamp": now(),
"resource_id": request.resource.id
}
conditionNotMet : | -
{
"message": "Access denied by condition"
}
Outputs are included in the API response for audit logging or UX customization.
Multiple Policy Versions
Maintain multiple versions of a policy:
# resource_policies/document_v1.yaml
resourcePolicy :
resource : document
version : "v1"
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
# resource_policies/document_v2.yaml
resourcePolicy :
resource : document
version : "v2"
rules :
- actions : [ "view" , "edit" ] # More permissive in v2
effect : EFFECT_ALLOW
roles : [ "user" ]
Clients specify the version in CheckResources requests:
{
"resource" : {
"kind" : "document" ,
"id" : "123" ,
"policyVersion" : "v2"
}
}
Best Practices
Use Descriptive Names Name your rules for easier debugging: name: "manager-approval-pending-only"
Start Restrictive Begin with minimal permissions and add rules as needed, rather than starting permissive.
Extract Derived Roles If multiple policies check owner == principal.id, create an owner derived role instead.
Use Variables Define complex conditions as variables for reusability and clarity.
Order matters for readability but not for evaluation. Cerbos evaluates all matching rules regardless of order.
Testing Resource Policies
Always write tests for your policies:
# tests/document_test.yaml
name : DocumentTestSuite
description : Tests for document resource policy
principals :
alice :
id : alice
roles : [ "user" ]
attr :
department : engineering
resources :
public_doc :
kind : document
id : doc1
attr :
owner : bob
public : true
tests :
- name : Public documents viewable by all
input :
principals : [ alice ]
resources : [ public_doc ]
actions : [ view ]
expected :
- principal : alice
resource : public_doc
actions :
view : EFFECT_ALLOW
Run tests with:
cerbos compile --tests=tests /path/to/policies
Learn more in Policy Testing .
Common Patterns
rules :
- actions : [ "edit" , "delete" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
condition :
match :
expr : request.resource.attr.owner == request.principal.id
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
roles : [ "employee" ]
condition :
match :
expr : request.resource.attr.department == request.principal.attr.department
rules :
- actions : [ "approve" ]
effect : EFFECT_ALLOW
roles : [ "manager" ]
condition :
match :
all :
of :
- expr : request.resource.attr.status == "PENDING"
- expr : request.resource.attr.amount < 10000
rules :
- actions : [ "edit" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
condition :
match :
expr : | -
timestamp(request.resource.attr.created_at).timeSince() < duration("24h")
Troubleshooting
Error: Rule does not specify any roles or derived roles
Every rule needs either roles or derivedRoles (or both): # Wrong
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
# Correct
rules :
- actions : [ "view" ]
effect : EFFECT_ALLOW
roles : [ "user" ]
Error: Derived role 'X' is not defined in any imports
Make sure the derived role is defined and imported: resourcePolicy :
importDerivedRoles :
- common_roles # Must contain the referenced role
rules :
- actions : [ "edit" ]
derivedRoles : [ "owner" ] # Must be in common_roles
My condition isn't working
Check:
Condition expressions use CEL syntax
Variable references use correct prefix (V. for variables, C. for constants)
Attribute paths match your request data (e.g., request.resource.attr.owner)
Test conditions with cerbos compile to catch syntax errors.
Next Steps
Derived Roles Create dynamic roles based on context
Conditions Master CEL expressions for complex rules
Testing Write comprehensive policy tests