Skip to main content

Overview

Principal policies allow you to define access rules for specific individuals or service accounts. They take precedence over resource policies, enabling you to grant or deny permissions for particular users without modifying general resource rules. Use principal policies to:
  • Grant temporary elevated access (e.g., CEO can access everything)
  • Implement user-specific overrides (e.g., suspend a user’s access)
  • Create service account permissions (e.g., backup service can read all data)
  • Handle exceptions to general rules
Principal policies should be used sparingly. They can make your authorization logic harder to understand and debug. Most access control should be handled with resource policies and derived roles.

Basic Structure

---
apiVersion: api.cerbos.dev/v1
principalPolicy:
  principal: "alice"          # Username or principal ID
  version: "default"          # Policy version
  rules:                      # List of resource-action rules
    - resource: document
      actions:
        - action: "*"
          effect: EFFECT_ALLOW

Required Fields

principal
string
required
The principal identifier (user ID, service account name, etc.). Must match the id field in CheckResources requests.
version
string
required
Policy version identifier. Use "default" for the primary version.
rules
array
required
List of rules specifying what this principal can do on various resources.

Rule Structure

Principal policy rules differ from resource policy rules:
rules:
  - resource: document        # Resource kind
    actions:                  # List of action-specific rules
      - name: "edit-rule"     # Optional rule name
        action: "edit"        # Specific action
        effect: EFFECT_ALLOW  # ALLOW or DENY
        condition:            # Optional condition
          match:
            expr: request.resource.attr.department == "engineering"
resource
string
required
The resource kind this rule applies to (e.g., document, project).
actions
array
required
List of action rules. Each action rule specifies permissions for one action.

Action Rule Fields

action
string
required
The action name. Use "*" as a wildcard for all actions.
effect
string
required
Either EFFECT_ALLOW or EFFECT_DENY.
name
string
Optional identifier for debugging and audit logs.
condition
object
Optional condition that must evaluate to true. See Conditions.

Complete Example

---
apiVersion: api.cerbos.dev/v1
description: |
  Special permissions for CEO

variables:
  is_dev_record: request.resource.attr.dev_record == true
  is_public: request.resource.attr.public == true

principalPolicy:
  principal: ceo@example.com
  version: "default"
  
  # Optional: Import constants/variables
  constants:
    local:
      sensitive_threshold: 1000000
  
  rules:
    # Full access to financial records
    - resource: financial_record
      actions:
        - action: "*"
          effect: EFFECT_ALLOW
    
    # Read-only access to HR records
    - resource: hr_record
      actions:
        - name: view-hr
          action: "view"
          effect: EFFECT_ALLOW
        
        - name: deny-hr-edit
          action: "edit"
          effect: EFFECT_DENY
    
    # Conditional access to documents
    - resource: document
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
          condition:
            match:
              any:
                of:
                  - expr: V.is_public
                  - expr: request.resource.attr.owner == request.principal.id
        
        - action: "delete"
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: request.resource.attr.value < C.sensitive_threshold

How Principal Policies Work

When a request is evaluated:
  1. Check for principal policy: If one exists for the requesting principal, evaluate it first
  2. Evaluate resource policy: Always evaluate the resource policy
  3. Combine results: Principal policy decisions take precedence

Precedence Rules

1

DENY Always Wins

If either policy says DENY, the result is DENY.
2

Principal Policy ALLOW Wins

If the principal policy says ALLOW, the request is allowed even if the resource policy denies it.
3

Resource Policy as Fallback

If the principal policy doesn’t have a rule for the action, fall back to the resource policy.

Use Cases

Grant a user temporary admin access:
principalPolicy:
  principal: alice@example.com
  version: "default"
  rules:
    - resource: "*"  # All resources
      actions:
        - action: "*"  # All actions
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: |-
                now() < timestamp("2024-12-31T23:59:59Z")
Block a user from all actions:
principalPolicy:
  principal: suspended_user@example.com
  version: "default"
  rules:
    - resource: "*"
      actions:
        - action: "*"
          effect: EFFECT_DENY
Grant a backup service read-only access:
principalPolicy:
  principal: backup-service
  version: "default"
  rules:
    - resource: database
      actions:
        - action: "read"
          effect: EFFECT_ALLOW
    
    - resource: storage
      actions:
        - action: "read"
          effect: EFFECT_ALLOW
Allow a manager to access only their department’s data:
principalPolicy:
  principal: manager@example.com
  version: "default"
  rules:
    - resource: employee_record
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: request.resource.attr.department == "engineering"
        
        - action: "edit"
          effect: EFFECT_DENY  # Can't edit even in their department

Variables and Constants

Principal policies support the same variables and constants as resource policies:
principalPolicy:
  principal: alice
  version: "default"
  
  constants:
    local:
      max_value: 10000
    import:
      - global_constants
  
  variables:
    local:
      is_owned: request.resource.attr.owner == request.principal.id
      is_recent: |-
        timestamp(request.resource.attr.created_at).timeSince() < duration("24h")
    import:
      - common_vars
  
  rules:
    - resource: document
      actions:
        - action: "delete"
          effect: EFFECT_ALLOW
          condition:
            match:
              all:
                of:
                  - expr: V.is_owned
                  - expr: V.is_recent

Scoped Principal Policies

Like resource policies, principal policies support scoping:
# Base policy: principal_policies/alice.yaml
principalPolicy:
  principal: alice
  version: "default"
  rules:
    - resource: document
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
# Scoped policy: principal_policies/alice.acme.yaml
principalPolicy:
  principal: alice
  version: "default"
  scope: "acme"  # Only applies when scope=acme
  rules:
    - resource: document
      actions:
        - action: "*"  # More permissive in acme scope
          effect: EFFECT_ALLOW
Specify scope in requests:
{
  "principal": {
    "id": "alice",
    "roles": ["user"],
    "scope": "acme"
  }
}

Scope Permissions

Control inheritance from parent scopes:
principalPolicy:
  principal: alice
  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 scope rules (default)
  • SCOPE_PERMISSIONS_MERGE_PARENT - Combine with parent scope rules

Action Wildcards

Actions support wildcard matching:
rules:
  - resource: document
    actions:
      # Match all actions
      - action: "*"
        effect: EFFECT_ALLOW
      
      # Match view:public, view:private, etc.
      - action: "view:*"
        effect: EFFECT_ALLOW

Output Expressions

Principal policy actions can emit output:
rules:
  - resource: document
    actions:
      - action: "view"
        effect: EFFECT_ALLOW
        output:
          when:
            ruleActivated: |-
              {
                "principal": request.principal.id,
                "action": "view",
                "resource": request.resource.id,
                "timestamp": now()
              }
            conditionNotMet: |-
              {
                "message": "Access denied by condition"
              }
Outputs are included in API responses for audit logging or UX customization.

Multiple Versions

Maintain multiple versions of principal policies:
# principal_policies/alice_v1.yaml
principalPolicy:
  principal: alice
  version: "v1"
  rules:
    - resource: document
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
# principal_policies/alice_v2.yaml
principalPolicy:
  principal: alice
  version: "v2"
  rules:
    - resource: document
      actions:
        - action: "*"  # More permissive in v2
          effect: EFFECT_ALLOW
Specify version in requests:
{
  "principal": {
    "id": "alice",
    "roles": ["user"],
    "policyVersion": "v2"
  }
}

Best Practices

Use Sparingly

Principal policies make debugging harder. Prefer resource policies with derived roles.

Document Why

Always add a description explaining why the override is needed.

Set Expiration

For temporary access, use conditions with time-based checks.

Audit Regularly

Review principal policies periodically to remove obsolete overrides.
Consider using resource policies with derived roles for most scenarios. Reserve principal policies for true exceptions.

Testing Principal Policies

# tests/alice_test.yaml
name: AlicePrincipalPolicyTest
description: Tests for Alice's special permissions

principals:
  alice:
    id: alice
    roles: ["user"]
  
  bob:
    id: bob
    roles: ["user"]

resources:
  sensitive_doc:
    kind: document
    id: doc1
    attr:
      sensitivity: high
      owner: bob

tests:
  - name: Alice can access sensitive docs
    input:
      principals: [alice]
      resources: [sensitive_doc]
      actions: [view]
    expected:
      - principal: alice
        resource: sensitive_doc
        actions:
          view: EFFECT_ALLOW  # Via principal policy
  
  - name: Bob cannot access sensitive docs
    input:
      principals: [bob]
      resources: [sensitive_doc]
      actions: [view]
    expected:
      - principal: bob
        resource: sensitive_doc
        actions:
          view: EFFECT_DENY  # Via resource policy

Common Patterns

principalPolicy:
  principal: monitoring-service
  version: "default"
  rules:
    - resource: "*"
      actions:
        - action: "read"
          effect: EFFECT_ALLOW
        - action: "list"
          effect: EFFECT_ALLOW
        - action: "view"
          effect: EFFECT_ALLOW
principalPolicy:
  principal: emergency-admin
  version: "default"
  rules:
    - resource: "*"
      actions:
        - action: "*"
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: |-
                request.aux_data.jwt.emergency_mode == true
principalPolicy:
  principal: blocked-user
  version: "default"
  rules:
    - resource: "*"
      actions:
        - action: "*"
          effect: EFFECT_DENY
          # Allow only reading profile
    - resource: user_profile
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: request.resource.id == request.principal.id

Troubleshooting

Check that:
  • The principal field matches the principal.id in your requests exactly
  • The policy version matches (or is “default”)
  • The policy file is in the correct location and loaded by Cerbos
Enable debug logging to see which policies are evaluated:
server:
  logLevel: debug
Remember:
  • DENY in principal policy overrides ALLOW in resource policy
  • DENY in resource policy overrides ALLOW in principal policy
  • If neither policy has a rule for the action, default is DENY
Check both policies to see where the DENY comes from.
Verify:
  • Attribute paths match your request data structure
  • Variable/constant references use correct prefix (V., C.)
  • CEL expression syntax is valid
Test conditions with cerbos compile to catch errors early.

When to Use Principal Policies

  • Temporary admin/elevated access
  • User suspension or account blocking
  • Service account permissions
  • Individual exceptions to general rules
  • Emergency access overrides
If you find yourself creating many principal policies with similar rules, consider refactoring to use resource policies with derived roles instead.

Real-World Example

Here’s a complete example for a compliance officer:
---
apiVersion: api.cerbos.dev/v1
description: |
  Special permissions for compliance officer.
  Provides read-only access to sensitive data for audit purposes.

variables:
  is_audit_request: request.aux_data.purpose == "audit"
  within_business_hours: |-
    now().getHours() >= 9 && now().getHours() < 17

principalPolicy:
  principal: compliance-officer@example.com
  version: "default"
  
  constants:
    local:
      max_records_per_query: 100
  
  rules:
    # Read access to financial records during business hours
    - resource: financial_record
      actions:
        - name: audit-view
          action: "view"
          effect: EFFECT_ALLOW
          condition:
            match:
              all:
                of:
                  - expr: V.is_audit_request
                  - expr: V.within_business_hours
          output:
            when:
              ruleActivated: |-
                {
                  "audit_log": {
                    "officer": request.principal.id,
                    "resource": request.resource.id,
                    "timestamp": now(),
                    "purpose": request.aux_data.purpose
                  }
                }
        
        - name: deny-edit
          action: "edit"
          effect: EFFECT_DENY  # Read-only access
        
        - name: deny-delete
          action: "delete"
          effect: EFFECT_DENY
    
    # Read access to employee records
    - resource: employee_record
      actions:
        - action: "view"
          effect: EFFECT_ALLOW
          condition:
            match:
              expr: V.is_audit_request
        
        - action: "export"
          effect: EFFECT_DENY  # Cannot export bulk data
    
    # No access to system admin functions
    - resource: system_config
      actions:
        - action: "*"
          effect: EFFECT_DENY

Next Steps

Resource Policies

Learn about general resource rules

Derived Roles

Alternative to principal policies for many scenarios

Testing

Test principal policy precedence