---
title: "Supercharging your policy rules with self-service custom roles"
description: "Managing self-service custom roles, with static attributes, or dynamic updates"
author: "Sam Lock"
date: "2023-02-07T00:00:00.000Z"
canonical: "https://www.cerbos.dev/blog/supercharging-your-policy-rules-with-self-service-custom-roles"
image: "https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/cover_image_f64dfafa36.png"
tags: ["guide","integration"]
source: "https://www.cerbos.dev/blog/supercharging-your-policy-rules-with-self-service-custom-roles"
---

# Supercharging your policy rules with self-service custom roles

A little while ago, we released the blog post: [Mapping business requirements to authorization policies](https://cerbos.dev/blog/mapping-business-requirements-to-authorization-policy). It laid the foundations for how to approach writing your authorization policies from the ground up and discussed different patterns for implementing each.

In this post (the next in a series 👀), we’ll dig a little deeper and look at how to implement a more specific feature: **self-service custom roles**.

### What?

This is a fair response. I mean, we already have all of the flexibility that ABAC gives us - what else do we need?! Well, bear with me, dear reader, whilst I endeavour to explain an approach that might open up even more doors to access-control freedom.

### 🤔

Consider this scenario: you’re an admin in a multi-tenant system and you want a method by which you can copy an existing role, and then select which permissions/actions to enable or disable for each.

### _Surely_ this can’t be achieved with static policies?

Aha! Well, prepare to be adequately surprised. Believe it or not, this approach can absolutely be achieved with static policies. In fact, this is the _idiomatic_ approach to handling these types of scenarios in Cerbos.

Before we dive into an example, let’s provide a little refresher on how access decisions are made in Cerbos:

* Cerbos is _stateless_. Your policies and **only** your policies are baked into the PDP instance.
* When access control requests are made to Cerbos, the body of the request contains **all** of the necessary data required to make that decision. This data is freeform - it’s up to the user to decide what’s required and how to structure it.
* The freeform data can be simple top-level attributes like booleans, integers, floats, and strings, or more complex types, such as arrays, maps, or user defined structs.
* Cerbos uses this contextual information to make decisions against the defined policies.

### A _map_, you say?

Yes! Maps are great, because by using them, we are no longer constrained to single, static attribute values. Suddenly, we can provide _further_ context (a key in this instance), and our _static_ data becomes a little more _dynamic_.

Take a look at this example; a resource policy for a resource of type `workspace`:

```yaml
apiVersion: "api.cerbos.dev/v1"
resourcePolicy:
  version: "default"
  resource: "workspace"
  rules:
    - actions:
        - workspace:view
        - pii:view
      effect: EFFECT_ALLOW
      roles:
        - USER
      condition:
        match:
          expr: P.attr.workspaces[R.id].role == "OWNER"

```

Notice how the condition relies on context passed in within the `P.attr.workspaces` map. The key is the resource ID; the value in this case is a custom struct representing a `workspace`. Calling the `role` attribute on that struct returns a predefined value `"OWNER"`.

Now, for our principal, we can provide lots of rich, custom data for each `workspace` in the organization! These `workspaces` are constructed from the state in your database of choice, and presented to Cerbos within that single `P.attr.workspaces` map 👌.

### I’m going to need an example!

I got you. Take a look at this; we can grant access to a principal with the `USER` role, by constructing the following request payload:

```sh
cat <<EOF | curl --silent "http://localhost:3592/api/check/resources?pretty" -d @-
{
  "requestId": "quickstart",
  "principal": {
    "id": "123",
    "roles": [
      "USER"
    ],
    "attr": {
      "workspaces": {
        "workspaceA": {
          "role": "OWNER"
        },
        "workspaceB": {
          "role": "MEMBER"
        }
      }
    }
  },
  "resources": [
    {
      "actions": [
        "workspace:view",
        "pii:view"
      ],
      "resource": {
        "id": "workspaceA",
        "kind": "workspace"
      }
    },
    {
      "actions": [
        "workspace:view",
        "pii:view"
      ],
      "resource": {
        "id": "workspaceB",
        "kind": "workspace"
      }
    }
  ]
}
EOF

```

Here, our principal is trying to carry out `workspace:view` and `pii:view` actions on two resources, imaginatively named `workspaceA` and `workspaceB`. When building the request, our app consulted our database and constructed the principal attribute data:

```yaml
    …
    "attr": {
      "workspaces": {
        "workspaceA": {
          "role": "OWNER"
        },
        "workspaceB": {
          "role": "MEMBER"
        }
      }

```

Cerbos then uses this to make all the decisions for us, et voila 🎉!

### Ok, that’s cool and all, but I need to create _actual_ dynamic policies on the fly...

Well, we got you there too 😎. We even wrote an [entire blog post](https://cerbos.dev/blog/dynamic-policy-management-with-the-admin-api) about dynamic policy management! But seeing as we’re here, I’ll summarise it again for you.

Firstly, you’re going to need a [mutable data store](https://docs.cerbos.dev/cerbos/latest/configuration/storage.html#sqlite3) to store your policies. Follow that link, scroll down, and you’ll see the ones that are currently supported by Cerbos. These DBs act as a dynamic store in which Cerbos can add, update and retrieve policies.

As an example, here’s the simplest case – an in-memory instance of SQLite that runs alongside the Cerbos PDP:

```yaml
storage:
  driver: "sqlite3"
  sqlite3:
    dsn: ":memory:"

```

There’s every chance that an ephemeral data-store such as the above isn’t going to cut the mustard in your real-life production application, but you get the idea.

OK, so now we have our backing data store, we need to tell Cerbos that it’s allowed to talk to the Admin API. Add (something like) this to your config:

```yaml
server:
  adminAPI:
    adminCredentials:
      passwordHash: JDJ5JDEwJGJWcFRKUzJKRzYxOTJERWs5SzZaS2VSb2Z1cXNSeTYzam9NR1U5UkVKM3BtZ1VLQUVuM0xlCgo= # passwordHash is the base64-encoded bcrypt hash of the password to use for authentication.
      username: cerbos # Username is the hardcoded username to use for authentication.
    enabled: true # Enabled defines whether the admin API is enabled.

```

You’ll then have a choice. You can form your own requests and hit the [API directly](https://docs.cerbos.dev/cerbos/latest/api/admin%5Fapi.html#policy-management), or alternatively; some of the SDKs have the functionality built in – check out the [Go](https://pkg.go.dev/github.com/cerbos/cerbos/client#AdminClient) and [Javascript](https://github.com/cerbos/cerbos-sdk-javascript/blob/main/docs/core.client.addorupdatepolicies.md) examples (we’re continuously improving our library of SDKs so check back for others!).

If you want to see this in action, then check out our [full demo](https://github.com/cerbos/demo-admin-api). It uses a Go backend and a React client; complete with an editing interface using the [Monaco editor](https://microsoft.github.io/monaco-editor/).

---

So there we have it; two wildly different approaches to achieving authorization via powerful, dynamic custom roles in your application. If you have any questions or feedback, head over to our [Slack community](https://community.cerbos.dev/) and introduce yourself!
