Example 3: Multi-tenant access control with RBAC
To elaborate on the previous RBAC example, you can expand your requirements to include SaaS multi-tenancy, which is a common requirement for SaaS providers. In multi-tenant solutions, resource access is always provided on behalf of a given tenant. That is, users of Tenant A cannot view the data of Tenant B, even if that data is logically or physically collocated in a system. The following example illustrates how you can implement tenant isolation by using multiple Verified Permissions policy stores, and how you can employ user roles to define permissions within the tenant.
Using the Per Tenant Policy Store design pattern is a best practice for
maintaining tenant isolation while implementing access control with Verified Permissions. In this
scenario, Tenant A and Tenant B user requests are verified against separate policy
stores, DATAMICROSERVICE_POLICYSTORE_A and
DATAMICROSERVICE_POLICYSTORE_B, respectively. For more information
about Verified Permissions design considerations for multi-tenant SaaS applications, see the Verified Permissions multi-tenant design
considerations section.
The following policy resides in the DATAMICROSERVICE_POLICYSTORE_A
policy store. It verifies that the principal will be a part of the group
allAccessRole of type Role. In this case, the
principal will be allowed to perform the viewData and
updateData actions on all resources that are associated with Tenant
A.
permit ( principal in MultitenantApp::Role::"allAccessRole", action in [ MultitenantApp::Action::"viewData", MultitenantApp::Action::"updateData" ], resource );
The following policies reside in the DATAMICROSERVICE_POLICYSTORE_B
policy store. The first policy verifies that the principal is part of the
updateDataRole group of type Role. Assuming that is
the case, it gives permission to principals to perform the updateData
action on resources that are associated with Tenant B.
permit ( principal in MultitenantApp::Role::"updateDataRole", action == MultitenantApp::Action::"updateData", resource );
This second policy mandates that principals that are a part of the
viewDataRole group of type Role should be allowed to
perform the viewData action on resources that are associated with
Tenant B.
permit ( principal in MultitenantApp::Role::"viewDataRole", action == MultitenantApp::Action::"viewData", resource );
The authorization request made from Tenant A needs to be sent to the
DATAMICROSERVICE_POLICYSTORE_A policy store and verified by the
policies that belong to that store. In this case, it's verified by the first policy
discussed earlier as part of this example. In this authorization request, the
principal of type User with a value of Alice is requesting
to perform the viewData action. The principal
belongs to the group allAccessRole of type Role. Alice is
trying to perform the viewData action on the SampleData
resource. Because Alice has the allAccessRole
role, this evaluation results in an ALLOW decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_A", "principal": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "viewData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "allAccessRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }
If, instead, you view a request made from Tenant B by User Bob, you
will see something like the following authorization request. The request is sent to
the DATAMICROSERVICE_POLICYSTORE_B policy store because it originates
from Tenant B. In this request, the principal Bob wants to perform the
action updateData on the resource SampleData. However,
Bob is not a part of a group that has access to the action
updateData on that resource. Therefore, the request results in a
DENY decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_B", "principal": { "entityType": "MultitenantApp::User", "entityId": "Bob" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "updateData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Bob" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "viewDataRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }
In this third example, User Alice tries to perform the
viewData action on the resource SampleData. This
request is directed to the DATAMICROSERVICE_POLICYSTORE_A policy store
because the principal Alice belongs to Tenant A. Alice is
a part of the group allAccessRole of the type Role, which
permits her to perform the viewData action on resources. As such, the
request results in an ALLOW decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_A", "principal": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "viewData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "allAccessRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }