Validating and testing policies

Validating policies

You can use the Cerbos compiler to make sure that your policies are valid before pushing them to a production Cerbos instance. We recommend setting up a git hook or a CI step to run the Cerbos compiler before you push any policy changes to production.

docker run -i -t -v /path/to/policy/dir:/policies ghcr.io/cerbos/cerbos:0.9.0 compile /policies

Example: GitHub Actions workflow

  • Create a directory named .github/actions/cerbos at the root of your repository

  • Create a file named .github/actions/cerbos/actions.yaml containing the following:

    ---
    name: 'cerbos'
    description: 'Cerbos compile'
    inputs:
      policyDir:
        description: 'Policy directory'
        required: true
    runs:
      using: 'docker'
      image: 'docker://ghcr.io/cerbos/cerbos:0.9.0'
      args:
        - 'compile'
        - ${{ inputs.policyDir }}
  • Now you can add a job to your workflow definition that references the action created above. For example, the following workflow runs the Cerbos compile step on every pull request:

    ---
    name: PR Check
    on:
      pull_request:
        branches:
          - main
    jobs:
      cerbosCheck:
        name: Check Cerbos policies
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v2
    
          - name: Run Cerbos compile
            uses: ./.github/actions/cerbos
            id: cerbos
            with:
              policyDir: cerbos/policies
See the Cerbos REST demo respoisitory for an example of a similar GitHub Action being used to check the policies on each pull request.

Testing policies

You can write optional tests for policies and run them as part of the compilation stage to make sure that the policies do exactly what you expect.

Tests are defined using the familiar YAML format as well. Make sure that your tests are in a separate directory from the policies to avoid confusion. We recommend storing them in a top-level directory named tests. A test file must have _test suffix in the name and one of the following file extensions: 'yaml', 'yml', or 'json'. For example, album_test.yml, album_test.yaml or album_test.json.

Test suite definition
---
name: AlbumObjectTestSuite (1)
description: Tests for verifying the album:object resource policy (2)
resources: (3)
  alicia_album:
    kind: "album:object",
    attr:
      owner: aliciaID,
      id: XX125,
      public: false,
      flagged: false
principals: (4)
  bradley:
    id: bradleyID
    roles:
      - user
  alicia:
    id: aliciaID
    roles:
      - user
tests: (5)
  - name: Alicia tries to view her own private album (6)
    input: (7)
      requestId: "test"
      actions: ["view", "delete"]
      resource: "alicia_album"
    expected: (8)
      - principal: alicia
        actions:
          view: EFFECT_ALLOW
          delete: EFFECT_ALLOW
      - principal: bradley
        actions:
          view: EFFECT_DENY
          delete: EFFECT_DENY
1 Name of the test suite
2 Description of the test suite
3 Map of resources. A key is a string that can be used to refer to the associated resource
4 Map of principals. A key is a string that can be used to refer to the associated principal
5 List of tests in this suite
6 Name of the test
7 Input to the policy engine
8 List of outcomes expected for a specified principal and a given action.

Sharing test fixtures

It is possible to share principals and resources between test suites stored in the same directory. Create a testdata directory in the directory containing your test suite files, then define shared resources and principals in testdata\resources.yml and testdata\principals.yml respectively (yaml and json extensions are also supported).

tests
├── album_object_test.yaml
├── gallery_object_test.yaml
├── slideshow_object_test.yaml
└── testdata
   ├── principals.yaml
   └── resources.yaml
An example of testdata\principals.yml
---
principals:
  john:
    id: johnID
    roles:
      - user
      - moderator
An example of testdata\resources.yml
---
resources:
  alicia_album:
    kind: "album:object",
    attr:
      owner: aliciaID,
      id: XX125,
      public: false,
      flagged: false

YAML anchors and overrides are a great way to reduce repetition and reuse definitions in test cases.

For example, the following definitions are equivalent:

Without anchors and overrides With anchors and overrides
resources:
  alicias_album1:
    kind: "album:object"
    attr:
      owner: "alicia"
      id: "XX125"
      public: false
      flagged: false

  alicias_album2:
    kind: "album:object"
    attr:
      owner: "alicia"
      id: "XX525"
      public: false
      flagged: false

  alicias_album3:
    kind: "album:object"
    attr:
      owner: "alicia"
      id: "XX925"
      public: false
      flagged: false
resources:
  alicias_album1:
    kind: "album:object"
    attr: &alicia_album_attr
      owner: "alicia"
      id: "XX125"
      public: false
      flagged: false

  alicias_album2:
    kind: "album:object"
    attr:
      <<: *alicia_album_attr
      id: "XX525"

  alicias_album3:
    kind: "album:object"
    attr:
      <<: *alicia_album_attr
      id: "XX925"

Running tests

To run the tests, provide the path to the tests directory using the --tests flag.

docker run -i -t \
    -v /path/to/policy/dir:/policies \
    -v /path/to/test/dir:/tests \
    ghcr.io/cerbos/cerbos:0.9.0 compile --tests=/tests /policies

Machine readable output can be produced by passing --format=json flag to the command.

By default, all discovered tests are run. To run just some of the tests, provide a regular expression that matches the test using the --run flag.

Example: Running only tests that contain 'Delete' in the name
docker run -i -t \
    -v /path/to/policy/dir:/policies \
    -v /path/to/test/dir:/tests \
    ghcr.io/cerbos/cerbos:0.9.0 compile --tests=/tests --run=Delete /policies

You can also skip entire suites or individual tests in a suite by adding skip: true to the test definition.

Example: Skipping a test
---
name: AlbumObjectTestSuite
description: Tests for verifying the album:object resource policy
tests:
  - name: View private album
    skip: true
    skipReason: "Policy under review"
    input:
      requestId: "test01"
      actions: ["view"]
      resource: alicia_private_album
    expected:
      - principal: alicia
        actions:
          view: EFFECT_ALLOW