Looking for Senior AWS Serverless Architects & Engineers?
Let's TalkOverview
If you are familiar with a full-stack apps framework that performs temporary credentials like AWS Amplify, Baseline, or Appycloud, I would like to share how to restrict and optimize your data permission with Cognito Identity Pool. This post will focus on the data in DynamoDB and S3. I don’t have any experience with other data services that support this approach.
Let me start with a common IAM Policy to grant access to a Dynamodb Table and S3 Bucket:
It will give the associated Role access to send the request to any items/objects in a particular DynamoDB table/S3 bucket. But hold a second, now let’s compare this to the one below:
So, in particular use cases, sometimes we need to restrict the items/objects belonging to their role identity. Can I call it fine-grained accessibility from now on? CMIIW!
Well, what is fine-grained accessibility and how does it work? Let me provide a diagram to help you understand the basic flow:
The above diagram describes how each user has restricted access to its own Cognito identity ID as the key property. When the requests come from unmatched identity keys, they will be blocked. For example, if User 1 is trying to access any Dynamodb items where the PK matches to Identity 1, it succeeds. Another example, if User 2 wants to query the list of objects inside a folder whose name matches Identity 2, it will work as well. Otherwise, they will be introduced as “unauthorized access”.
Implementation
What are the requirements?
- AWS Account
- AWS Dynamodb
- AWS Cognito Identity
- AWS IAM Role Policy
Create a Dynamodb table, for example, named “demo-dynamodb-table” with a table schema that includes both a partition key and a sort key.
Next, create a new Cognito Identity to manage the user identities. For now, let’s keep it simple by setting up the unauthenticated role (guest access), and identifying the pool name, for example, "demopoolidentity."
Once the Identity Pool is created, we will adjust the IAM Role Policy document to implement restricted conditions. Go to IAM console, open the “demo-unauth-identity-role,” and edit the Permissions policies to be:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": ["arn:aws:dynamodb:<REGION>:<AWS_ACCOUNT_ID>:table/demo-dynamodb-table"],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
}
}
}
]
}
Now we are ready to validate the above configuration, here we go:
- Get the identity:
aws --profile <AWS_PROFILE> --region <REGION> cognito-identity get-id --identity-pool-id <IDENTITY_POOL_ID>
- Pass the unique Identity ID from the output in the get-credentials-for-identity API call to obtain temporary AWS credentials:
aws --profile <AWS_PROFILE> --region <REGION> cognito-identity get-credentials-for-identity --identity-id <IDENTITY_ID>
- Set environment variables for the CLI with the obtained temporary security credentials:
export AWS_ACCESS_KEY_ID="<ACCESS_KEY>"
export AWS_SECRET_ACCESS_KEY="<SECRET_KEY>"
export AWS_SESSION_TOKEN="<SESSION_TOKEN>"
- Test by making DynamoDB calls for that user identity ID:
aws --region <REGION> dynamodb put-item --table-name demo-dynamodb-table --item '{"PK": {"S": "<IDENTITY_ID>"}, "SK": {"S": "test 1"}}'
- Test unauthorized calls with another user identity ID or random value for the PK attribute. It will throw an error message:
An error occurred (AccessDeniedException) when calling the PutItem operation: User: arn:aws:sts::xxxxx:assumed-role/demo-unauth-identity-role/CognitoIdentityCredentials is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:xxxx:xxxxxxx:table/demo-dynamodb-table because no identity-based policy allows the dynamodb:PutItem action
aws --region <REGION> dynamodb put-item --table-name demo-dynamodb-table --item '{"PK": {"S": "unauthorized_string"}, "SK": {"S": "test 2"}}'
The above sample is very basic, we can manage the condition based on the Identity Pool ID instead, for example, to allow one admin user access to all items. We can also manage the condition based on Authenticated roles, Defined attributes, etc. Please find out more best practices from the provided links on the last page.
Conclusion
When we use a full stack apps framework, most of them are set with common permissions. I am not saying it’s bad. I would prefer to say, there is an elegant way to improve the initial policy to make our apps more secure. I don’t need to write bulk codes to have a restriction flow for resources like Dynamodb or S3. They are managed very well through IAM Policy conditions.
Another thing I have to disclaim: this approach is for learning purposes, and references AWS official documentation. Please note there is a dedicated AWS Solutions Architect team that addresses the architecture and design-level queries for custom solutions.
References
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_items.html
https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_cognito-bucket.html