Define permissions for L2 constructs with the AWS CDK - AWS Cloud Development Kit (AWS CDK) v2

This is the AWS CDK v2 Developer Guide. The older CDK v1 entered maintenance on June 1, 2022 and ended support on June 1, 2023.

Define permissions for L2 constructs with the AWS CDK

Define AWS Identity and Access Management (IAM) roles and policies for L2 constructs when using the AWS Cloud Development Kit (AWS CDK).

Use grant methods to define permissions

When you define your infrastructure using L2 constructs from the AWS Construct Library, you can use the provided grant methods to specify the permissions your resources will require. The AWS CDK will automatically create the IAM roles needed for all AWS resources that require them.

The following is an example that defines permissions between an AWS Lambda function and Amazon Simple Storage Service (Amazon S3) bucket. Here, the grantRead method of the Bucket L2 construct is used to define these permissions:

TypeScript
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as kms from 'aws-cdk-lib/aws-kms'; export class CdkDemoStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const key = new kms.Key(this, 'BucketKey'); const bucket = new s3.Bucket(this, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(this, 'Handler', { runtime: lambda.Runtime.NODEJS_20_X, handler: 'index.handler', code: lambda.Code.fromAsset('lambda'), }); // Define permissions between function and S3 bucket using grantRead method bucket.grantRead(handler); } }
JavaScript
const { Stack, Duration } = require('aws-cdk-lib'); const s3 = require('aws-cdk-lib/aws-s3'); const lambda = require('aws-cdk-lib/aws-lambda'); const kms = require('aws-cdk-lib/aws-kms'); class CdkDemoStack extends Stack { constructor(scope, id, props) { super(scope, id, props); const key = new kms.Key(this, 'BucketKey'); const bucket = new s3.Bucket(this, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(this, 'Handler', { runtime: lambda.Runtime.NODEJS_20_X, handler: 'index.handler', code: lambda.Code.fromAsset('lambda'), }); // Define permissions between function and S3 bucket using grantRead method bucket.grantRead(handler); } } // ...
Python
from aws_cdk import ( Stack, aws_s3 as s3, aws_lambda as _lambda, aws_kms as kms, ) from constructs import Construct class CdkDemoStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) key = kms.Key(self, 'BucketKey') bucket = s3.Bucket(self, 'Bucket') handler = _lambda.Function( self, 'Handler', runtime = _lambda.Runtime.NODEJS_20_X, handler = 'index.handler', code = _lambda.Code.from_asset('lambda'), ) # Define permissions between function and S3 bucket using grantRead method bucket.grantRead(handler)
Java
package com.myorg; import software.amazon.awscdk.core.App; import software.amazon.awscdk.core.Stack; import software.amazon.awscdk.core.StackProps; import software.amazon.awscdk.services.kms.Key; import software.amazon.awscdk.services.kms.KeyProps; import software.amazon.awscdk.services.s3.Bucket; import software.amazon.awscdk.services.s3.BucketProps; import software.amazon.awscdk.services.lambda.Function; import software.amazon.awscdk.services.lambda.FunctionProps; import software.amazon.awscdk.services.lambda.Runtime; import software.amazon.awscdk.services.lambda.Code; import software.constructs.Construct; public class CdkDemoStack extends Stack { public CdkDemoStack(final Construct scope, final String id) { this(scope, id, null); } public CdkDemoStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); Key key = new Key(this, "BucketKey", KeyProps.builder().build()); Bucket bucket = new Bucket(this, "Bucket", BucketProps.builder() .encryptionKey(key) .build()); Function handler = new Function(this, "Handler", FunctionProps.builder() .runtime(Runtime.NODEJS_20_X) .handler("index.handler") .code(Code.fromAsset("lambda")) .build()); // Define permissions between function and S3 bucket using grantRead method bucket.grantRead(handler); } public static void main(final String[] args) { App app = new App(); new CdkDemoStack(app, "CdkDemoStack"); app.synth(); } }
C#
using Amazon.CDK; using Amazon.CDK.AWS.KMS; using Amazon.CDK.AWS.S3; using Amazon.CDK.AWS.Lambda; namespace CdkDemo { public class CdkDemoStack : Stack { internal CdkDemoStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) { var key = new Key(this, "BucketKey"); var bucket = new Bucket(this, "Bucket", new BucketProps { EncryptionKey = key }); var handler = new Function(this, "Handler", new FunctionProps { Runtime = Runtime.NODEJS_20_X, Handler = "index.handler", Code = Code.FromAsset("lambda") }); // Define permissions between function and S3 bucket using grantRead method bucket.GrantRead(handler); } } }
Go
package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" "github.com/aws/aws-cdk-go/awscdk/v2/awskms" "github.com/aws/aws-cdk-go/awscdk/v2/awss3" "github.com/aws/aws-cdk-go/awscdk/v2/awslambda" "github.com/aws/constructs-go/constructs/v10" "github.com/aws/jsii-runtime-go" ) // ... func NewCdkDemoStack(scope constructs.Construct, id string, props *CdkDemoStackProps) awscdk.Stack { stack := awscdk.NewStack(scope, &id, &props.StackProps) key := awskms.NewKey(stack, jsii.String("BucketKey"), nil) bucket := awss3.NewBucket(stack, jsii.String("Bucket"), &awss3.BucketProps{ EncryptionKey: key, }) handler := awslambda.NewFunction(stack, jsii.String("Handler"), &awslambda.FunctionProps{ Runtime: awslambda.Runtime_NODEJS_20_X(), Handler: jsii.String("index.handler"), Code: awslambda.Code_FromAsset(jsii.String("lambda"), &awss3assets.AssetOptions{}), }) bucket.GrantRead(handler) return stack } // ...

When you use grant methods of L2 constructs to define permissions between resources, the AWS CDK will create roles with least privilege policies based on the method you specify. As a security best practice, we recommend that you use the method that applies only the permissions that you require. For example, if you only need to grant permissions for a Lambda function to read from an Amazon S3 bucket, use the grantRead method instead of grantReadWrite.

For each method that you use, the CDK creates a unique IAM role for the specified resources. If necessary, you can also directly modify the policy that will be attached to the role. The following is an example:

TypeScript
import { aws_iam as iam } from 'aws-cdk-lib'; handler.addToRolePolicy(new iam.PolicyStatement({ actions: ['s3:GetObject', 's3:List*'], resources: [ bucket.bucketArn, bucket.arnForObjects('*'), ] }));
JavaScript
const iam = require('aws-cdk-lib/aws-iam'); handler.addToRolePolicy(new iam.PolicyStatement({ actions: ['s3:GetObject', 's3:List*'], resources: [ bucket.bucketArn, bucket.arnForObjects('*'), ] }));
Python
from aws_cdk import aws_iam as iam handler.add_to_role_policy(iam.PolicyStatement( actions=['s3:GetObject', 's3:List*'], resources=[ bucket.bucket_arn, bucket.arn_for_objects('*'), ] ))
Java
import software.amazon.awscdk.services.iam.PolicyStatement; import software.amazon.awscdk.services.iam.PolicyStatementProps; handler.addToRolePolicy(PolicyStatement.Builder.create() .actions(Arrays.asList("s3:GetObject", "s3:List*")) .resources(Arrays.asList( bucket.getBucketArn(), bucket.arnForObjects("*") )) .build());
C#
using Amazon.CDK.AWS.IAM; using Amazon.CDK.AWS.S3; using Amazon.CDK.AWS.Lambda; handler.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps { Actions = new[] { "s3:GetObject", "s3:List*" }, Resources = new[] { bucket.BucketArn, bucket.ArnForObjects("*") } }));
Go
package main import ( // ... "github.com/aws/aws-cdk-go/awscdk/v2/awsiam" // ... ) // ... func NewCdkDemoStack(scope constructs.Construct, id string, props *CdkDemoStackProps) awscdk.Stack { // ... handler.AddToRolePolicy(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{ Actions: jsii.Strings("s3:GetObject", "s3:List*"), Resources: jsii.Strings(bucket.BucketArn(), bucket.ArnForObjects("*")), })) // ...

However, we recommend that you use the grant methods when available.

Manually create and use IAM roles

If you prefer not to use the CDK grant methods to create and manage permissions, you must manually create and configure them. You can create IAM roles using the AWS Management Console, AWS CLI, or AWS SDKs. Then, you can pass them into your CDK application manually or use the role customization feature.

Reference and manage all roles manually

Constructs that require a role have an optional role property that you can use to pass in a role object.

To reference a role manually
  1. Use Role.fromRoleName() to reference your pre-existing role. The following is an example:

    const existingRole = Role.fromRoleName(stack, 'Role', 'my-pre-existing-role', { mutable: false // Prevent CDK from attempting to add policies to this role }
  2. Pass the pre-existing role when defining your resource. The following is an example:

    const handler = new lambda.Function(stack, 'Handler', { runtime: lambda.Runtime.NODEJS_20_XZ, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), // Pass in pre-existing role role: existingRole, });

Use the role customization feature

The AWS CDK role customization feature generates a report of roles and policies in your CDK app. You can use this feature to generate a report. Then you can substitute pre-created roles for them.

To use the role customization feature
  1. Add Role.customizeRoles() somewhere towards the top of your CDK application. The following is an example:

    const stack = new Stack(app, 'LambdaStack'); // Add this to use the role customization feature iam.Role.customizeRoles(stack); // Define your resources using L2 constructs const key = new kms.Key(stack, 'BucketKey'); const bucket = new s3.Bucket(stack, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(stack, 'Handler', { runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); // The grantRead() is still important. Even though it actually doesn't mutate // any policies, it indicates the need for them. bucket.grantRead(handler);
  2. When you synthesize your application, the CDK will throw an error, indicating that you need to provide the pre-created role name to Role.customizeRoles(). The following is an example of the generated report:

    <missing role> (LambdaStack/Handler/ServiceRole)
    
    AssumeRole Policy:
    [
      {
        "Action": "sts:AssumeRole",
        "Effect": "Allow",
        "Principal": {
          "Service": "lambda.amazonaws.com"
        }
      }
    ]
    
    Managed Policy ARNs:
    [
      "arn:(PARTITION):iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    ]
    
    Managed Policies Statements:
    NONE
    
    Identity Policy Statements:
    [
      {
        "Action": [
          "s3:GetObject*",
          "s3:GetBucket*",
          "s3:List*"
        ],
        "Effect": "Allow",
        "Resource": [
          "(LambdaStack/Bucket/Resource.Arn)",
          "(LambdaStack/Bucket/Resource.Arn)/*"
        ]
      }
    ]
  3. Once the role is created, you can pass it into your application for the resource that it applies to. For example, if the name of the role created for LambdaStack/Handler/ServiceRole is lambda-service-role, you would update your CDK app as follows:

    const stack = new Stack(app, 'LambdaStack'); // Add this to pass in the role iam.Role.customizeRoles(stack, { usePrecreatedRoles: { 'LambdaStack/Handler/ServiceRole': 'lambda-service-role', }, });

    The CDK will now use the pre-created role name anywhere that the role is referenced in the CDK application. It will also continue to generate the report so that any future policy changes can be referenced.

    You will notice that the reference to the Amazon S3 bucket ARN in the report is rendered as (LambdaStack/Bucket/Resource.Arn) instead of the actual ARN of the bucket. This is because the bucket ARN is a deploy time value that is not known at synthesis (the bucket hasn’t been created yet). This is another example of why we recommend allowing CDK to manage IAM roles and permissions by using the provided grant methods. In order to create the role with the initial policy, the admin will have to create the policy with broader permissions (for example, arn:aws:s3:::*).