Attribute-based access control (ABAC)
Attribute-based access control (ABAC) is a flexible, fine-grained strategy for managing user operations within FaunaDB. ABAC extends the default FaunaDB authentication and authorization mechanisms.
ABAC is an extension of traditional role-based access control (RBAC), where roles can define privileges that can be dynamically determined based on any attribute of the user attempting to access or modify data (e.g. have they completed a specific training?), any attribute of the data to be accessed or modified (e.g. is the document in "review" state?), or contextual information available during a transaction (e.g. is the current time between 10am and 2pm?).
A role defines a set of privileges — specific actions that can be executed on specific resources — and membership — specific identities that should have the specified privileges.
Changes to identities or resources are reflected immediately, as ABAC is evaluated for every query. For example, if Carol’s manager membership is removed, she can no longer access manager-specific resources.
Privileges
A privilege specifies a resource in FaunaDB, where the resource could be a database, collection, document, key, index, function, etc., and a set of pre-defined actions to permit.
Privileges associated with containers (e.g. databases, collections, etc.) also apply to the resources within the container.
The actions available vary according to the target resource:
-
Core schemas (Databases, Collections, Indexes, Functions, Keys, etc.):
create
anddelete
-
Documents:
create
,read
,write
,delete
,history_read
,history_write
-
User-defined functions:
call
For example:
CreateRole({
name: "access_todos",
membership: [{ resource: Collection("users") }],
privileges: [{
resource: Collection("todos"),
actions: {
create: true,
update: true,
delete: true,
write: true
}
}]
})
Action permissions operate as a whitelist: they define permitted actions. The default is that no actions are permitted.
Action permissions can use a predicate function to determine permissions dynamically. For example, an action might be permissible only during a specific period of the day.
For a given resource, there may be multiple privileges defined in separate roles. When a query attempts to operate on a resource, permission is granted to process the action if any privilege grants the action. |
Membership
Membership describes the set of documents that should have the role’s privileges.
Membership is managed with a collection; documents in the collection are members of the role. Typically, a "document" would refer to a "user", but a document can be any record within FaunaDB. For example, FaunaDB access keys can be assigned a role, which is useful for background processes.
Membership can also be controlled with a predicate function for dynamic membership evaluation. For example, membership for some users might be available only during a specific period of the day.
Multiple roles can be associated with a FaunaDB resource, and users can be associated with multiple roles. Attribute-based access is computed for every FaunaDB transaction (including index filtering), and updates to the role configuration take effect immediately. Additionally, action permissions can be computed dynamically via Lambda functions.
For example:
CreateRole({
name: "can_manage_todos",
membership: [
{
resource: Collection("users"),
predicate: Query(Lambda(ref =>
Select(["data", "vip"], Get(ref))
))
}
],
privileges: [
// ...
]
})
Predicate functions
A predicate function is an FQL Lambda function that operates in a
read-only fashion, accepting command-specific arguments, and returning
true
or false
to indicate whether the action is permitted or
prohibited.
The actions and their associated arguments are:
-
create
: the new data that is about to be created. -
read
,history_read
,delete
: the ref to the underlying document. -
read
for indexes: the terms being used to match against the index. -
write
,history_write
: the old data, and the new data. -
call
: the parameters to be passed to the user-defined function.
For example:
CreateRole({
name: "can_manage_todos",
membership: [
// ...
],
privileges: [
{
resource: Collection("todos"),
actions: {
create: Query(Lambda(newData =>
Select(["data", "vip"], Get(Identity()))
)),
// ...
}
}
]
})
Example
Here is a complete role example:
CreateRole({
name: "users",
membership: [
{
// This role will be assigned to all users
// as long as they are active
class: Collection("users"),
predicate: Query(ref =>
Select(["data", "isActive"], Get(ref), false)
)
}
],
privileges: [
{
resource: Collection("todos"),
actions: {
write: {
// Can write to your own data but,
// can't change the owner of the data
predicate: Query((oldData, newData) =>
And(
Equals(
Identity(),
Select(["data", "owner"], oldData)
),
Equals(
Select(["data", "owner"], oldData),
Select(["data", "owner"], newData),
)
)
),
}
}
}
]
})
Summary
All of this flexibility can be very useful, and it can also become very complex. FaunaDB does not favor any particular approach to using ABAC, but we provide the following suggestions that may be useful:
-
It is possible to lock yourself out using your current secret/token. You can recover access via the FaunaDB Console.
-
Only define as many roles as you need.
-
Only provide role membership to those users that need it.
Was this article helpful?
We're sorry to hear that.
Tell us how we can improve!
documentation@fauna.com
Thank you for your feedback!