Storage block

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.

Cerbos supports multiple backends for storing policies. Which storage driver to use is defined by the driver setting.

Blob driver

Cerbos policies can be stored in AWS S3, Google Cloud Storage, or any other S3-compatible storage systems such as Minio.

Configuration keys
  • bucket: Required. A URL specifying the service (e.g. S3, GCS), the storage bucket and any other configuration parameters required by the provider.

    • AWS S3: s3://my-bucket?region=us-west-1. Must specify region in the URL.

    • Google Cloud Storage: gs://my-bucket

    • S3-compatible (e.g. Minio): s3://my-bucket?endpoint=my.minio.local:8080&disableSSL=true&s3ForcePathStyle=true&region=local. Must specify region in the URL.

  • prefix: Optional. Look for policies only under this key prefix.

  • workDir: Optional. Path to the local directory to download the policies to. Defaults to the system cache directory if not specified.

  • updatePollInterval: Optional. How frequently the blob store should be checked to discover new or updated policies. Defaults to 0 — which disables polling.

  • requestTimeout: Optional. HTTP request timeout. It takes an HTTP request to download a policy file. Defaults to 5s.

  • downloadTimeout: Optional. Timeout to download all policies from the the storage provider. Must be greater than the requestTimeout. Defaults to 60s.

Setting the updatePollInterval to a low value could increase resource consumption in both the client and the server systems. Some managed service providers may even impose rate limits or temporary suspensions on your account if the number of requests is too high.

Credentials for accessing the storage buckets are retrieved from the environment. The method of specifying credentials in the environment vary by cloud provider and security configuration. Usually, it involves defining environment variables such as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for S3 and GOOGLE_APPLICATION_CREDENTIALS for GCS. Refer to the relevant cloud provider documentation for more details.

AWS S3
storage:
  driver: "blob"
  blob:
    bucket: "s3://my-bucket-name?region=us-east-2"
    prefix: policies
    workDir: ${HOME}/tmp/cerbos/work
    updatePollInterval: 15s
    downloadTimeout: 30s
    requestTimeout: 10s
Google Cloud Storage
storage:
  driver: "blob"
  blob:
    bucket: "gs://my-bucket-name"
    workDir: ${HOME}/tmp/cerbos/work
    updatePollInterval: 10s
Minio local container
storage:
  driver: "blob"
  blob:
    bucket: "s3://my-bucket-name?endpoint=localhost:9000&disableSSL=true&s3ForcePathStyle=true&region=local"
    workDir: ${HOME}/tmp/cerbos/work
    updatePollInterval: 10s

Disk driver

The disk driver is a way to serve the policies from a directory on the filesystem. Any .yaml, .yml or .json files in the directory tree rooted at the given path will be read and parsed as policies.

Static fileset with no change detection
storage:
  driver: disk
  disk:
    directory: /etc/cerbos/policies
Dynamic fileset with change detection
storage:
  driver: disk
  disk:
    directory: /etc/cerbos/policies
    watchForChanges: true
On some platforms the automatic change detection feature can be inefficient and resource-intensive if the watched directory contains many files or gets updated frequently.

Archive files

Alternatively, you can opt to archive and/or compress your policies directory into a Zip (.zip), Tar (.tar) or Gzip file (.tgz or .tar.gz). The archive is assumed to be laid out like a standard policy directory. It must contain no non-policy YAML files.

You specify the file in your config like so:

Archived fileset using a Zip file
storage:
  driver: disk
  disk:
    directory: /etc/cerbos/policies.zip
Change detection will be disabled when using archive files.

Git driver

Git is the preferred method of storing Cerbos policies. The server is smart enough to detect when new commits are made to the git repository and refresh its state based on the changes.

Azure DevOps repositories use a newer protocol that is currently not supported by the Git library used by Cerbos. We are working to address this issue. In the mean time, please consider using the Cerbos disk storage in conjunction with an external Git sync implementation such as https://github.com/kubernetes/git-sync or using a CI pipeline to publish your policies to another storage implementation supported by Cerbos.
  • Git repositories can be local (file protocol) or remote (ssh or https). Please note that the local file protocol requires git to be available and cannot be used with the Cerbos container.

  • If no branch is specified, the default branch would be the master branch.

  • If no subDir is specified, the entire repository would be scanned for policies (.yaml, .yml or .json).

  • The checkoutDir is the working directory of the server and must be writable by the server process.

  • If updatePollInterval is set to 0, the source repository will not be polled to pick up any new commits.

  • If operationTimeout is not specified, the default timeout for git operations is 60 seconds.

If the git repository is remote, setting the updatePollInterval to a low value could increase resource consumption in both the client and the server systems. Some managed service providers may even impose rate limits or temporary suspensions on your account if the number of requests is too high.
Local git repository
storage:
  driver: "git"
  git:
    protocol: file
    url: file://${HOME}/tmp/cerbos/policies
    checkoutDir: ${HOME}/tmp/cerbos/work
    updatePollInterval: 10s
Remote git repository accessed over HTTPS
storage:
  driver: "git"
  git:
    protocol: https
    url: https://github.com/cerbos/policy-test.git
    branch: main
    subDir: policies
    checkoutDir: ${HOME}/tmp/work/policies
    updatePollInterval: 60s
    operationTimeout: 30s
    https:
      username: cerbos
      password: ${GITHUB_TOKEN}
Remote git repository accessed over SSH
storage:
  driver: "git"
  git:
    protocol: ssh
    url: github.com:cerbos/policy-test.git
    branch: main
    subDir: policies
    checkoutDir: ${HOME}/tmp/cerbos/work
    updatePollInterval: 60s
    ssh:
      user: git
      privateKeyFile: ${HOME}/.ssh/id_rsa

Hub driver

Requires a Cerbos Hub account.

Try Cerbos Hub

Connects the PDP to a Cerbos Hub deployment label. Whenever a policy change is detected, the Cerbos Hub CI/CD pipeline compiles, tests and pushes an optimized policy bundle to the PDP.

If you are new to Cerbos Hub, follow the getting started guide. For more information about configuring a PDP to connect to Cerbos Hub, refer to the Service PDP documentation.

MySQL driver

The MySQL storage backend is one of the dynamic stores that supports adding or updating policies at runtime through the Admin API.

The cerbosctl utility is a handy way to interact with the Admin API and supports loading policies through the built-in put command.

Cerbos has an in-memory cache for holding compiled policy definitions to speed up the evaluation process. When a policy is removed or updated using the Admin API this cache is updated by the instance that handles the request. However, if you share the database with multiple Cerbos instances, the other instances won’t be aware of the change and might still have the old policy definition cached in memory. There are several ways to handle this situation.

  • By default, the cache entries are stored indefinitely until there’s memory pressure. You can set a maximum cache duration for entries by setting the compile.cacheDuration configuration value. This could help make all the Cerbos instances to become eventually consistent within a time frame that’s acceptable to you.

  • You can turn off caching completely by setting compile.cacheSize configuration to 0. This would have a small performance hit but depending on your use case it could be negligible.

  • Invoke the /admin/store/reload API endpoint on all the Cerbos instances whenever you make a change to your policies.

Unlike the SQLite3 driver, the tables and other database objects are not created automatically by the Cerbos MySQL driver. This is to minimize the privileges the Cerbos instance has on the MySQL installation. You must create the required tables using the provided script before configuring Cerbos to connect to the database.

The driver configuration expects the connection details to be provided as a DSN in the following form:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

See https://github.com/go-sql-driver/mysql#dsn-data-source-name for the list of supported parameters.

You can use environment variable references in the URL to avoid storing credentials as part of the Cerbos configuration file.

Using MySQL as a storage backend for Cerbos
storage:
  driver: "mysql"
  mysql:
    dsn: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:3306)/cerbos"

Secure connections

If your MySQL server requires TLS or if you want to use RSA key pair-based password exchange, you can configure those settings as follows:

TLS certificates
storage:
  driver: "mysql"
  mysql:
    dsn: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:3306)/cerbos?tls=mysecuretls"
    tls:
      mysecuretls:
        caCert: /path/to/ca_certificate.crt
        cert: /path/to/certificate.crt
        key: /path/to/private.key
Server public key
storage:
  driver: "mysql"
  mysql:
    dsn: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:3306)/cerbos?serverPubKey=mypubkey"
    serverPubKey:
      mypubkey: /path/to/server_public_key.pem

Connection pool

Cerbos uses a connection pool when connecting to a database. You can configure the connection pool settings by adding a connPool section to the driver configuration.

Available options are:

maxLifeTime

The maximum length of time a connection can be reused for. This is useful when your database enforces a maximum lifetime on connections or if you have a load balancer in front of your database to spread the load.

maxIdleTime

How long a connection should be idle for before it is closed. Useful if you want to cleanup idle connections quickly.

maxOpen

Maximum number of connections that can be open at any given time (including idle connections).

maxIdle

Maximum number of idle connections that can be open at any given time.

Connection pool settings can have a significant impact on the performance of Cerbos and your database server. Make sure you fully understand the implications of updating these settings before making any changes.
storage:
  driver: "mysql"
  mysql:
    dsn: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:3306)/cerbos"
    connPool:
      maxLifeTime: 5m
      maxIdleTime: 3m
      maxOpen: 10
      maxIdle: 5

Connection retries

Cerbos attempts to connect to the database on startup and exits if connection cannot be established after three attempts. You can configure the connection retry settings using the connRetry options.

maxAttempts

Maximum number of connection attempts before giving up

initialInterval

The time to wait before the second connection attempt. Subsequent attempts have increasing wait times (exponential backoff) derived from a combination of this value and the retry attempt number

maxInterval

Maximum amount of time to wait between retries. This affects the maximum value produced by the exponential backoff algorithm.

Changing the retry settings affect the availability of Cerbos and the time it takes to detect and recover from a failure. For example, if the database connection details are incorrect or have changed, it will take longer for a Cerbos PDP to fail on startup because of retries.

Database object definitions

You can customise the script below to suit your environment. Make sure to specify a strong password for the cerbos_user user.

CREATE DATABASE IF NOT EXISTS cerbos CHARACTER SET utf8mb4;

USE cerbos;

CREATE TABLE IF NOT EXISTS policy (
    id BIGINT PRIMARY KEY,
    kind VARCHAR(128) NOT NULL,
    name VARCHAR(1024) NOT NULL,
    version VARCHAR(128) NOT NULL,
    scope VARCHAR(512),
    description TEXT,
    disabled BOOLEAN default false,
    definition BLOB);

CREATE TABLE IF NOT EXISTS policy_dependency (
    policy_id BIGINT NOT NULL,
    dependency_id BIGINT NOT NULL,
    PRIMARY KEY (policy_id, dependency_id),
    FOREIGN KEY (policy_id) REFERENCES policy(id) ON DELETE CASCADE);

CREATE TABLE IF NOT EXISTS policy_ancestor (
    policy_id BIGINT NOT NULL,
    ancestor_id BIGINT NOT NULL,
    PRIMARY KEY (policy_id, ancestor_id),
    FOREIGN KEY (policy_id) REFERENCES policy(id) ON DELETE CASCADE);

CREATE TABLE IF NOT EXISTS policy_revision (
    revision_id INTEGER AUTO_INCREMENT PRIMARY KEY,
    action ENUM('INSERT', 'UPDATE', 'DELETE'),
    id BIGINT NOT NULL,
    kind VARCHAR(128),
    name VARCHAR(1024),
    version VARCHAR(128),
    scope VARCHAR(512),
    description TEXT,
    disabled BOOLEAN,
    definition BLOB,
    update_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

CREATE TABLE IF NOT EXISTS attr_schema_defs (
    id VARCHAR(255) PRIMARY KEY,
    definition JSON);

DROP TRIGGER IF EXISTS policy_on_insert;

CREATE TRIGGER policy_on_insert AFTER INSERT ON policy
FOR EACH ROW
INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
VALUES('INSERT', NEW.id, NEW.kind, NEW.name, NEW.version, NEW.scope, NEW.description, NEW.disabled, NEW.definition);

DROP TRIGGER IF EXISTS policy_on_update;

CREATE TRIGGER policy_on_update AFTER UPDATE ON policy
FOR EACH ROW
INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
VALUES('UPDATE', NEW.id, NEW.kind, NEW.name, NEW.version, NEW.scope, NEW.description, NEW.disabled, NEW.definition);

DROP TRIGGER IF EXISTS policy_on_delete;

CREATE TRIGGER policy_on_delete AFTER DELETE ON policy
FOR EACH ROW
INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
VALUES('DELETE', OLD.id, OLD.kind, OLD.name, OLD.version, OLD.scope, OLD.description, OLD.disabled, OLD.definition);

CREATE USER IF NOT EXISTS cerbos_user IDENTIFIED BY 'changeme';
GRANT SELECT,INSERT,UPDATE,DELETE ON cerbos.policy TO cerbos_user;
GRANT SELECT,INSERT,UPDATE,DELETE ON cerbos.attr_schema_defs TO cerbos_user;
GRANT SELECT,INSERT,UPDATE,DELETE ON cerbos.policy_dependency TO cerbos_user;
GRANT SELECT,INSERT,UPDATE,DELETE ON cerbos.policy_ancestor TO cerbos_user;
GRANT SELECT,INSERT ON cerbos.policy_revision TO cerbos_user;

Overlay driver

You can provide redundancy by configuring an overlay driver, which wraps a base and a fallback driver. Under normal operation, the base driver will be targeted as usual. However, if the driver consistently errors, the PDP will start targeting the fallback driver instead. The fallback is determined by a configurable circuit breaker pattern.

You can configure the fallback error threshold and the fallback error window to determine how many errors can occur within a rolling window before the circuit breaker is tripped.

storage:
  driver: "overlay"
  overlay:
    baseDriver: postgres
    fallbackDriver: disk
    fallbackErrorThreshold: 5 # number of errors that occur within the fallbackErrorWindow to trigger failover
    fallbackErrorWindow: 5s # the rolling window in which errors are aggregated
  disk:
    directory: policies
    watchForChanges: true
  postgres:
    url: "postgres://${PG_USER}:${PG_PASSWORD}@localhost:5432/postgres?sslmode=disable&search_path=cerbos"
The overlay driver assumes the same interface as the base driver. Any operations that are available on the base driver but not the fallback driver will error if the circuit breaker is open and the fallback driver is being targeted. Likewise, even if the fallback driver supports additional operations compared to the base driver, these will still not be available should failover occur.

Postgres driver

The Postgres storage backend is one of the dynamic stores that supports adding or updating policies at runtime through the Admin API.

The cerbosctl utility is a handy way to interact with the Admin API and supports loading policies through the built-in put command.

Cerbos has an in-memory cache for holding compiled policy definitions to speed up the evaluation process. When a policy is removed or updated using the Admin API this cache is updated by the instance that handles the request. However, if you share the database with multiple Cerbos instances, the other instances won’t be aware of the change and might still have the old policy definition cached in memory. There are several ways to handle this situation.

  • By default, the cache entries are stored indefinitely until there’s memory pressure. You can set a maximum cache duration for entries by setting the compile.cacheDuration configuration value. This could help make all the Cerbos instances to become eventually consistent within a time frame that’s acceptable to you.

  • You can turn off caching completely by setting compile.cacheSize configuration to 0. This would have a small performance hit but depending on your use case it could be negligible.

  • Invoke the /admin/store/reload API endpoint on all the Cerbos instances whenever you make a change to your policies.

Unlike the SQLite3 driver, the tables and other database objects are not created automatically by the Cerbos Postgres driver. This is to minimize the privileges the Cerbos instance has on the Postgres installation. You must create the required tables using the provided script before configuring Cerbos to connect to the database.

The driver configuration expects the connection details to be provided as connection URL. See Postgres connstring documentation for more information. Use the search_path parameter to point to the schema containing the Cerbos tables.

You can use environment variable references in the URL to avoid storing credentials as part of the Cerbos configuration file.

Using Postgres as a storage backend for Cerbos
storage:
  driver: "postgres"
  postgres:
    url: "postgres://${PG_USER}:${PG_PASSWORD}@localhost:5432/postgres?sslmode=disable&search_path=cerbos"

Connection pool

Cerbos uses a connection pool when connecting to a database. You can configure the connection pool settings by adding a connPool section to the driver configuration.

Available options are:

maxLifeTime

The maximum length of time a connection can be reused for. This is useful when your database enforces a maximum lifetime on connections or if you have a load balancer in front of your database to spread the load.

maxIdleTime

How long a connection should be idle for before it is closed. Useful if you want to cleanup idle connections quickly.

maxOpen

Maximum number of connections that can be open at any given time (including idle connections).

maxIdle

Maximum number of idle connections that can be open at any given time.

Connection pool settings can have a significant impact on the performance of Cerbos and your database server. Make sure you fully understand the implications of updating these settings before making any changes.
storage:
  driver: "postgres"
  postgres:
    url: "postgres://${PG_USER}:${PG_PASSWORD}@localhost:5432/postgres?sslmode=disable&search_path=cerbos"
    connPool:
      maxLifeTime: 5m
      maxIdleTime: 3m
      maxOpen: 10
      maxIdle: 5

Connection retries

Cerbos attempts to connect to the database on startup and exits if connection cannot be established after three attempts. You can configure the connection retry settings using the connRetry options.

maxAttempts

Maximum number of connection attempts before giving up

initialInterval

The time to wait before the second connection attempt. Subsequent attempts have increasing wait times (exponential backoff) derived from a combination of this value and the retry attempt number

maxInterval

Maximum amount of time to wait between retries. This affects the maximum value produced by the exponential backoff algorithm.

Changing the retry settings affect the availability of Cerbos and the time it takes to detect and recover from a failure. For example, if the database connection details are incorrect or have changed, it will take longer for a Cerbos PDP to fail on startup because of retries.

Database object definitions

You can customise the script below to suit your environment. Make sure to specify a strong password for the cerbos_user user.

CREATE SCHEMA IF NOT EXISTS cerbos;

SET search_path TO cerbos;

CREATE TABLE IF NOT EXISTS policy (
    id bigint NOT NULL PRIMARY KEY, 
    kind VARCHAR(128) NOT NULL,
    name VARCHAR(1024) NOT NULL,
    version VARCHAR(128) NOT NULL,
    scope VARCHAR(512),
    description TEXT,
    disabled BOOLEAN default false,
    definition BYTEA
);

CREATE TABLE IF NOT EXISTS policy_dependency (
    policy_id BIGINT,
    dependency_id BIGINT,
    PRIMARY KEY (policy_id, dependency_id),
    FOREIGN KEY (policy_id) REFERENCES cerbos.policy(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS policy_ancestor (
    policy_id BIGINT,
    ancestor_id BIGINT,
    PRIMARY KEY (policy_id, ancestor_id),
    FOREIGN KEY (policy_id) REFERENCES cerbos.policy(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS policy_revision (
    revision_id SERIAL PRIMARY KEY,
    action VARCHAR(64),
    id BIGINT,
    kind VARCHAR(128),
    name VARCHAR(1024),
    version VARCHAR(128),
    scope VARCHAR(512),
    description TEXT,
    disabled BOOLEAN, 
    definition BYTEA,
    update_timestamp TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS attr_schema_defs (
    id VARCHAR(255) PRIMARY KEY,
    definition JSON
);

CREATE OR REPLACE FUNCTION process_policy_audit() RETURNS TRIGGER AS $policy_audit$
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
            VALUES('DELETE', OLD.id, OLD.kind, OLD.name, OLD.version, OLD.scope, OLD.description, OLD.disabled, OLD.definition);
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
            VALUES('UPDATE', NEW.id, NEW.kind, NEW.name, NEW.version, NEW.scope, NEW.description, NEW.disabled, NEW.definition);
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO policy_revision(action, id, kind, name, version, scope, description, disabled, definition)
            VALUES('INSERT', NEW.id, NEW.kind, NEW.name, NEW.version, NEW.scope, NEW.description, NEW.disabled, NEW.definition);
        END IF;
        RETURN NULL; 
    END;
$policy_audit$ LANGUAGE plpgsql;

CREATE TRIGGER policy_audit
AFTER INSERT OR UPDATE OR DELETE ON policy 
FOR EACH ROW EXECUTE PROCEDURE process_policy_audit();

CREATE USER cerbos_user WITH PASSWORD 'changeme';
GRANT CONNECT ON DATABASE postgres TO cerbos_user;
GRANT USAGE ON SCHEMA cerbos TO cerbos_user;
GRANT SELECT,INSERT,UPDATE,DELETE ON cerbos.policy, cerbos.policy_dependency, cerbos.policy_ancestor, cerbos.attr_schema_defs TO cerbos_user; 
GRANT SELECT,INSERT ON cerbos.policy_revision TO cerbos_user;
GRANT USAGE,SELECT ON cerbos.policy_revision_revision_id_seq TO cerbos_user;

SQLite3 driver

The SQLite3 storage backend is one of the dynamic stores that supports adding or updating policies at runtime through the Admin API.

The cerbosctl utility is a handy way to interact with the Admin API and supports loading policies through the built-in put command.
In-memory ephemeral database
storage:
  driver: "sqlite3"
  sqlite3:
    dsn: "file::memory:?cache=shared"
Cerbos uses a database connection pool which would result in unexpected behaviour when using the SQLite :memory: database. Use file::memory:?cache=shared instead. See https://www.sqlite.org/draft/inmemorydb.html for details.
On-disk persistent database
storage:
  driver: "sqlite3"
  sqlite3:
    dsn: "file:/tmp/cerbos.sqlite?mode=rwc&cache=shared&_fk=true"