In this blog post, we will explore how to implement S3 bucket policies using AWS CDK (Cloud Development Kit). We'll cover two approaches: explicit and implicit. By the end of this guide, you will understand how to create S3 bucket policies in a structured and reusable manner using AWS CDK.
The caller of an S3 request made by Lambda is the function's execution role, not the generic Lambda service principal. The revised examples create a role that Lambda can assume and grant that role access. Assign it to an actual function separately, or prefer bucket.grantRead(function) when you manage both resources. See Lambda execution roles.
Prerequisites
Before we dive into the code, ensure you have the following installed:
- AWS CDK
- AWS CLI
- Node.js
- TypeScript
The folder structure for the project will look like below.
S3-BUCKET-POLICY-AWS-CDK ├── bin │ └── app.ts ├── cdk.out ├── node_modules ├── stacks │ ├── index.ts │ ├── s3-bucket-policy-explicit-stack.ts │ └── s3-bucket-policy-implicit-stack.ts ├── test ├── .gitignore ├── .npmignore ├── cdk.json ├── jest.config.js ├── package.json ├── README.md └── tsconfig.json
Link to full GitHub project.
Step 1: Setting Up the Project
First, let's initialize the project and install the necessary dependencies:
mkdir S3-BUCKET-POLICY-AWS-CDK cd S3-BUCKET-POLICY-AWS-CDK cdk init app --language=typescript
Step 2: Defining the Explicit S3 Bucket Policy Stack
We will start with the explicit method, where we create a separate BucketPolicy resource and attach it to the S3 bucket.
Create a new file stacks/s3-bucket-policy-explicit-stack.ts and add the following code:
import { RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; import { ArnPrincipal, Effect, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { Bucket, BucketPolicy } from "aws-cdk-lib/aws-s3"; import { Construct } from "constructs"; export class S3BucketPolicyExplicitStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const readerRole = new Role(this, "ReaderRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); // Create the S3 bucket const explicitBucket = new Bucket(this, "explicit-s3-bucket", { removalPolicy: RemovalPolicy.DESTROY, }); // Create the bucket policy const bucketPolicy = new BucketPolicy(this, "explicit-bucket-policy-id", { bucket: explicitBucket, }); // Add policy statements to the bucket policy bucketPolicy.document.addStatements( new PolicyStatement({ effect: Effect.ALLOW, principals: [new ArnPrincipal(readerRole.roleArn)], actions: ["s3:GetObject"], resources: [`${explicitBucket.bucketArn}/*`], }) ); } }
Step 3: Defining the Implicit S3 Bucket Policy Stack
Next, we will use the implicit method, where we add the bucket policy directly to the bucket resource.
Create a new file stacks/s3-bucket-policy-implicit-stack.ts and add the following code:
import { RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; import { ArnPrincipal, Effect, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { Bucket } from "aws-cdk-lib/aws-s3"; import { Construct } from "constructs"; export class S3BucketPolicyImplicitStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const readerRole = new Role(this, "ReaderRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); // Create the S3 bucket const implicitBucket = new Bucket(this, "implicit-s3-bucket", { removalPolicy: RemovalPolicy.DESTROY, }); // Create a bucket policy automatically implicitBucket.addToResourcePolicy( new PolicyStatement({ effect: Effect.ALLOW, principals: [new ArnPrincipal(readerRole.roleArn)], actions: ["s3:GetObject"], resources: [`${implicitBucket.bucketArn}/*`], }) ); // Access the bucket policy implicitBucket.policy?.document.addStatements( new PolicyStatement({ effect: Effect.ALLOW, principals: [new ArnPrincipal(readerRole.roleArn)], actions: ["s3:GetBucketTagging"], resources: [implicitBucket.bucketArn], }) ); } }
Step 4: Setting Up the Application Entry Point
Now, let's set up the application entry point to deploy these stacks. Update the bin/app.ts file as follows:
#!/usr/bin/env node import "source-map-support/register"; import { App } from "aws-cdk-lib"; import { S3BucketPolicyExplicitStack, S3BucketPolicyImplicitStack, } from "../stacks"; const app = new App(); new S3BucketPolicyImplicitStack(app, "S3BucketPolicyImplicitStack", {}); new S3BucketPolicyExplicitStack(app, "S3BucketPolicyExplicitStack", {});
Step 5: Deploying the Stacks
Finally, deploy the stacks using the following commands:
cdk bootstrap cdk deploy S3BucketPolicyImplicitStack cdk deploy S3BucketPolicyExplicitStack
Conclusion
In this blog post, we demonstrated how to create and manage S3 bucket policies using AWS CDK in both explicit and implicit ways. This approach provides flexibility and a clear structure for managing your infrastructure as code.
Feel free to reach out in the comments if you have any questions or run into issues.
Find the complete CDK code below.
Continue reading
Create an IAM User with AWS CDK: Scoped S3 Permissions · Configure S3 CORS: JSON Rules, Preflight, and Troubleshooting
Quick Recap
What's the difference between explicit and implicit S3 bucket policies in CDK?
The explicit method creates a separate BucketPolicy construct and attaches it to the bucket. The implicit method calls addToResourcePolicy() directly on the bucket, which creates the underlying policy resource automatically.
How do you grant Lambda access to an S3 bucket with an explicit bucket policy?
Create a BucketPolicy construct for the bucket, then call bucketPolicy.document.addStatements() with a PolicyStatement allowing the Lambda execution role ARN the needed object actions. For a Lambda function managed in the same app, bucket.grantRead(function) is usually a simpler way to grant access.
How do you add a policy statement to an S3 bucket implicitly?
Call bucket.addToResourcePolicy() with a PolicyStatement directly on the Bucket construct. CDK creates and manages the underlying BucketPolicy resource for you.
Comments
Share your thoughts and questions below