Quick Start :: Cerbos Authorization Management Platform // Documentation

Quick Start

Cerbos is an open-source authorization engine. Policies written in YAML declare which actions a principal (the user or service making a request) may perform on a resource (the thing being accessed). At runtime, an application asks the Cerbos PDP "can principal P do action A on resource R?" and the PDP evaluates the applicable policies to return an allow or deny decision.

Synapse is a proxy that sits in front of Cerbos. It exposes the same Cerbos API, so calling applications remain unchanged. Before each request reaches the PDP, Synapse can look up data from other systems, add attributes to principals or resources, rewrite requests, or modify responses. Lookups that would otherwise live in every application are centralized in the proxy.

This guide covers running Synapse locally and writing a first proxy extension in Starlark. The extension looks up user data from an external API and attaches it to the principal before the embedded Cerbos PDP evaluates a policy. Other extension types are covered in their own guides.

The finished setup runs Synapse in front of an embedded Cerbos PDP. Each CheckResources request is intercepted, the principal is enriched with attributes fetched from a public test API, and a policy evaluates the enriched request. The client sends the same call it would send to Cerbos directly; no application code changes.

Synapse processes each Cerbos API request in three stages:

  1. The caller sends a CheckResources or PlanResources request.
  2. Synapse runs the configured proxy extensions, which may modify the request (for example, by adding attributes to the principal) and later modify the response.
  3. The augmented request is handed to the PDP, which evaluates the relevant policies and returns a decision.

Before you begin

You will need:

Log in to the Cerbos distribution repository:

$ docker login CERBOS_DISTRIBUTION_REPO --username=YOUR_LICENCE_USER --password=YOUR_LICENCE_KEY

Create a working directory

Create a directory that will hold the configuration, policies and extension code:

$ mkdir -p synapse-quickstart/policies synapse-quickstart/extensions
$ cd synapse-quickstart

The policies directory will hold the Cerbos policy files loaded by the embedded PDP. The extensions directory will hold the Starlark source for the proxy extension written later in this guide.

Write a policy

Create policies/invoice.yaml with a single resource policy that allows an employee to view an invoice when the invoice belongs to the same company as the principal:

apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: invoice
  version: default
  rules:
    - actions:
        - view
      effect: EFFECT_ALLOW
      roles:
        - employee
      condition:
        match:
          expr: request.resource.attr.company == request.principal.attr.company

The condition compares a company attribute on the resource to a company attribute on the principal. Neither attribute is built in to Cerbos: the caller is expected to send them as part of the request, or an extension must inject them. This guide takes the second approach.

Start Synapse

Create config.yaml with the minimum configuration needed to run Synapse with an in-process PDP that loads policies from disk:

server:
  listenAddress: ":3594"
pdp:
  inProcess:
    storage:
      driver: "disk"
      disk:
        directory: /policies
    audit:
      enabled: true
      backend: file
      file:
        path: stdout

Start the container with the three bind mounts for the config, policies and extensions directories:

$ docker run --rm --name synapse-qs -p 3594:3594 \
    -v $(pwd)/config.yaml:/config/config.yaml:ro \
    -v $(pwd)/policies:/policies:ro \
    -v $(pwd)/extensions:/extensions:ro \
    CERBOS_DISTRIBUTION_REPO/synapse/synapse:latest \
    server --conf.path=/config/config.yaml --log.level=debug

Make a check request

In a new terminal, from the synapse-quickstart directory, create check-request.json:

{
  "requestId": "qs-001",
  "principal": {
    "id": "1",
    "roles": ["employee"]
  },
  "resources": [
    {
      "actions": ["view"],
      "resource": {
        "kind": "invoice",
        "id": "inv-42",
        "attr": {
          "company": "Romaguera-Crona"
        }
      }
    },
    {
      "actions": ["view"],
      "resource": {
        "kind": "invoice",
        "id": "inv-99",
        "attr": {
          "company": "Other Inc"
        }
      }
    }
  ]
}

Send the request:

$ curl -s -X POST http://localhost:3594/api/check/resources \
    -H 'Content-Type: application/json' \
    -d @check-request.json

Response

{
  "requestId": "qs-001",
  "results": [
    { "resource": { "id": "inv-42", "kind": "invoice" }, "actions": { "view": "EFFECT_DENY" } },
    { "resource": { "id": "inv-99", "kind": "invoice" }, "actions": { "view": "EFFECT_DENY" } }
  ]
}

Both resources are denied. The policy condition references request.principal.attr.company, and the request did not supply it, so the comparison fails for every resource.

Add a proxy extension

Create extensions/enrich_principal.star:

load("http", "http")

def augment_check_request(req):
    return _augment(req)

def augment_plan_request(req):
    return _augment(req)

def _augment(req):
    if not hasattr(req, "principal"):
        return req

resp = http.get("https://jsonplaceholder.typicode.com/users/" + req.principal.id)
    if resp.status_code != 200:
        return req

user = resp.json()
    req.principal.attr = {
        "company": user["company"]["name"],
    }
    return req

Register the extension by replacing the contents of config.yaml with:

server:
  listenAddress: ":3594"
pdp:
  inProcess:
    storage:
      driver: "disk"
      disk:
        directory: /policies
    audit:
      enabled: true
      backend: file
      file:
        path: stdout

extensions:
  proxyExtensions:
    enrichPrincipal:
      extensionURL: /extensions/enrich_principal.star
      required: true

Restart and verify

Stop the running container and start it again with the same docker run command as before. The extension is loaded at startup.

Send the same request again:

$ curl -s -X POST http://localhost:3594/api/check/resources \
    -H 'Content-Type: application/json' \
    -d @check-request.json

Response

{
  "requestId": "qs-001",
  "results": [
    { "resource": { "id": "inv-42", "kind": "invoice" }, "actions": { "view": "EFFECT_ALLOW" } },
    { "resource": { "id": "inv-99", "kind": "invoice" }, "actions": { "view": "EFFECT_DENY" } }
  ]
}

The decision for inv-42 has flipped to EFFECT_ALLOW...

Build a test suite to ensure repeatability

Manually restarting the container and sending requests via curl becomes cumbersome very quickly. Build test suites using the built-in testing framework to automate the process and codify the requirements of your extension.

Create a test suite for your extension by adding a new file named extensions/enrich_principal_test.star with the following contents.

test_suite = struct(
    name = "Test principal enrichment",
    synapse_config = testing.load_synapse_config("/config/config.yaml")
)

def test_enrichment(context):
    request = struct(
        requestId = "qs-001",
        principal = struct(
            id = "1",
            roles = ["employee"]
        ),
        resources = [
            struct(
                actions = ["view"],
                resource = struct(
                    kind = "invoice",
                    id = "inv-42",
                    attr = {
                        "company": "Romaguera-Crona"
                    }
                )
            ),
            struct(
                actions = ["view"],
                resource = struct(
                    kind = "invoice",
                    id = "inv-99",
                    attr = {
                        "company": "Other Inc"
                    }
                )
            )
        ]
    )

have = context.check_resources(request)
    return testing.assert(have.results[0].actions["view"] == "EFFECT_ALLOW" and have.results[1].actions["view"] == "EFFECT_DENY")

Run the test suite.

$ docker run \
    --rm --name synapse-test \
    -v $(pwd)/config.yaml:/config/config.yaml:ro \
    -v $(pwd)/policies:/policies:ro \
    -v $(pwd)/extensions:/extensions:ro \
    CERBOS_DISTRIBUTION_REPO/synapse/synapse:latest \
    test /extensions

Adapt the extension to your own API

The extension above targets a public demonstration API. Retargeting it at an internal service is typically a URL change. Production integrations require additional handling:

Next steps