Flexible Policy Composition - Local & Export Variables in Cerbos Policies
Tags: opensource, security, howto, tutorial
Published: 2023-07-26 07:36:41

Cerbos has continuously evolved to provide you with cool features. Variables were first introduced experimentally in v0.8, offering a way to reduce duplication in policy conditions. Building on this foundation, they were further refined and stabilized in v0.9, making them even more valuable for crafting robust access control rules.

However, there was one limitation - variables were restricted to each individual policy, making it impossible to reuse them across multiple policies. When there was a need to use the same variable across the configuration, this led to repetition and sub-optimal policy maintainability. But with the latest release of [Cerbos v0.29](https://cerbos.dev/blog/shared-variables-globals-scope-updates-and-more-cerbos-v029), this limitation has been overcome with the introduction of `exportVariables` - an enabler to share variables across the configuration. This enhancement empowers policy authors to define variables in a dedicated file and effortlessly import them into multiple policies. Not only does this reduce duplication within policies, but it also streamlines policy management across the entire access control system.

In this piece, we'll explore how to leverage Cerbos' new `exportVariables` feature and compare it with the old approach of using local variables within policies.

## How to use exportVariables with Cerbos Policies?
The first thing you need to do is update your version of Cerbos accordingly. The feature we’re covering is available since v0.29 so that is the lowest you should aim for in order to be able to use `exportVariables`. See [installation instructions](https://docs.cerbos.dev/cerbos/latest/installation/binary.html) for more information.

In **earlier versions of Cerbos**, only local variables existed, and were accessible from the policy they are defined in. Note that variables were defined in a top-level `variables` field in the policy file. 

Let’s say you had resource policy file named _leave_request_policies.yaml_, structured like this:

```yml
---
apiVersion: api.cerbos.dev/v1
variables:
      pending_approval: ("PENDING_APPROVAL")
      principal_location: (P.attr.ip_address.inIPAddrRange("10.20.0.0/16") ? "GB" : "")
resourcePolicy:
      # ...
```

Whenever you wanted to use any of these variables, there was no option other than to redefine them in every file you’d be using them in. Which, we admit, was not the smoothest way to go around it.

**From Cerbos v0.29 onwards**, variables can now be defined in a separate file known as the `exportVariables` policy and effortlessly imported into other policies. Let's see how this is done. You’ll need define a file named for example _common_variables.yaml_ in which you will define variables you want to be able to import across your configuration:

```yml
---
apiVersion: api.cerbos.dev/v1
description: Common variables used within our app
exportVariables:
  name: our_common_variables
  definitions:
    pending_approval: ("PENDING_APPROVAL")
    principal_location: (P.attr.ip_address.inIPAddrRange("10.20.0.0/16") ? "GB" : "")
```

In the snippet above you see how the `exportVariable` keyword is being used, and nested below it are `name` and `definitions`, which respectively represent the name to use when importing this set of variables, and the map of variable name to expression.

With this approach, our _leave_request_policies.yaml_ (and any other policy file) can simply import the `our_common_variables` anywhere you need them, like this:

```yml
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  variables:
    import:
      - our_common_variables
  # ...
```

And that’s it! Usage of the variables within policy conditions stays unchanged.

```yml
---
apiVersion: api.cerbos.dev/v1
variables:
    import:
      - our_common_variables
resourcePolicy:
  version: "default"
  scope: "our.app.uk"
  resource: leave_request
  rules:
    - actions: ["delete"]
      condition:
        match:
          expr: request.resource.attr.geography == variables.principal_location
      effect: EFFECT_ALLOW
```

## How to use local variables in Cerbos policies?
There still might be a lot of cases where you’d prefer to keep (some of) your variables local to the policy.

**From Cerbos v0.29 onwards**, the previously-used top-level `variables` approach is deprecated and replaced with a `variables.local` section within the policy definition.

```yml
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  variables:
    local:
      pending_approval: ("PENDING_APPROVAL")
      principal_location: (P.attr.ip_address.inIPAddrRange("10.20.0.0/16") ? "GB" : "")
      # ...
```

You can define local variables as well as importing variables in the same policy, but you can’t define the same variable in multiple places.

In order to ensure backwards compatibility, the deprecated top-level field is merged with the `variables.local` section in derived roles, resource, and principal policies.
## Conclusion
With the introduction of `exportVariables` in [Cerbos v0.29](https://github.com/cerbos/cerbos/releases/tag/v0.29.0), managing access control policies becomes more straightforward and flexible. By transitioning from the old approach of using local variables to the new method of importing exported variables, policy authors can streamline their policies, reduce duplication, and further improve overall readability. If you still have a case for using local variables, don’t forget to update to a `variables.local` approach as shown above.