AWS Database Blog

Simplify cross-account access control with Amazon DynamoDB using resource-based policies

Amazon DynamoDB is a serverless, NoSQL, fully-managed database service that delivers single-digit millisecond latency at any scale. Customers increasingly use workload isolation strategies to minimize security threats and to ease the complexity of compliance demands for their DynamoDB-backed workloads. Implementing workload isolation strategies often requires cross-account and cross-Region access to DynamoDB resources using IAM identity-based policies, which increases management and application complexity. Customers regularly ask us to simplify resource access control, especially for cross-account access to DynamoDB resources.

Today, we are excited to announce the availability of resource-based policies for DynamoDB. With resource-based policies, you can specify the AWS Identity and Access Management (IAM) users or services that have access to a resource, and what actions they can perform on it. A resource-based policy can be attached to a DynamoDB table, index, or a stream. A significant benefit of using resource-based policies is to simplify cross-account access control for sharing resources with IAM principals of different AWS accounts.

Resource-based policies also support integrations with IAM Access Analyzer and Block Public Access (BPA) capabilities. IAM Access Analyzer reports cross-account access to external entities specified in resource-based policies, to help prevent unintended external access definitions in policies. Access Analyzer can also help you generate least-privilege policies. BPA helps you prevent public access to your DynamoDB tables, indexes, and streams, and is automatically enabled in the resource-based policies creation and modification workflows.

In this post, we explore four different DynamoDB authorization scenarios and how you can implement resource-based policies to solve them.

Solution overview

In this example, we explore how resource-based policies simplifies cross-account access control compared to identity-based policies. For this use case, you have a multi-tenant application that requires access to a central table that holds information required to run the core business logic for each tenant. Each tenant (Alfa, Bravo, and Charlie) deploys its infrastructure in their own AWS account. We will first look at an identity-based policy solution and then a resource-based policy solution.

Before resource-based policies were supported for DynamoDB, using identity-based policies was the only way to manage access control. The following diagram shows the steps required to read information from the central account from each of the tenants using identity-based policies.

The steps for granting access to the Alfa tenant with an identify-based policy are as follows:

  1. In the central account, create an identity-based policy that allows access to the central DynamoDB table. You can use the sample policy AmazonDynamoDBFullAccess as a reference.
  2. Create a role in the central account that will have the Alfa account as its trusted entity. Attach the policy you created.
  3. In the Alfa account, create a second identity-based policy that allows the account’s AWS Lambda function to assume the role you created.
  4. Create a second role specifying the trusted entity as Lambda and assign the policy created in Step 3.
  5. Configure the Lambda function and assign the role you created in Step 4.
  6. The Lambda function must assume a different role than the Lambda execution one. For this, you use AWS Security Token Service (AWS STS) assuming the role you have created in Step 2. After setting up the session, use the temporary credentials from STS to initialize the DynamoDB client for the main account table and region.
  7. Once the session is established you use the temporary credentials provided by STS to initialize the DynamoDB client for the central account table and region.
  8. Run the code to retrieve the information from the central table.

Since the tenants Bravo and Charlie also need access to the central table you will need to repeat these steps two more times.

The Lambda function is illustrated by the following code:

import boto3

## Using STS to assume the role created in step 2. 
sts_client = boto3.client('sts')
sts_session = sts_client.assume_role(RoleArn='arn:aws:iam::<Central Account>:role/DynamoDB-FullAccess-For-tenant-alfa', RoleSessionName='test-dynamodb-session')

## Get the security credentials required to start the client in central account 
KEY_ID = sts_session['Credentials']['AccessKeyId']
ACCESS_KEY = sts_session['Credentials']['SecretAccessKey']
TOKEN = sts_session['Credentials']['SessionToken']
REGION = "<AWS region>"

## Initializes boto3 client in the central account from tenants Alfa, Bravo, Charlie
dynamodb_client = boto3.client('dynamodb',
                               region_name=REGION, 
                               aws_access_key_id=KEY_ID, 
                               aws_secret_access_key=ACCESS_KEY, 
                               aws_session_token=TOKEN)

def lambda_handler(event, context):
     data = dynamodb_client.get_item(
       TableName='rbac-central-table',
       Key={'PK':{'S':'foo'}, 'SK':{'S':'bar'}}
     )
     return data

With the introduction of resource-based policies, you can define access control per resource, for example a DynamoDB table, index, or stream. By using this approach, you can avoid the hassle of creating and managing account roles for each tenant’s account, and instead achieve centralized access control at the table level, making your security controls simpler. You can attach resource-based policies using the AWS Command Line Interface (AWS CLI), AWS Management Console, AWS SDKs, or AWS CloudFormation.

The following diagram shows the simplified architecture that uses resource-based policies for access control.

The steps for adding Alfa, Bravo, and Charlie with resource-based policies are as follows:

  1. In the central account, create a resource-based policy for central-table, and specify the Alfa, Bravo, and Charlie accounts as principals in the resource-based policy.
  2. In the respective accounts, create an identity-based policy for the respective user to grant permission to perform the desired action on central-table.
  3. Assign this policy to a respective role that the Lambda function will assume.
  4. Run the Lambda function code by specifying the full Amazon Resource Name (ARN) as the table name. Note that you neither need to assume other roles, nor increase your code complexity.

With resource-based policies, you only need a single role in the central account to control your table access. Fewer policies simplify your table access management and reduces the number of roles and policies required vs identity-based access. In this example, 1 resource-based policy vs 3 identity-based policies and 3 roles.

You can use the following code to create a Lambda function in the Alfa account:

import boto3

dynamodb_client = boto3.client('dynamodb')

def lambda_handler(event, context):
    
    response = dynamodb_client.get_item(
        TableName='arn:aws:dynamodb:<AWS region>:<Central Account>:table/rbac-central-table', 
        Key={'PK':{'S':'foo'}, 'SK':{'S':'bar'}}
    )
    return response

When you need to access a DynamoDB resource cross-account or cross-Region as part of a DynamoDB API operation, you need to specify the table’s full ARN as part of the API call, instead of the table name. However, If you only specify the table name, the SDK assumes you want to access a resource from the same account and Region where the Lambda function is running.

You can also see the difference in code complexity because you don’t need to assume the identity-based role to get information from your DynamoDB table; you only need to modify your code to access the resource externally. As you can see, the permissions complexity required to access resources from another account has significantly decreased, enhancing the efficiency of managing resource access control.

Policy examples

With resource-based policies, you can now specify the IAM principals that you want to provide access to and can choose any from those listed in the following table.

Principal Type ARN
AWS account and root user arn:aws:iam::{Account}:root
IAM roles arn:aws:iam::{Account}:role/{RoleNameWithPath}
IAM role sessions arn:aws:sts::{Account}:assumed-role/{RoleName}/{RoleSessionName}
IAM users arn:aws:iam::{Account}:user/{UserName}
Federated user sessions arn:aws:iam::{Account}:federated-user/{UserName}
AWS services {ServiceName}.amazonaws.com

Whenever you are using both an identity-based and a resource-based policy, the aggregated permission is evaluated as per the policy evaluation logic, in most of the cases the following evaluating logic applies:

Type of Policy Identity-based policy
Allow No Allow or Deny Deny
Resource-based policy Allow Allow Allow Deny
No Allow or Deny Allow Deny Deny
Deny Deny Deny Deny

You can configure two types of resource-based policies, one for tables and another for active streams. Note that if you don’t have Amazon DynamoDB Streams enabled, you will only see resource-based policies for tables on the DynamoDB console in the following screenshot.

Let’s look at some examples of resource-based policies attached to DynamoDB tables.

Example 1 – Give specific user permissions to a table

The following resource-based policy for a table, attached to the DynamoDB table rbac-central-table, gives the IAM user foo and foobar permission to perform GetItem operations:

{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Sid": "Statement1",
     "Effect": "Allow",
     "Principal": {
       "AWS": [
         "arn:aws:iam::111122223333:user/foo",
         "arn:aws:iam::111122223333:user/foobar"
       ]
     },
     "Action": "dynamodb:GetItem",
     "Resource": "arn:aws:dynamodb:us-east-1:444455556666:table/rbac-central-table"
   }
 ]
}

Example 2 – Give to Admin role read access to a DynamoDB Stream

The following resource-based policy for an active stream, attached to the DynamoDB stream 2024-01-11T16:35:51.514, gives the Admin role permission to perform the actions DescribeStream, GetRecords, and GetShardIterator:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement2",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/Admin" },
      "Action": [
        "dynamodb:DescribeStream",
        "dynamodb:GetRecords",
        "dynamodb:GetShardIterator"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:444455556666:table/rbac-central-table /stream/2024-01-11T16:35:51.514"
    }
  ]
}

Example 3 – Fined Grained Access control to a specific user for query operations in a table

You can specify permissions for a cross-account IAM identity to access DynamoDB resources. For example, you might need a user from a trusted account to get access to read the contents of your table, with the condition that they access only specific items and specific attributes in those items. The following policy allows access to user foo from trusted AWS account ID 111111111111 to access data from a table in account 222222222222 by using the Query API. The policy makes sure that the user can access only items with the primary key prefix ProductCatalog- and that the user can retrieve the attributes ProductName, Inventory, and DueDate but no other attributes:

{
  "Sid": "Statement3",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam:: 111111111111:user/foo" },
  "Action": "dynamodb:Query",
  "Resource": "arn:aws:dynamodb:us-east-1:222222222222:table/rbac-central-table",
  "Condition": {
    "ForAllValues:StringEquals": {
      "dynamodb:LeadingKeys": "ProductCatalog-",
      "dynamodb:Attributes": ["ProductName", "Inventory", "DueDate"]
    },
    "StringEqualsIfExists": {
      "dynamodb:ReturnValues": ["NONE", "UPDATE_OLD", "UPDATE_NEW"],
      "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
    }
  }
}

Example 4 – Give to Admin role full access to a table based on source IP address or VPC endpoint.

You can apply a condition to restrict source IP addresses, VPCs, and VPC endpoints. You can specify permissions based on the source addresses of the originating request. For example, you might want to allow a user to access DynamoDB resources only if they are coming from a specific IP source, such as a corporate VPN endpoint. Specify these IP addresses in the Condition statement:

{
  "Sid": "Statement4",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:role/Admin" },
  "Action": "dynamodb:*",
  "Resource": "arn:aws:dynamodb:us-east-1:444455556666:table/rbac-central-table",
  "Condition": {
    "IpAddress": 
       { "aws:SourceIp": ["54.240.143.0/24", "2001:DB8:1234:5678::/64"] }
  }

You can also deny all access to DynamoDB except when the source is a specific VPC endpoint:

{
  "Sid": "Statement5",
  "Effect": "Deny",
  "Principal": { "AWS": "arn:aws:iam::111122223333:role/Admin" },
  "Action": "dynamodb:*",
  "Resource": "arn:aws:dynamodb:us-east-1:444455556666:table/rbac-central-table",
  "Condition": { "StringNotEquals": { "aws:SourceVpce": "vpce-1a2b3c4d"} }
}

Conclusion

In this post, we showed how you can simplify your cross-account access when multiple teams or applications need to share resources using resource-based policies for DynamoDB. There is no additional cost to use resource-based policies, and you can get started by using the AWS console, AWS APIs, AWS CLI, AWS SDK, or AWS CloudFormation. To learn more, refer to using resource-based policies with DynamoDB.


About the authors

Esteban Serna is a Senior DynamoDB Specialist Solutions Architect. Esteban has been working with databases for the last 15 years, helping customers choose the right architecture to match their needs. Fresh from university, he worked deploying the infrastructure required to support contact centers in distributed locations. When NoSQL databases were introduced, he fell in love with them and decided to focus on them because centralized computing was no longer the norm. Today, Esteban is focusing on helping customers design distributed massive scale applications that require single digit-millisecond latency using DynamoDB. Some people say he is an open book and he loves to share his knowledge with others.

Ashwin Venkatesh is a Senior Product Manager for Amazon DynamoDB at Amazon Web Services, and is based out of Santa Clara, California. With 25+ years in product management and technology roles, Ashwin has a passion for engaging with customers to understand business use cases, defining strategy, working backwards to define new features that deliver long-term customer value, and having deep-dive discussions with technology peers. Outside work, Ashwin enjoys travel, sports and family events.