How to implement relationship-based access control with Amazon Verified Permissions and Amazon Neptune
This repository provides an AWS CloudFormation template that deploys a sample application demonstrating the implementation of Relationship-based Access Control (ReBAC) using Amazon Verified Permissions and Amazon Neptune, a managed, serverless graph database on AWS. For details about ReBAC, you may refer to the blog post "How to Implement Relationship-based Access Control with Amazon Verified Permissions and Amazon Neptune".
- Introduction
- Overview of a ReBAC application
- Prerequisites
- Deployment on AWS
- Walkthrough
- Extending ReBAC’s capabilities with Attribute-based Access Control (ABAC)
- Cleaning up
- Conclusion
In this solution, relationship data is stored in Neptune. Prior to requesting an authorization decision from Verified Permissions, the application runs a Neptune query that traverses the relationship graph to retrieve the set of principals that have a specific relationship with the resource. The application then constructs an authorization request for Verified Permissions, using the results of this query to populate the entity data in the request.
In the Cedar schema, the resource has an attribute—named for the relationship—that contains the set of principals that have that relationship with the resource. In our sample application, entities of type Video have an attribute called OWNER
, which contains the set of users that have an owner relationship, directly or indirectly, with a video. Each potential relationship is represented by a distinct resource attribute and requires a dedicated query to fetch the set of principals that have that relationship.
- The user authenticates with Amazon Cognito and obtains an access token and an ID token.
- The user accesses the application via Amazon API Gateway with the provided token.
- An application AWS Lambda function traverses the relationship graph in Neptune and returns the set of principals that have a specific relationship with the resource.
- The application Lambda function constructs the requests by putting relationship data in the entities field and passes the requests to Verified Permissions. Verified Permissions acts as the policy decision point (PDP) and evaluates the Cedar policies to arrive at an authorization decision.
- The application Lambda function acts as the policy enforcement point (PEP) to enforce the authorization decision returned by Verified Permissions by allowing or denying access to the API.
To complete this solution, make sure you meet the following prerequisites:
- An AWS account with necessary IAM permissions to manage AWS resources including Amazon Verified Permissions, Amazon Neptune, Amazon Cognito, AWS Lambda, Amazon CloudWatch, and Amazon API Gateway.
- An active terminal.
- Sign in to your AWS account.
- Navigate to
us-east-1
region. - Download the CloudFormation template file here.
- Upload the CloudFormation template file in the CloudFormation create stack page to deploy the solution.
- Enter a name for the CloudFormation stack (for example
aws-blog-avp-rebac
). - Select I acknowledge that AWS CloudFormation might create IAM resources with custom names. at the last step of stack creation.
- Choose Submit. The CloudFormation stack creation process takes around 15–20 minutes to complete.
Before walking through the solution, package gremlinpython 3.7.2
as a Lambda Layer for Lambda functions. A Lambda layer is a .zip file archive that contains supplementary code or data. Layers usually contain library dependencies, a custom runtime, or configuration files. For more information about Lambda Layer, please refer to this link.
- In your terminal, navigate to the
lambda-layer
directory of the repository. This directory contains the scripts that you use to create and package the layer properly.
cd implementing-relationship-based-access-control-with-amazon-verified-permissions-and-amazon-neptune/lambda-layer
- Examine the requirements.txt file. This file defines the dependencies that you want to include in the layer, namely the
gremlinpython
library. You can update this file to include any dependencies that you want to include in your own layer.
gremlinpython==3.7.2
- Ensure that you have permissions to run both scripts.
chmod 744 1-install.sh && chmod 744 2-package.sh
- Run the 1-install.sh script using the following command:
./1-install.sh
This script uses venv to create a Python virtual environment named create-gremlinpython-layer
. It then installs all required dependencies in the create-gremlinpython-layer/lib/python3.9/site-packages
directory.
Example 1-install.sh
python3.9 -m venv create-gremlinpython-layer
source create-gremlinpython-layer/bin/activate
pip install -r requirements.txt
- Run the 2-package.sh script using the following command:
./2-package.sh
This script copies the contents from the create-gremlinpython-layer/lib
directory into a new directory named python
. It then zips the contents of the python
directory into a file named gremlinpython-layer.zip
. This is the .zip file for your layer. You can unzip the file and verify that it contains the correct file structure, as shown in the Layer paths for Python runtimes section.
Example 2-package.sh
mkdir python
cp -r create-gremlinpython-layer/lib python/
zip -r gremlinpython-layer.zip python
- Create the Lambda Layer using the following command:
aws lambda publish-layer-version --layer-name gremlinpython-layer \
--zip-file fileb://gremlinpython-layer.zip \
--compatible-runtimes python3.9 \
--compatible-architectures "x86_64"
Take note of the Amazon Resource Name (ARN) of the Lambda Layer from the response, which looks like arn:aws:lambda:us-east-1:123456789012:layer:python-requests-layer:1
.
- Add the layer to the Lambda Functions
avp-rebac-blog-PetVideosAppApiLambda
andavp-rebac-blog-PetVideosAppBootstrapLambda
using the below commands, replace the layer ARN with the response from the previous step:
aws lambda update-function-configuration --function-name avp-rebac-blog-PetVideosAppApiLambda \
--cli-binary-format raw-in-base64-out \
--layers "arn:aws:lambda:us-east-1:123456789012:layer:python-requests-layer:1"
aws lambda update-function-configuration --function-name avp-rebac-blog-PetVideosAppBootstrapLambda \
--cli-binary-format raw-in-base64-out \
--layers "arn:aws:lambda:us-east-1:123456789012:layer:python-requests-layer:1"
- Invoke the Lambda Function
avp-rebac-blog-PetVideosAppBootstrapLambda
to bootstrap the solution with the following:
aws lambda invoke --function-name avp-rebac-blog-PetVideosAppBootstrapLambda \
--cli-binary-format raw-in-base64-out \
response.json
-
Navigate to the Neptune graph notebook from your AWS console, and open JupyterLab from
Actions
. Open a newPython 3
notebook. -
Visualize the relationship graph bootstrapped in Neptune with the following query:
%%gremlin -p v,oute,inv
g.V().outE().inV().path().by(elementMap('name','directoryId','videoId','ownerName','ownerId','userId','isPublic').order().by(keys))
The below shows the relationship graph for this sample application.
Verified Permissions uses the Cedar policy language to define fine-grained permissions. The default decision for an authorization response is DENY
. The first policy permits a principal to perform actions in the action group OwnerActions
on resources in petVideosDirectory
only when the same principal is included in the set of resource owners.
// Resource owner and related persons can access the resources
permit (
principal,
action in [PetVideosApp::Action::"OwnerActions"],
resource in PetVideosApp::Directory::<petVideosDirectory_UUID> )
when {
resource has owner &&
principal in resource.owner };
The second policy is an ABAC policy that permits a principal to perform actions in the action group PublicActions
on resources in petVideosDirectory
only when the resource has the static attribute isPublic
and its value is true
.
// Allow public access to the resources
permit (
principal,
action in [PetVideosApp::Action::"PublicActions"],
resource in PetVideosApp::Directory::<petVideosDirectory_UUID> )
when {
resource has isPublic &&
resource.isPublic == true };
The blog post "How to Implement Relationship-based Access Control with Amazon Verified Permissions and Amazon Neptune" has provided some guidelines and considerations for desiging your policies and queries for a ReBAC application.
- First go to the CloudFormation console, on the stack Outputs tab, find the below values:
- ApiEndpoint
- LoginPage
- PasswordforCognitoUser
- Go to the LoginPage, login as
alice
,bob
, andcharlie
. Enter the password obtained in Step 1. - Copy and paste the access token and ID token for each user.
- Open your terminal, export the ApiEndpoint, access token and ID token for
alice
,bob
, andcharlie
as variables with the below:
export API_ENDPOINT=<ApiEndpoint>
export ACCESS_TOKEN_ALICE=<access token for alice>
export ID_TOKEN_ALICE=<id token for alice>
export ACCESS_TOKEN_BOB=<access token for bob>
export ID_TOKEN_BOB=<id token for bob>
export ACCESS_TOKEN_CHARLIE=<access token for charlie>
export ID_TOKEN_CHARLIE=<id token for charlie>
First, to test an authenticated user accessing their own resources, access the video aliceCatVideo.mp4
with the ViewVideo
API in your terminal, and using the access token and ID token of alice
. It is expected to be successful with the Cedar policy Resource owner and related persons can access the resources
.
curl -X GET "$API_ENDPOINT/video/get?videoName=aliceCatVideo.mp4" -H "Authorization: Bearer $ACCESS_TOKEN_ALICE" -H "ID-Token: $ID_TOKEN_ALICE" | jq
The application Lambda function constructs the below authorization request to Amazon Verified Permissions, and sends logs to Amazon CloudWatch. You may examine the authorization request in the CloudWatch Log Group of the application Lambda function. An example of authorization requests made to Amazon Verified Permissions is shown below.
{
"policyStoreId": "HhuNNuHBJJYJd4MfEhAZzD",
"identityToken": [ID Token Redacted],
"action": {
"actionType": "PetVideosApp::Action",
"actionId": "ViewVideo"
},
"resource": {
"entityType": "PetVideosApp::Video",
"entityId": "878c101a-ca0e-4733-904d-af3f252abf50"
},
"entities": {
"entityList": [
{
"identifier": {
"entityType": "PetVideosApp::Video",
"entityId": "878c101a-ca0e-4733-904d-af3f252abf50"
},
"attributes": {
"owner": {
"set": [
{
"entityIdentifier": {
"entityType": "PetVideosApp::User",
"entityId": "ap-southeast-2_K9khoza7q|696e7428-e021-708d-7996-2d322fcf4b29"
}
},
{
"entityIdentifier": {
"entityType": "PetVideosApp::User",
"entityId": "ap-southeast-2_K9khoza7q|f91eb468-2001-7080-b860-eff8e20c333c"
}
}
]
},
"isPublic": {
"boolean": false
}
},
"parents": [
{
"entityType": "PetVideosApp::Directory",
"entityId": "8e46133a-18da-47dc-bb7c-5e8640f45043"
},
{
"entityType": "PetVideosApp::Directory",
"entityId": "5e732639-692b-4fb0-8b69-d305926144fe"
}
]
}
]
}
}
There are several things to take note in the above authorization request:
- identityToken: When using Cognito as the identity store with Verified Permissions, access token or ID token can be used as the principal to make authorization requests. User entities are mapped into the format of
<user pool ID>|<sub>
in Verified Permissions. - action: This authorization request is made against the action
ViewVideo
. - resource: This authorization request is made against a specific video ID.
- entities:
- The entities of type
video
have an attribute named for the relationshipOWNER
. Two owner IDs, which denotealice
andcharlie
, are returned by traversing the relationship graph in Neptune. Both IDs are included asowner
attribute, showing the direct and inheritedOWNER
relationship between the principals and resources. - There is a static attribute
isPublic
set asfalse
for this video resource, which denotes it is a private video. - The resource hierarchy of this video resource is shown by including the parent directories
petVideosDirectory
andaliceVideosDirectory
as the parent entities.
- The entities of type
The below response is returned from the application, showing access is allowed with the determining policy ID, which matched the Cedar policy ID of <Resource owner and related persons can access the resources>
.
{
"message": "Access allowed -- determining policy id is Hqm41Phz4JVKNKhmUofB4T"
}
Now, to test an authenticated user accessing others’ resources, access the video aliceCatVideo.mp4
with the ViewVideo
API, and using the access token and ID token of bob
.
curl -X GET "$API_ENDPOINT/video/get?videoName=aliceCatVideo.mp4" -H "Authorization: Bearer $ACCESS_TOKEN_BOB" -H "ID-Token: $ID_TOKEN_BOB" | jq
It is expected to fail as bob
had no direct or inherited OWNER
relationship with the video aliceCatVideo.mp4
.
{
"message": "Access denied -- permissions check failed"
}
Finally, to test the inherited OWNER
relationship between user charlie
and video aliceCatVideo.mp4
, access the video aliceCatVideo.mp4
with the ViewVideo
API, and using the access token and ID token of charlie
.
curl -X GET "$API_ENDPOINT/video/get?videoName=aliceCatVideo.mp4" -H "Authorization: Bearer $ACCESS_TOKEN_CHARLIE" -H "ID-Token: $ID_TOKEN_CHARLIE" | jq
It is expected to be successful. This relationship inheritance is discovered through traversing the relationship graph from aliceCatVideo.mp4
along to the permission boundary, in thie case, the root directory petVideosDirectory
.
{
"message": "Access allowed -- determining policy id is Hqm41Phz4JVKNKhmUofB4T"
}
ReBAC policies are a great fit when you want to create access based on a relationship between the principal and the resource. However, there can be cases where an ABAC policy is a more intuitive expression of a business rule. For example, in the sample application, you might want to grant all principals permission to view any public resource.
With ReBAC, we would need to create a vertex public
in the relationship graph, create MEMBEROF
relationships between all public resources and this vertex, and then create a VIEWER
relationship between all principals and the vertex public
.
With Cedar can create a policy store that is mix of ABAC and ReBAC policies, enabling you to express this access rule with a single ABAC policy that allows public access to resources, as described in the section Cedar Policy Design. This policy grants broad access on any resource with the attribute isPublic
set to true
.
Now you can use the following Gremlin query to modify the static property isPublic
of video resource vertex bobDogVideo.mp4
to true
.
# Set the property "isPublic" to "true" for a specific video
g.V().hasLabel('video').has('name','bobDogVideo.mp4').property(single,'isPublic',true)
You can verify the value of property isPublic
of bobDogVideo.mp4
with the following Gremlin query.
# Verify the value of property "isPublic" of a specific video
g.V().hasLabel('video').has('name','bobDogVideo.mp4').values('isPublic')
The below authorization request is made to Verified Permissions using principal alice
after we have set the isPublic
property of video resource bobDogVideo.mp4
. Please note that alice
has no direct or inherited OWNER
relationship to the resource bobDogVideo.mp4
.
curl -X GET "$API_ENDPOINT/video/get?videoName=bobDogVideo.mp4" -H "Authorization: Bearer $ACCESS_TOKEN_ALICE" -H "ID-Token: $ID_TOKEN_ALICE" | jq
The following authorization request is made to Verified Permissions using principal alice
after you have set the isPublic
property of video resource bobDogVideo.mp4
. In the entities field, there is the attribute isPublic
with true
as the value. With reference to the Cedar policy <Allow public access to the resources>
, the following authorization request returns ALLOW
.
{
"policyStoreId": "HhuNNuHBJJYJd4MfEhAZzD",
"identityToken": [ID Token Redacted],
"action": {
"actionType": "PetVideosApp::Action",
"actionId": "ViewVideo"
},
"resource": {
"entityType": "PetVideosApp::Video",
"entityId": "8646429e-dca1-4229-aa26-9afcf75f053b"
},
"entities": {
"entityList": [
{
"identifier": {
"entityType": "PetVideosApp::Video",
"entityId": "8646429e-dca1-4229-aa26-9afcf75f053b"
},
"attributes": {
"owner": {
"set": [
{
"entityIdentifier": {
"entityType": "PetVideosApp::User",
"entityId": "ap-southeast-2_K9khoza7q|b99ee448-f081-7078-5343-826a680f781f"
}
},
{
"entityIdentifier": {
"entityType": "PetVideosApp::User",
"entityId": "ap-southeast-2_K9khoza7q|f91eb468-2001-7080-b860-eff8e20c333c"
}
}
]
},
"isPublic": {
"boolean": true
}
},
"parents": [
{
"entityType": "PetVideosApp::Directory",
"entityId": "b1551923-838e-43dc-946c-9fc63a85f445"
},
{
"entityType": "PetVideosApp::Directory",
"entityId": "5e732639-692b-4fb0-8b69-d305926144fe"
}
]
}
]
}
}
To avoid incurring future charges, make sure to clean up all the AWS resources that you created as part of this post. Follow the below the clean up the resources:
- Remove the VPC access of AWS Lambda
avp-rebac-blog-PetVideosAppBootstrapLambda
andavp-rebac-blog-PetVideosAppApiLambda
with the following:
aws lambda update-function-configuration --function-name avp-rebac-blog-PetVideosAppBootstrapLambda --vpc-config SubnetIds=[],SecurityGroupIds=[] && aws lambda update-function-configuration --function-name avp-rebac-blog-PetVideosAppApiLambda --vpc-config SubnetIds=[],SecurityGroupIds=[]
- Verify the Elastic Network Interfaces of both Lambdas are deleted:
aws ec2 describe-network-interfaces --filters "Name=interface-type,Values=lambda"
- After that you can delete the CloudFormation stack with the following:
aws cloudformation delete-stack --stack-name <stack name>
In this post, we showed you what ReBAC is and its benefits and demonstrated the implementation of ReBAC using Amazon Verified Permissions and Amazon Neptune. We also reviewed Cedar policy design patterns and considerations, in addition to the authorization request structure for a ReBAC application. You also saw how to combine ReBAC policies with ABAC policies.
Refer to the links here to learn more about Cedar Policies, Amazon Verified Permissions, and Amazon Neptune.
See CONTRIBUTING for more information.
This library is licensed under the MIT-0 License. See the LICENSE file.