Introduction
An authorization policy (or simply, a policy) is expressed in a declarative language called rego.
A policy can include one or more rego
files, and one or more JSON data files.
A rego
file has a package
directive which sets the namespace for the rego file, and defines one or more decisions.
package sample.GET.api.orders
allowed {
true
}
In the rego file above, the package
name follows the Topaz convention:
policy-root
.HTTP method
.HTTP route
Decision
A decision is an output from the evaluation of a policy. The policy above exports a
decision called allowed
, and sets its value to true
.
The policy below exports the same decision (allowed
), has a default value of false
for this
decision, and a rule that sets allowed
to true only if the logged-in users's department
property
is equal to "Sales"
:
package sample.GET.api.orders
default allowed = false
allowed {
input.user.properties.department == "Sales"
}
This is known as attribute-based accesc control, or ABAC, because the department
property is an attribute of the user.
User context
The Topaz authorization APIs take an identity context, which allows the caller to pass in the identity of the user as a JWT or a Subject. Topaz will automatically resolve the user and make it available as input.user
, so that the policy can access any of the user's properties.
In the policy above, we were able to use the department
property to help compute the allowed
decision. This is an example of User context that is used in a policy. If you import your identities from your identity provider, any properties they contain about users will be available to your policy.
In addition, you can add properties on your objects in an extensible JSON property bag, and use these properties in authorization decisions.
Resource context
Policies can also use resource context in their evaluation process. If we added the allowed clause
below to our policy, and the caller included a resource context that contains { "user_key": "<the-user-key>" }
,
the policy would evaluate allowed
to true
if the user key and the resource user_key
match.
allowed {
input.user.key == input.resource.user_key
}
Objects
The directory can store any object, and if you pass in a key in your resource context, you may want to load the object (with its properties) based on this key.
To do this you can use the ds.object
built-in. Objects are resolved based on their type
and key
value.
You don't need to do this for the user, since Topaz automatically does it for you. But if you wanted to load a department
resource based on a key passed in the resource context, you would do it as follows:
user = ds.object({ "type": "department", "key": input.resource.key })
Check Relation built-in
The ds.check_relation
built-in allows you to query the Topaz directory for a relationship between an object an a subject. Once we resolve the object and the subject, we can use the ds.check_relation
built-in to check if the object has a relationship with the subject.
allowed {
ds.check_relation({
"subject": {
"key": input.user.key,
"type": "user"
},
"object": {
"key": input.resource.key,
"type": "department"
},
"relation": {
"object_type": "department",
"name": "owner"
}
})
}
The example above will check whether the user represented in the identity context is a department owner
of the department
object with the key passed in as input.resource.key
.
Check Permission built-in
Similar to the ds.check_relation
built-in, the ds.check_permission
built-in allows you to query the Topaz directory for a permission that is associated with a relationship between an object and a subject. Once we resolve the object and the subject, we can use the ds.check_permission
built-in to check if the object has a permission with the subject.
allowed {
ds.check_permission({
"subject": {
"key": input.user.key,
"type": "user"
},
"object": {
"key": input.resource.key,
"type": "department"
},
"permission": {
"name": "can-read"
}
})
}
Example policy
The policy below is a sample policy that will take advantage of the relation and permission built-ins to check if a user has permission to delete a department. It will also combine an ABAC rule with that will check if the user is a member of the "IT" department. The resulting policy will be true if either of these conditions is met.
package sample.DELETE.api.departments
default allowed = false
## allow if the department property of the user is "IT"
allowed {
input.user.properties.department == "IT"
}
## allow if the user has the "can-delete" permission on the department identified by "key"
allowed {
ds.check_permission({
"subject": {
"key": input.user.key,
"type": "user"
},
"object": {
"key": input.resource.key,
"type": "department"
},
"permission": {
"name": "can-delete"
}
})
}
To combine the rules so that BOTH must be true for the allowed
decision to evaluate to true
, you can put them in the same allowed
block: these conditions are AND
-ed together.
## allow if BOTH conditions are true
allowed {
input.user.properties.department == "IT"
ds.check_permission({
"subject": {
"key": input.user.key,
"type": "user"
},
"object": {
"key": input.resource.key,
"type": "department"
},
"permission": {
"name": "can-delete"
}
})
}