A little while ago, we released the blog post: Mapping business requirements to authorization policies. 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.
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.
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:
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
:
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 got you. Take a look at this; we can grant access to a principal with the USER
role, by constructing the following request payload:
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:
…
"attr": {
"workspaces": {
"workspaceA": {
"role": "OWNER"
},
"workspaceB": {
"role": "MEMBER"
}
}
Cerbos then uses this to make all the decisions for us, et voila 🎉!
Well, we got you there too 😎. We even wrote an entire blog post 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 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:
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:
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, or alternatively; some of the SDKs have the functionality built in – check out the Go and Javascript 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. It uses a Go backend and a React client; complete with an editing interface using the 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 and introduce yourself!
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.