Skip to main content

ABAC as additional constraints on RBAC in OPA

Attributes are often used to strengthen another policy model like Role-Based Access Control (RBAC).

Attributes in the RBAC model

The generic notion of permitting an action if a subject has a role that grants them to perform action on a resource is extended:

Thinking of it as an overlay, further constraints are imposed on the access control decision.

In the following, we'll develop the additions over RBAC in Rego to implement this model. We start with a simple dynamic RBAC model from the basic variants of our RBAC in Rego How-To:

Base definitions and data

Input:

{
"subject": "alice",
"action": "create",
"resource": { "type": "documents" }
}

The common model:

package common

import rego.v1

subject := input.subject

action := input.action

resource := input.resource

The data stored in OPA's in-memory store:

{
"subject_mappings": {
"alice": [
"admin"
],
"bob": [
"viewer"
],
"catherine": [
"editor"
]
},
"role_mappings": {
"admin": {
"actions": "*",
"resources": "*"
},
"viewer": {
"actions": [
"view",
"list"
],
"resources": [
"documents"
]
},
"editor": {
"actions": [
"view",
"list",
"create",
"delete"
],
"resources": [
"documents"
]
}
}
}

And the basic RBAC definition:

package rbac

import rego.v1

subject := data.common.subject

action := data.common.action

resource := data.common.resource

role_mappings := data.role_mappings

subject_mappings := data.subject_mappings

subject := data.common.subject

default allow := false

allow if {
some role in subject_mappings[subject]
action_matches(action, role_mappings[role].actions)
resource_matches(resource.type, role_mappings[role].resources)
}

action_matches(_, "*")

action_matches(a, actions) if a in actions

resource_matches(_, "*")

resource_matches(r, resources) if r in resources

Attribute-based Conditions as Mandatory Globally Enforced Policies

Conceptually, this extension of conditions imposed on a common model tuple (subject, action, resource) can be constructed as a form of Mandatory Globally Enforced Policies.

Note the added lines for hooking in mandatory rules.

Evaluating this with the currently-provided input will yield "true":

  • "alice" has role "admin"
  • role "admin" is permitted for every action on every resource

Recall that our RBAC document management example has the roles "admin", "editor", and "viewer". Only subjects with the "admin" or "editor" role are permitted to delete a document.

As an extra condition, we'll impose that immutable documents can only be deleted by subjects with role "admin". Constructed as a mandatory rule, it would say

Action delete on an immutable document is only granted to subjects with role "admin".

package mandatory.abac

import rego.v1

action := data.common.action

resource := data.common.resource

roles := data.rbac.roles

default allow := true # matches if resource.immutable is undefined or false

allow := "admin" in roles if {
resource.immutable
action == "delete"
}

And use an entrypoint policy for ensuring that our mandatory rules are met:

package authz

import rego.v1

allow := data.rbac.allow

default decision := false

decision if {
allow # allowed by our base RBAC model

every _, result in data.mandatory { # and all our attribute constraints are met
result.allow
}
}
Example Evaluations

Subject "alice" is permitted to "delete" an immutable "document", via the "admin" role:

package tests

import rego.v1

test_admin_can_delete_immutable_documents if {
data.authz.decision with input.action as "delete"
with input.subject as "alice"
with input.resource as {"type": "documents", "immutable": true}
}

Subject "catherine" is permitted to "delete" a "document", via the "editor" role:

test_editor_can_delete_documents if {
data.authz.decision with input.action as "delete"
with input.subject as "catherine"
with input.resource as {"type": "documents"}
}

However, subject "catherine" is not permitted to "delete" an immutable "document", although carrying the "editor" role:

test_editor_cannot_delete_immutable_documents := output if {
output := data.authz.decision with input.action as "delete"
with input.subject as "catherine"
with input.resource as {"type": "documents", "immutable": true}
}

Attribute-based Conditions as Mixed-in Denies

It is also possible to construct extra conditions on attributes as "deny" rules, following Mixing Allows and Denies.

Our example immutable documents condition is defined in a "deny" rule:

package abac

import rego.v1

resource := data.common.resource

roles := data.rbac.roles

deny contains "immutable documents can only be deleted by admins" if {
resource.immutable
not "admin" in roles
}

and used from an entrypoint policy for resolving the "conflict":

package authz

import rego.v1

allow := data.rbac.allow

deny := data.abac.deny

default decision := false

decision if {
allow # there must be at least one rule that allows the operation
count(deny) == 0 # AND there must be no rules that deny the operation
}

context["reason"] := concat(",", deny) if {
count(deny) > 0
}
Example Evaluations

We can now verify our requirements using the conflict resolution model of layered ABAC.

Subject "alice" is permitted to "delete" an immutable "document", via the "admin" role:

package tests

import rego.v1

test_admin_can_delete_immutable_documents if {
data.authz.decision with input.action as "delete"
with input.subject as "alice"
with input.resource as {"type": "documents", "immutable": true}
}

Subject "catherine" is permitted to "delete" a "document", via the "editor" role:

test_editor_can_delete_documents if {
data.authz.decision with input.action as "delete"
with input.subject as "catherine"
with input.resource as {"type": "documents"}
}

However, subject "catherine" is not permitted to "delete" an immutable "document", although carrying the "editor" role:

test_editor_cannot_delete_immutable_documents := {"decision": output.decision, "context": output.context} if {
output := data.authz with input.action as "delete"
with input.subject as "catherine"
with input.resource as {"type": "documents", "immutable": true}
}
Why two constructions?

Mandatory rules and mixed-in "deny" rules are logically equivalent:

  • Mandatory rules: every mandatory rule needs to be true
  • Mixed-in "deny" rules: none of these rules may be false

By De Morgan's laws, these are equivalent. Which construction to use in your policies is up to you and should be determined by what's most aligned with your requirements and thought process conceptually.

See Wikipedia: De Morgan's laws for more details on the logic beneath this.