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 access 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_id": "<the-user-id>" }
,
the policy would evaluate allowed
to true
if the user id and the resource user_id
match.
allowed {
input.user.id == input.resource.user_id
}
Objects
The directory can store any object, and if you pass in an id in your resource context, you may want to load the object (with its properties) based on this id.
To do this you can use the ds.object
built-in. Objects are resolved based on their object_type
and object_id
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 an id passed in the resource context, you would do it as follows:
user = ds.object({ "object_type": "department", "object_id": input.resource.id })
Check built-in
The ds.check
built-in allows you to query the Topaz directory for a relation or permission between an object an a subject.
allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "owner",
"subject_type": "user",
"subject_id": input.user.id
})
}
The example above will check whether the user represented in the identity context is a department owner
of the department
object with the id passed in as input.resource.id
.
We can also use the ds.check
built-in to check if the object has a permission with the subject.
allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-read",
"subject_type": "user",
"subject_id": input.user.id
})
}
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 to 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 "id"
allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-delete",
"subject_type": "user",
"subject_id": input.user.id
})
}
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({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-delete",
"subject_type": "user",
"subject_id": input.user.id
})
}