# Conditions

|     |     |
| --- | --- |
|  | This documentation is for<br>a previous<br>version of Cerbos. Choose 0.53.0 from the version picker at the top right or navigate to [https://docs.cerbos.dev](https://docs.cerbos.dev/) for the latest version. |

A powerful feature of Cerbos policies is the ability to define conditions that are evaluated against the data provided in the request. Conditions are written using the [Common Expression Language (CEL)](https://github.com/google/cel-spec/blob/master/doc/intro.md).

|     |     |
| --- | --- |
|  | Cerbos ships with an interactive REPL that can be used to experiment with writing CEL conditions. It can be started by running `cerbos repl`. See [the REPL documentation](https://docs.cerbos.dev/cerbos/0.50.0/cli/cerbos#repl) for more information. |

Every condition expression must evaluate to a boolean true/false value. A condition block in a policy can contain either a single condition expression, or multiple expressions combined using the `all`, `any`, or `none` operators. These logical operators may be nested.

## Condition block

```yaml
condition:
  match:
    all:
      of:
        - expr: request.resource.attr.status == "PENDING_APPROVAL"
        - expr: >
            "GB" in request.resource.attr.geographies
```

## Top-level identifiers

Within a condition expression, you have access to several top-level identifiers:

- `request`  
Data provided in the check or plan request (principal, resource, and auxiliary data).

- `runtime`  
Additional data computed while evaluating the policy.

- `variables`  
Variables declared in the [`variables` section of the policy](https://docs.cerbos.dev/cerbos/0.50.0/policies/variables#variables).

- `constants`  
Variables declared in the [`constants` section of the policy](https://docs.cerbos.dev/cerbos/0.50.0/policies/variables#constants).

- `globals`  
Global variables declared in the [policy engine configuration](https://docs.cerbos.dev/cerbos/0.50.0/configuration/engine#globals).

## The `request` object

```yaml
request:
  principal: (1)
    id: alice (2)
    roles: (3)
      - employee
    attr: (4)
      geography: GB

resource: (5)
    kind: leave_request (6)
    id: XX125 (7)
    attr: (8)
      owner: alice

auxData: (9)
    jwt: (10)
      iss: acme.corp
```

|     |     |
| --- | --- |
| **1** | The principal whose permissions are being checked. |
| **2** | ID of the principal. |
| **3** | Static roles that are assigned to the principal by your identity management system. |
| **4** | Free-form context data about the principal. |
| **5** | The resource on which the principal is performing actions. |
| **6** | Resource kind. |
| **7** | ID of the resource instance. |
| **8** | Free-form context data about the resource instance. |
| **9** | [Auxiliary data sources](https://docs.cerbos.dev/cerbos/0.50.0/configuration/auxdata). |
| **10** | JWT claims. |

## The `runtime` object

```yaml
runtime:
  effectiveDerivedRoles: (1)
    - owner
    - gb_employee
```

|     |     |
| --- | --- |
| **1** | [Derived roles](https://docs.cerbos.dev/cerbos/0.50.0/policies/derived_roles) that were assigned to to the principal by Cerbos while evaluating the policy. This is only populated in expressions in resource policies, and only includes derived roles that are referenced in at least one policy rule. |

## Expressions and blocks

### Single boolean expression

```yaml
condition:
  match:
    expr: P.id.matches("^dev_.*")
```

### `all` operator: all expressions must evaluate to true (logical AND)

```yaml
condition:
  match:
    all:
      of:
        - expr: R.attr.status == "PENDING_APPROVAL"
        - expr: >
            "GB" in R.attr.geographies
        - expr: P.attr.geography == "GB"
```

### `any` operator: only one of the expressions has to evaluate to true (logical OR)

```yaml
condition:
  match:
    any:
      of:
        - expr: R.attr.status == "PENDING_APPROVAL"
        - expr: >
            "GB" in R.attr.geographies
        - expr: P.attr.geography == "GB"
```

### `none` operator: none of the expressions should evaluate to true (logical negation)

```yaml
condition:
  match:
    none:
      of:
        - expr: R.attr.status == "PENDING_APPROVAL"
        - expr: >
            "GB" in R.attr.geographies
        - expr: P.attr.geography == "GB"
```

### Nesting operators

```yaml
condition:
  match:
    all:
      of:
        - expr: R.attr.status == "DRAFT"
        - any:
            of:
              - expr: R.attr.dev == true
              - expr: R.attr.id.matches("^[98][0-9]+")
        - none:
            of:
              - expr: R.attr.qa == true
              - expr: R.attr.canary == true
```

The above nested block is equivalent to the following:

```yaml
condition:
  match:
    expr: >
      (R.attr.status == "DRAFT" &&
        (R.attr.dev == true || R.attr.id.matches("^[98][0-9]+")) &&
        !(R.attr.qa == true || R.attr.canary == true))
```

### Quotes in expressions

Single and double quotes have special meanings in YAML. To avoid parsing errors when your expression contains quotes, use the YAML block scalar syntax or wrap the expression in parentheses.

```yaml
expr: >
  "GB" in R.attr.geographies
```

```yaml
expr: ("GB" in R.attr.geographies)
```

## Policy variables

To avoid duplication in condition expressions, you can define [variables and constants in policies](https://docs.cerbos.dev/cerbos/0.50.0/policies/variables).

## Auxiliary data

If you have [auxiliary data sources configured](https://docs.cerbos.dev/cerbos/0.50.0/configuration/auxdata), they can be accessed using `request.auxData`.

### Accessing JWT claims

```yaml
"cerbie" in request.auxData.jwt.aud && request.auxData.jwt.iss == "cerbos"
```

## Operators

| Operator | Description |
| --- | --- |
| `!` | Logical negation (NOT) |
| `-` | Subtraction/numeric negation |
| `!=` | Unequals |
| `%` | Modulo |
| `&&` | Logical AND |
| `||` | Logical OR |
| `*` | Multiplication |
| `+` | Addition/concatenation |
| `/` | Division |
| `<=` | Less than or equal to |
| `<` | Less than |
| `==` | Equals |
| `>=` | Greater than or equal to |
| `>` | Greater than |
| `in` | Membership in lists or maps |
| `? :` | Ternary condition (if-then-else) |

## Durations

Duration values must be specified in one of the following units. Larger units like days, weeks or years are not supported because of ambiguity around their meaning due to factors such as daylight saving time transitions.

| Suffix | Unit |
| --- | --- |
| `ns` | Nanoseconds |
| `us` | Microseconds |
| `ms` | Milliseconds |
| `s` | Seconds |
| `m` | Minutes |
| `h` | Hours |

## Test data

```json
...  
"resource": {
  "kind": "leave_request",
  "attr": {
    "cooldownPeriod": "3750s",
    "lastAccessed": "2021-04-20T10:00:20.021-05:00"
  }
}
...  
```

| Function | Description | Example |
| --- | --- | --- |
| `duration` | Convert a string to a duration. The string must contain a valid duration suffixed by one of `ns`, `us`, `ms`, `s`, `m` or `h`. E.g. `3750s` | `duration(R.attr.cooldownPeriod).getSeconds() == 3750` |
| `getHours` | Get hours from a duration | `duration(R.attr.cooldownPeriod).getHours() == 1` |
| `getMilliseconds` | Get milliseconds from a duration | `duration(R.attr.cooldownPeriod).getMilliseconds() == 3750000` |
| `getMinutes` | Get minutes from a duration | `duration(R.attr.cooldownPeriod).getMinutes() == 62` |
| `getSeconds` | Get seconds from a duration | `duration(R.attr.cooldownPeriod).getSeconds() == 3750` |
| `timeSince` | Time elapsed since the given timestamp to current time on the server. This is a Cerbos extension to CEL | `timestamp(R.attr.lastAccessed).timeSince() > duration("1h")` |

## Hierarchies

The hierarchy functions are Cerbos-specific extensions to CEL.

### Test data

```json
...  
"principal": {
  "id": "john",
  "roles": ["employee"],
  "attr": {
    "scope": "foo.bar.baz.qux",
  }
},
"resource": {
  "kind": "leave_request",
  "attr": {
    "scope": "foo.bar",
  }
}
...  
```

| Function | Description | Example |
| --- | --- | --- |
| `hierarchy` | Convert a dotted string or a string list to a hierarchy | `hierarchy("a.b.c") == hierarchy(["a","b","c"])` |
| `ancestorOf` | Returns true if the first hierarchy shares a common prefix with the second hierarchy | `hierarchy("a.b").ancestorOf(hierarchy("a.b.c.d")) == true` |
| `siblingOf` | Returns true if both hierarchies share the same parent | `hierarchy("a.b.c").siblingOf(hierarchy("a.b.d")) == true` |
| `size` | Returns the number of levels in the hierarchy | `hierarchy("a.b.c").size() == 3` |

## IP addresses

The IP address functions are Cerbos-specific extensions to CEL.

### Test data

```json
...  
"principal": {
  "id": "elmer_fudd",
  "attr": {
    "ipv4Address": "192.168.0.10",
    "ipv6Address": "2001:0db8:0000:0000:0000:0000:1000:0000"
  }
}
...  
```

| Function | Description | Example |
| --- | --- | --- |
| `inIPAddrRange` | Check whether the IP address is in the range defined by the CIDR | `P.attr.ipv4Address.inIPAddrRange("192.168.0.0/24") && P.attr.ipv6Address.inIPAddrRange("2001:db8::/48")` |

## Lists and maps

### Test data

```json
...  
"principal": {
  "id": "elmer_fudd",
  "attr": {
    "id": "125",
    "teams": ["design", "communications", "product", "commercial"],
    "limits": {
        "design": 10,
        "product": 25
    },
    "clients": {
      "acme": {"active": true},
      "bb inc": {"active": true}
    }
  }
}
...  
```

| Operator/Function | Description | Example |
| --- | --- | --- |
| `+` | Concatenates lists | `P.attr.teams + ["design", "engineering"]` |
| `[]` | Index into a list or a map | `P.attr.teams[0] == "design" && P.attr.clients["acme"]["active"] == true` |
| `filter` | Filter a list using the predicate. | `size(P.attr.teams.filter(t, t.matches("^comm"))) == 2` |

## Math

| Function | Description | Example |
| --- | --- | --- |
| `math.abs` | Returns the absolute value of the numeric type provided as input | `math.abs(1.2) == 1.2 && math.abs(-2) == 2` |
| `math.ceil` | Compute the ceiling of a double value | `math.ceil(1.2) == 2.0 && math.ceil(-1.2) == -1.0` |
| `math.round` | Rounds the double value to the nearest whole number with ties rounding away from zero | `math.round(1.2) == 1.0 && math.round(1.5) == 2.0` |

## SPIFFE

| Function | Description | Example |
| --- | --- | --- |
| `spiffeID.isMemberOf` | Check whether the ID belongs to given trust domain | `spiffeID(P.id).isMemberOf(spiffeTrustDomain("spiffe://cerbos.dev"))` |

## Strings

### Test data

```json
...  
"resource": {
  "kind": "leave_request",
  "attr": {
    "id": "125",
    "department": "marketing"
  }
}
...  
```

| Function | Description | Example |
| --- | --- | --- |
| `base64.encode` | Encode as base64 | `base64.encode(bytes("hello")) == "aGVsbG8="` |
| `contains` | Check whether a string contains the given substring | `R.attr.department.contains("arket")` |

## Timestamps

### Test data

```json
...  
"resource": {
  "kind": "leave_request",
  "attr": {
    "lastAccessed": "2021-04-20T10:00:20.021-05:00",
    "lastUpdateTime": "2021-05-01T13:34:12.024Z",
  }
}
...  
```

| Function | Description | Example |
| --- | --- | --- |
| `timestamp` | Convert an RFC3339 formatted string to a timestamp | `timestamp(R.attr.lastAccessed).getFullYear() == 2021` |
| `getDate` | Get day of month from a timestamp | `timestamp(R.attr.lastAccessed).getDate() == 20` |

Example: Assert that more than 36 hours has elapsed between last access time and last update time

```yaml
timestamp(R.attr.lastUpdateTime) - timestamp(R.attr.lastAccessed) > duration("36h")
```

Example: Add a duration to a timestamp

```yaml
timestamp(R.attr.lastUpdateTime) + duration("24h") == timestamp("2021-05-02T13:34:12.024Z")
```
