Adding conditions

This documentation is for an as-yet unreleased version of Cerbos. Choose 0.40.0 from the version picker at the top right or navigate to https://docs.cerbos.dev for the latest version.
The policies for this section can be found on GitHub.

In the previous section, an RBAC policy was created that allowed anyone with a user role to update a user resource - this isn’t what is intended as it would allow users to update other users' profiles.

apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "user"
  rules:
    - actions:
        - create
        - read
        - update
      effect: EFFECT_ALLOW
      roles:
        - user
# ....other conditions

This blanket approach is where using pure role-based access controls falls down as there is more nuanced required to meet the requirements.

Conditions

Cerbos is a powerful Attribute-based Access Control system that can make contextual decisions at request time whether an action can be taken.

In this scenario, Cerbforce’s business logic states that a user can only update their own user profile. To implement this a check needs to be made to ensure the ID of the user making the request matches the ID of the user resource being updated.

Conditions in Cerbos are written in Common Expression Language (CEL) which is a simple way of defining boolean logic of conditions. In this environment, there are two main bits of data provided that are of interest request.principal which is the information about the user making the request and request.resource which is the information about the resource being accessed.

The data model for each of these is as follows:

// request.principal
{
  "id": "somePrinicpalId", // the prinicpal ID
  "roles": ["user"], // the list of roles from the auth provider
  "attr": {
    // a map of attributes about the prinicpal
  }
}

// request.resource
{
  "id": "someResourceId", // the resource ID
  "attr": {
    // a map of attributes about the resourece
  }
}

Using this information a check to see if the principal ID is the same as the ID of the user resource being accessed can be defined as

request.resource.id == request.principal.id

Adding this to the policy request a new rule to be created that is just for the update and delete actions which are for the user role and has a single condition.

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "user"
  rules:
    - actions:
        - create
        - read
      effect: EFFECT_ALLOW
      roles:
        - user

    - actions:
        - update
        - delete
      effect: EFFECT_ALLOW
      roles:
        - user
      condition:
        match:
          expr: request.resource.id == request.principal.id

# ....other conditions

Complex logic can be defined in conditions (or sets of conditions) which you can read more about in the docs.

Extending tests

Now that you have a conditional policy, you can add these as test cases in the user tests. You can now define multiple user resources and principals and create test cases for ensuring the update action is allowed when the ID of the principal matches the ID of the resource, as well as checking that it isn’t allowed if the condition is not met.

---
name: UserTestSuite
description: Tests for verifying the user resource policy

principals:
  admin:
    id: admin
    roles:
      - admin

  user1:
    id: user1
    roles:
      - user

  user2:
    id: user2
    roles:
      - user

resources:
  admin:
    kind: user
    id: admin

  user1:
    kind: user
    id: user1

  user2:
    kind: user
    id: user2

tests:
  - name: User CRUD Actions
    input:
      principals:
        - admin
        - user1
        - user2

      resources:
        - admin
        - user1
        - user2

      actions:
        - create
        - read
        - update
        - delete

    expected:
      - principal: admin
        resource: admin
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_ALLOW
          delete: EFFECT_ALLOW

      - principal: admin
        resource: user1
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_ALLOW
          delete: EFFECT_ALLOW

      - principal: admin
        resource: user2
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_ALLOW
          delete: EFFECT_ALLOW

      - principal: user1
        resource: admin
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_DENY
          delete: EFFECT_DENY

      - principal: user1
        resource: user1
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_ALLOW
          delete: EFFECT_ALLOW

      - principal: user1
        resource: user2
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_DENY
          delete: EFFECT_DENY

      - principal: user2
        resource: admin
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_DENY
          delete: EFFECT_DENY

      - principal: user2
        resource: user1
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_DENY
          delete: EFFECT_DENY

      - principal: user2
        resource: user2
        actions:
          create: EFFECT_ALLOW
          read: EFFECT_ALLOW
          update: EFFECT_ALLOW
          delete: EFFECT_ALLOW