In the realm of information technology (IT) and cybersecurity, access control plays a pivotal role in ensuring the confidentiality, integrity, and availability of sensitive resources. Let's delve into why access control policies are crucial for protecting your valuable data.
Access control is a mechanism that regulates who or what can view, use, or access a specific resource within a computing environment. Its primary goal is to minimize security risks by ensuring that only authorized users, systems, or services have access to the resources they need. But it's more than just granting or denying access, it involves several key components:
- Authentication: Verifying the identity of an individual or system.
- Authorization: Determining what actions or operations an actor is allowed to perform.
- Access: Granting or denying access based on authorization.
- Management: Administering access rights and permissions.
- Audit: Tracking and monitoring access patterns for accountability.
-
Mitigating Security Risks: Cybercriminals are becoming increasingly sophisticated, employing advanced techniques to breach security systems. By controlling who has access to your database, you significantly reduce the risk of unauthorized access, both from external attackers and insider threats.
-
Compliance with Regulations: Various regulatory requirements, such as the General Data Protection Regulation (GDPR) and the Health Insurance Portability and Accountability Act (HIPAA), mandate stringent access control measures to protect personal data. Implementing access control ensures compliance with these regulations.
-
Preventing Data Breaches: Access control acts as a proactive measure to deter, detect, and prevent unauthorized access. It ensures that only those with the necessary permissions can access sensitive data or services.
-
Managing Complexity: Modern IT infrastructure, including cloud computing and mobile devices, has exponentially increased the number of access points. Technologies like identity and access management (IAM) and approaches like zero trust help manage this complexity effectively.
Several access control models exist, including:
-
Role-Based Access Control (RBAC): Assigns permissions to roles, roles then are granted to users. A user's active role then defines their access. (e.g., admin, user, manager).
-
Attribute-Based Access Control (ABAC): Considers various attributes (e.g., user attributes, resource attributes) for access decisions.
-
Discretionary Access Control (DAC): Users with sufficient permissions (resource owners) are to grant / share an object with other users.
-
Mandatory Access Control (MAC): Users are not allowed to grant access to other users. Permissions are granted based on a minimum role / hierarchy (security labels and clearances) that must be met.
-
Policy-Based Access Control (PBAC): Enforces access based on defined policies.
-
Relation-Based Access Control (ReBac): Relations between objects and users in the system are used to derive their permissions.
-
Note: DefraDB access control rules strongly resembles Discretionary Access Control (DAC), which is implemented through a Relation-Based Access Control System (ReBac) Engine
- Distributed IT Environments: Cloud computing and remote work create new challenges.
- Rise of Mobility: Mobile devices in the workplace add complexity.
- Password Fatigue: Balancing security with usability.
- Data Governance: Ensuring visibility and control.
- Multi-Tenancy: Managing complex permissions in SaaS applications.
A robust access control policy system is your first line of defense against unauthorized access and data breaches.
In 2019, Google published their Zanzibar paper, a paper explaining how they handle authorization across their many services. It implements an enhanced type of access control list (ACL), which supports both object and subject grouping. Zanzibar's design is inspired on the relationship-based access control model, rather than the widely adopted role-based access control, which is incompatible with ACLs. Relationship-Based Access Control (ReBAC) establishes an authorization model where a subject's permission to access an object is defined by the presence of relationships between those subjects and objects.
The way Zanzibar works is it exposes an API with (mainly) operations to manage Relationships (tuples) and Verify Access Requests (can Bob do X) through the Check call. A tuple includes subject, relation, and object. The Check call performs Graph Search over the tuples to find a path between the user and the object, if such a path exist then according to RelBAC the user has the queried permission. It operates as a Consistent and Partition-Tolerant System.
However the Zanzibar API is centralized, so we (Source Network) created a decentralized implementation of Zanzibar called Zanzi. Which is powered by our SourceHub trust protocol. Zanzi is a general purpose Zanzibar implementation which operates over a KV persistence layer.
DefraDB wraps the local and remote SourceHub ACP Modules to bring all that magic to DefraDB.
In order to setup the relation based access control, SourceHub requires an agreed upon contract which models the relations, permissions, and actors. That contract is refered to as a SourceHub Policy. The policy model's all the relations and permissions under a resource.
A resource corresponds to that "thing" that we want to gate the access control around. This can be a Type, Container, Schema, Shape or anything that has Objects that need access control. Once the policy is finalized, it has to be uploaded to the SourceHub Module so it can be used.
Once the Policy is uploaded to the SourceHub Module then an Actor can begin registering the Object for access control by linking to a Resource that exists on the uploaded Policy.
After the Object is registered successfully, the Actor will then get a special built-in relation with that Object called the "owner" relation. This relation is given to the Registerer of an Object.
Then an Actor can issue Check calls to see if they have access to an Object.
In DefraDB's case we wanted to gate access control around the Documents that belonged to a specific Collection. Here, the Collection (i.e. the type/shape of the Object) can be thought of as the Resource, and the Documents are the Objects.
We also want the ability to do a more granular access control than just DAC. Therefore we have Field level access control for situations where some fields of a Document need to be private, while others do not. In this case the Document becomes the Resource and the Fields are the Objects being gated.
SourceHub Policies are too flexible (atleast until the ability to define Meta Policies is implemented). This is because SourceHub leaves it up to the user to specify any type of Permissions and Relations. However for DefraDB, there are certain guarantees that MUST be maintained in order for the Policy to be effective. For example the user can input any name for a Permission, or Relation that DefraDB has no knowledge of. Another example is when a user might make a Policy that does not give any Permission to the owner. Which means in the case of DAC no one will have any access to the Document they created.
Therefore There was a very clear need to define some rules while writing a Resource in a Policy which will be used with DefraDB's DAC, FAC, or NAC. These rules will guarantee that certain Required Permissions will always be there on a Resource and that Owner has the correct Permissions.
We call these rules DPI A.K.A DefraDB Policy Interface.
- 'SourceHub Address' is a
Bech32Address with a specific SourceHub prefix. - 'Identity' is a combination of SourceHub Address and a Key-Pair Signature.
- 'DPI' means 'DefraDB Policy Interface'.
- 'Partially-DPI' policy means a policy with at least one DPI compliant resource.
- 'Permissioned Collection' means to have a policy on the collection, like:
@policy(id:".." resource: "..") - 'Permissioned Request' means to have a request with a SourceHub Identity.
To qualify as a DPI-compliant resource, the following rules MUST be satisfied:
- The resource must encompass all the required permissions under the
permissionsattribute.
For a Policy to be DPI compliant for DAC, all of its resources must be DPI compliant.
To be Partially-DPI at least one of its resource must be DPI compliant.
All mandatory permissions are:
- Specified in the
dpi.gofile within the variabledpiRequiredPermissions.
- Check out tests here: tests/integration/acp/dac/link_collection
- The tests linked are broken into
accept_*_test.goandreject_*_test.gofiles. - Accepted tests document the valid DPIs (as the collection is accepted).
- Rejected tests document invalid DPIs (as the collection is rejected).
- There are also some Partially-DPI tests that are both accepted and rejected depending on the resource.
Here are some valid expression examples. Assuming these expr are under a required permission label:
expr:expr: readerexpr: reader + writerexpr: reader & admin
To perform authenticated operations you will need to generate a secp256k1 key pair.
The command below will generate a new secp256k1 private key and print the 256 bit X coordinate as a hexadecimal value.
openssl ecparam -name secp256k1 -genkey | openssl ec -text -noout | head -n5 | tail -n3 | tr -d '\n:\ 'Copy the private key hex from the output.
read EC key
e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acUse the private key to generate authentication tokens for each request.
defradb client ... --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acWe have in examples/policy/dac_policy.yml:
name: An Example Policy
description: A Valid DefraDB Policy Interface (DPI)
actor:
name: actor
resources:
users:
permissions:
read:
expr: owner + reader + updater + deleter
update:
expr: owner + updater
delete:
expr: owner + deleter
relations:
owner:
types:
- actor
reader:
types:
- actor
updater:
types:
- actor
deleter:
types:
- actorCLI Command:
defradb client acp document policy add -f examples/policy/dac_policy.yml --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"PolicyID": "50d354a91ab1b8fce8a0ae4693de7616fb1d82cfc540f25cfbe11eb0195a5765"
}We have in examples/collection/permissioned/users.graphql:
type Users @policy(
id: "50d354a91ab1b8fce8a0ae4693de7616fb1d82cfc540f25cfbe11eb0195a5765",
resource: "users"
) {
name: String
age: Int
}CLI Command:
defradb client collection add -f examples/collection/permissioned/users.graphqlResult:
[
{
"Name": "Users",
"ID": 1,
"RootID": 1,
"CollectionVersionID": "bafyreifnbhwntycylk2l6n4khiocdt3vks46tizjdaz6yx4tsmdjtdtlma",
"Sources": [],
"Fields": [
{
"Name": "_docID",
"ID": 0
},
{
"Name": "age",
"ID": 1
},
{
"Name": "name",
"ID": 2
}
],
"Indexes": [],
"Policy": {
"ID": "50d354a91ab1b8fce8a0ae4693de7616fb1d82cfc540f25cfbe11eb0195a5765",
"ResourceName": "users"
}
}
]
CLI Command:
defradb client document add --collection-name Users '[{ "name": "SecretShahzad" }, { "name": "SecretLone" }]' --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acCLI Command:
defradb client document add --collection-name Users '[{ "name": "PublicShahzad" }, { "name": "PublicLone" }]'CLI Command:
defradb client query '{ Users { _docID name } }'Result:
{
"data": {
"Users": [
{ "_docID": "bae-63ba68c9-78cb-5060-ab03-53ead1ec5b83", "name": "PublicShahzad" },
{ "_docID": "bae-ba315e98-fb37-5225-8a3b-34a1c75cba9e", "name": "PublicLone" }
]
}
}defradb client query '{ Users { _docID name } }' --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"data": {
"Users": [
{ "_docID": "bae-63ba68c9-78cb-5060-ab03-53ead1ec5b83", "name": "PublicShahzad" },
{ "_docID": "bae-a5830219-b8e7-5791-9836-2e494816fc0a", "name": "SecretShahzad" },
{ "_docID": "bae-ba315e98-fb37-5225-8a3b-34a1c75cba9e", "name": "PublicLone" },
{ "_docID": "bae-eafad571-e40c-55a7-bc41-3cf7d61ee891", "name": "SecretLone" }
]
}
}CLI Command:
defradb client document get --collection-name Users "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"_docID": "bae-a5830219-b8e7-5791-9836-2e494816fc0a",
"name": "SecretShahzad"
}CLI Command:
defradb client document get --collection-name Users "bae-a5830219-b8e7-5791-9836-2e494816fc0a"Error:
Error: document not found or not authorized to access
CLI Command:
defradb client document get --collection-name Users "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --identity 4d092126012ebaf56161716018a71630d99443d9d5217e9d8502bb5c5456f2c5Error:
Error: document not found or not authorized to access
CLI Command:
defradb client document update --collection-name Users --docID "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --updater '{ "name": "SecretUpdatedShahzad" }' --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"Count": 1,
"DocIDs": [
"bae-a5830219-b8e7-5791-9836-2e494816fc0a"
]
}CLI Command:
defradb client document get --collection-name Users "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"_docID": "bae-a5830219-b8e7-5791-9836-2e494816fc0a",
"name": "SecretUpdatedShahzad"
}CLI Command:
defradb client document delete --collection-name Users --docID "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"Count": 1,
"DocIDs": [
"bae-a5830219-b8e7-5791-9836-2e494816fc0a"
]
}CLI Command:
defradb client document get --collection-name Users "bae-a5830219-b8e7-5791-9836-2e494816fc0a" --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acError:
Error: document not found or not authorized to access
To share a document (or grant a more restricted access) with another actor, we must add a relationship between the actor and the document. In order to make the relationship we require all of the following:
- Target DocID: The
docIDof the document we want to make a relationship for. - Collection Name: The name of the collection that has the
Target DocID. - Relation Name: The type of relation (name must be defined within the linked policy on collection).
- Target Identity: The identity of the actor the relationship is being made with.
- Requesting Identity: The identity of the actor that is making the request.
Note:
- ACP must be available (i.e. ACP can not be disabled).
- The collection with the target document must have a valid policy and resource linked.
- The target document must be registered with ACP already (private document).
- The requesting identity MUST either be the owner OR the manager (manages the relation) of the resource.
- If the specified relation was not granted the miminum DPI permissions (read or update or delete) within the policy, and a relationship is formed, the subject/actor will still not be able to access (read or update or delete) the resource.
- If the relationship already exists, then it will just be a no-op.
Consider the following policy that we have under examples/policy/dac_policy_with_manages.yml:
name: An Example Policy
description: A Policy
actor:
name: actor
resources:
users:
permissions:
read:
expr: owner + writer + updater + deleter + reader
update:
expr: owner + writer + updater
delete:
expr: owner + writer + deleter
nothing:
expr: dummy
relations:
owner:
types:
- actor
reader:
types:
- actor
updater:
types:
- actor
deleter:
types:
- actor
writer:
types:
- actor
admin:
manages:
- reader
types:
- actor
dummy:
types:
- actorAdd the policy:
defradb client acp document policy add -f examples/policy/dac_policy_with_manages.yml \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"PolicyID": "ec11b7e29a4e195f95787e2ec9b65af134718d16a2c9cd655b5e04562d1cabf9"
}Add collection, linking to the users resource and our policyID:
defradb client collection add '
type Users @policy(
id: "ec11b7e29a4e195f95787e2ec9b65af134718d16a2c9cd655b5e04562d1cabf9",
resource: "users"
) {
name: String
age: Int
}
'Result:
[
{
"Name": "Users",
"ID": 1,
"RootID": 1,
"CollectionVersionID": "bafyreifnbhwntycylk2l6n4khiocdt3vks46tizjdaz6yx4tsmdjtdtlma",
"Sources": [],
"Fields": [
{
"Name": "_docID",
"ID": 0,
"Kind": null,
"RelationName": null,
"DefaultValue": null
},
{
"Name": "age",
"ID": 1,
"Kind": null,
"RelationName": null,
"DefaultValue": null
},
{
"Name": "name",
"ID": 2,
"Kind": null,
"RelationName": null,
"DefaultValue": null
}
],
"Indexes": [],
"Policy": {
"ID": "ec11b7e29a4e195f95787e2ec9b65af134718d16a2c9cd655b5e04562d1cabf9",
"ResourceName": "users"
},
"IsMaterialized": true
}
]Add a private document:
defradb client document add --collection-name Users '[{ "name": "SecretShahzadLone" }]' \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acOnly the owner can see it:
defradb client query '{ Users { _docID name } }' --identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"data": {
"Users": [
{ "_docID": "bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c", "name": "SecretShahzadLone" }
]
}
}Another actor can not:
defradb client query '{ Users { _docID name } }' --identity 4d092126012ebaf56161716018a71630d99443d9d5217e9d8502bb5c5456f2c5Result:
{
"data": {
"Users": []
}
}Now let's make the other actor a reader of the document by adding a relationship:
defradb client acp document relationship add \
--collection Users \
--docID bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c \
--relation reader \
--actor did:key:z7r8os2G88XXBNBTLj3kFR5rzUJ4VAesbX7PgsA68ak9B5RYcXF5EZEmjRzzinZndPSSwujXb4XKHG6vmKEFG6ZfsfcQn \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"ExistedAlready": false
}Note: If the same relationship is added again the ExistedAlready would then be true, indicating no-op
Now the other actor can read:
defradb client query '{ Users { _docID name } }' --identity 4d092126012ebaf56161716018a71630d99443d9d5217e9d8502bb5c5456f2c5Result:
{
"data": {
"Users": [
{ "_docID": "bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c", "name": "SecretShahzadLone" }
]
}
}But, they still can not perform an update as they were only granted a read permission (through reader relation):
defradb client document update --collection-name Users --docID "bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c" \
--identity 4d092126012ebaf56161716018a71630d99443d9d5217e9d8502bb5c5456f2c5 '{ "name": "SecretUpdatedShahzad" }'Result:
Error: document not found or not authorized to accessSometimes we might want to give a specific access (i.e. form a relationship) not just with one identity, but with
any identity (includes even requests with no-identity).
In that case we can specify "*" instead of specifying an explicit actor:
defradb client acp document relationship add \
--collection Users \
--docID bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c \
--relation reader \
--actor "*" \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"ExistedAlready": false
}**Note: specifying * does not overwrite any previous formed relationships, they will remain as is **
To revoke access to a document for an actor, we must delete the relationship between the actor and the document. In order to delete the relationship we require all of the following:
- Target DocID: The docID of the document we want to delete a relationship for.
- Collection Name: The name of the collection that has the Target DocID.
- Relation Name: The type of relation (name must be defined within the linked policy on collection).
- Target Identity: The identity of the actor the relationship is being deleted for.
- Requesting Identity: The identity of the actor that is making the request.
Notes:
- ACP must be available (i.e. ACP can not be disabled).
- The target document must be registered with ACP already (policy & resource specified).
- The requesting identity MUST either be the owner OR the manager (manages the relation) of the resource.
- If the relationship record was not found, then it will be a no-op.
Consider the same policy and added relationship from the previous example in the section above where we learnt how to share the document with other actors.
We made the document accessible to an actor by adding a relationship:
defradb client acp document relationship add \
--collection Users \
--docID bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c \
--relation reader \
--actor did:key:z7r8os2G88XXBNBTLj3kFR5rzUJ4VAesbX7PgsA68ak9B5RYcXF5EZEmjRzzinZndPSSwujXb4XKHG6vmKEFG6ZfsfcQn \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"ExistedAlready": false
}Similarly, in order to revoke access to a document we have the following command to delete the relationship:
defradb client acp document relationship delete \
--collection Users \
--docID bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c \
--relation reader \
--actor did:key:z7r8os2G88XXBNBTLj3kFR5rzUJ4VAesbX7PgsA68ak9B5RYcXF5EZEmjRzzinZndPSSwujXb4XKHG6vmKEFG6ZfsfcQn \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"RecordFound": true
}Note: If the same relationship is deleted again (or a record for a relationship does not exist) then the RecordFound
would be false, indicating no-op
Now the other actor can no longer read:
defradb client query '{ Users { _docID name } }' --identity 4d092126012ebaf56161716018a71630d99443d9d5217e9d8502bb5c5456f2c5Result:
{
"data": {
"Users": []
}
}We can also revoke the previously granted implicit relationship which gave all actors access using the "" actor. Similarly we can just specify "" to revoke all access given to actors implicitly through this relationship:
defradb client acp document relationship delete \
--collection Users \
--docID bae-ff3ceb1c-b5c0-5e86-a024-dd1b16a4261c \
--relation reader \
--actor "*" \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acResult:
{
"RecordFound": true
}**Note: Deleting with* does not remove any explicitly formed relationships, they will remain as they were **
To perform authenticated operations you will need to build and sign a JWT token with the following required fields:
subpublic key of the identityaudhost name of the defradb api- The
expandnbffields should also be set to short-lived durations.
Additionally, if using SourceHub ACP, the following must be set:
issshould be set to the user's DID, e.g."did:key:z6MkkHsQbp3tXECqmUJoCJwyuxSKn1BDF1RHzwDGg9tHbXKw"iatshould be set to the current unix timestampauthorized_accountshould be set to the SourceHub address of the account signing SourceHub transactions on your behalf - WARNING - this will currently enable this account to make any SourceHub as your user for the lifetime of the token, so please only set this if you fully trust the node/account.
The JWT must be signed with the secp256k1 private key of the identity you wish to perform actions as.
The signed token must be set on the Authorization header of the HTTP request with the bearer prefix prepended to it.
If authentication fails for any reason a 403 forbidden response will be returned.
In addition to document access control (DAC) we have another type of access control called node access control (NAC).
In this case the entire Node would be the Resource Object that we we will gate.
In order, to start node acp the user needs to specify an "owner" identity at node startup and indicate they want to enable node acp for the node.
This can be done like this:
defradb start --no-keyring --node-acp-enable true \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acNote: The owner will by default have access to all the permissions.
The state of node access control will be saved, such that on next startup the node will automatically recover the previous state the node acp was left in. So, the user doesn't need to specify the are enabling node acp on subsequent starts.
While we do have an option to purge the entire node acp state to delete all relationships and reset the owner,
sometimes you want to preserve the entire state of the node acp while temporarily disabling it. This can be
done using the disable command like so:
This can be done like this:
defradb client acp node disable \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acNote: The identity MUST have the permission to be able to disable node acp.
To re-enable node acp when it is temporarily disabled, use the re-enable command like so:
defradb client acp node re-enable \
--identity e3b722906ee4e56368f581cd8b18ab0f48af1ea53e635e3f7b8acd076676f6acNote: The identity MUST have the permission to be able to re-enable node acp.
Node ACP could be in various states, for example it could be enabled, disabled temporarily,
or not configured (in a clean state).
To check the status of node acp, we have a utility status command:
This can be done like this:
defradb client acp node statusYou can bypass DAC checks for performance if you use owner identity or any identity that has the bypass-dac
permission.
Note: When NAC is not enabled, bypass-dac permission is not automatically granted like other permissions,
so DAC checks won't be bypassed if conditions that enable DAC are satisfied. We do plan to give users the
ability to be able to disable DAC in the future (which would bypass DAC).
- If using Local ACP, P2P will only work with collections that do not have a policy assigned. If you wish to use ACP on collections connected to a multi-node network, please use SourceHub ACP.
The following features currently don't work with ACP, they are being actively worked on.
The following features may have undefined/unstable behavior until they are properly tested: