The AWS CDK S3 library provides comprehensive security features to protect your S3 buckets and objects. This includes IAM-based access control with convenient grant methods, public access controls, encryption options, SSL enforcement, and bucket policies for fine-grained permissions management.
import { BucketEncryption, BlockPublicAccess, BucketPolicy } from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';All bucket instances provide convenient methods for granting IAM permissions to AWS principals:
interface IBucket {
grantRead(identity: IGrantable, objectsKeyPattern?: any): Grant;
grantWrite(identity: IGrantable, objectsKeyPattern?: any): Grant;
grantReadWrite(identity: IGrantable, objectsKeyPattern?: any): Grant;
grantPut(identity: IGrantable, objectsKeyPattern?: any): Grant;
grantPutAcl(identity: IGrantable, objectsKeyPattern?: string): Grant;
grantDelete(identity: IGrantable, objectsKeyPattern?: any): Grant;
grantPublicAccess(keyPrefix?: string, ...allowedActions: string[]): Grant;
}const bucket = new s3.Bucket(this, 'MyBucket');
const role = new iam.Role(this, 'MyRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
});
// Grant read access to all objects
bucket.grantRead(role);
// Grant write access to all objects
bucket.grantWrite(role);
// Grant full read/write access
bucket.grantReadWrite(role);
// Grant delete permissions
bucket.grantDelete(role);Grant permissions to specific object patterns using key prefixes:
// Grant read access only to 'public/' folder
bucket.grantRead(role, 'public/*');
// Grant write access to user-specific folder
bucket.grantWrite(role, `users/${userId}/*`);
// Grant read/write to specific file types
bucket.grantReadWrite(role, '*.json');
// Multiple patterns can be granted separately
bucket.grantRead(role, 'images/*');
bucket.grantRead(role, 'documents/*.pdf');// Grant object upload permissions (PUT operations)
bucket.grantPut(myLambda, 'uploads/*');
// Grant ACL modification permissions (requires explicit call as of CDK 1.85.0+)
bucket.grantPutAcl(role, 'shared/*');
// Grant public read access with optional restrictions
const grant = bucket.grantPublicAccess('public/*', 's3:GetObject');
grant.resourceStatement!.addCondition('IpAddress', {
'aws:SourceIp': '203.0.113.0/24'
});When buckets use KMS encryption, grant methods automatically include necessary key permissions:
const encryptionKey = new kms.Key(this, 'MyKey');
const bucket = new s3.Bucket(this, 'MyBucket', {
encryption: s3.BucketEncryption.KMS,
encryptionKey: encryptionKey
});
// Automatically grants both S3 and KMS permissions
bucket.grantReadWrite(role); // Includes kms:Decrypt and kms:Encrypt permissionsS3 buckets support multiple encryption methods for data protection:
enum BucketEncryption {
UNENCRYPTED = 'NONE', // No encryption
S3_MANAGED = 'S3MANAGED', // Server-side encryption with S3-managed keys (SSE-S3)
KMS_MANAGED = 'MANAGED', // Server-side encryption with KMS-managed keys (SSE-KMS)
KMS = 'KMS' // Server-side encryption with customer-managed KMS keys
}// S3-managed encryption (SSE-S3)
const s3ManagedBucket = new s3.Bucket(this, 'S3ManagedBucket', {
encryption: s3.BucketEncryption.S3_MANAGED
});
// KMS-managed encryption with AWS managed key
const kmsManagedBucket = new s3.Bucket(this, 'KmsManagedBucket', {
encryption: s3.BucketEncryption.KMS_MANAGED
});
// Customer-managed KMS key encryption
const customerKey = new kms.Key(this, 'CustomerKey');
const customerManagedBucket = new s3.Bucket(this, 'CustomerManagedBucket', {
encryption: s3.BucketEncryption.KMS,
encryptionKey: customerKey
});
// Enable S3 Bucket Key for cost optimization with KMS
const bucketKeyBucket = new s3.Bucket(this, 'BucketKeyBucket', {
encryption: s3.BucketEncryption.KMS,
bucketKeyEnabled: true // Reduces KMS request costs
});// Recommended secure configuration
const secureBucket = new s3.Bucket(this, 'SecureBucket', {
encryption: s3.BucketEncryption.KMS,
bucketKeyEnabled: true,
enforceSSL: true, // Require HTTPS for all requests
versioned: true, // Enable versioning for data protection
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
});
// Access the automatically created encryption key
if (secureBucket.encryptionKey) {
secureBucket.encryptionKey.addAlias('alias/my-bucket-key');
}Control public access to buckets and objects using Block Public Access settings:
class BlockPublicAccess {
static readonly BLOCK_ALL: BlockPublicAccess;
static readonly BLOCK_ACLS: BlockPublicAccess;
readonly blockPublicAcls?: boolean;
readonly blockPublicPolicy?: boolean;
readonly ignorePublicAcls?: boolean;
readonly restrictPublicBuckets?: boolean;
constructor(options: BlockPublicAccessOptions);
}
interface BlockPublicAccessOptions {
blockPublicAcls?: boolean; // Block public ACLs on bucket and objects
blockPublicPolicy?: boolean; // Block public bucket policies
ignorePublicAcls?: boolean; // Ignore public ACLs on bucket and objects
restrictPublicBuckets?: boolean; // Restrict public bucket policies
}// Block all public access (recommended for most use cases)
const privateeBucket = new s3.Bucket(this, 'PrivateBucket', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
});
// Block only ACL-based public access
const aclBlockedBucket = new s3.Bucket(this, 'AclBlockedBucket', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS
});
// Custom public access controls
const customBlockedBucket = new s3.Bucket(this, 'CustomBlockedBucket', {
blockPublicAccess: new s3.BlockPublicAccess({
blockPublicPolicy: true, // Block public policies
ignorePublicAcls: true // Ignore public ACLs
})
});
// Allow public read access (use carefully)
const publicBucket = new s3.Bucket(this, 'PublicBucket', {
publicReadAccess: true, // Grants public GetObject permissions
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS // Still block ACL-based access
});When blockPublicPolicy is enabled, attempting to grant public access will throw an error:
const blockedBucket = new s3.Bucket(this, 'BlockedBucket', {
blockPublicAccess: new s3.BlockPublicAccess({ blockPublicPolicy: true })
});
// This will throw an error
try {
blockedBucket.grantPublicAccess();
} catch (error) {
console.log("Cannot grant public access when 'blockPublicPolicy' is enabled");
}Create custom IAM policies for fine-grained access control:
class BucketPolicy extends Resource {
constructor(scope: Construct, id: string, props: BucketPolicyProps);
readonly document: PolicyDocument;
applyRemovalPolicy(removalPolicy: RemovalPolicy): void;
}
interface BucketPolicyProps {
readonly bucket: IBucket;
readonly removalPolicy?: RemovalPolicy;
}const bucket = new s3.Bucket(this, 'MyBucket');
// Create a bucket policy
const bucketPolicy = new s3.BucketPolicy(this, 'MyBucketPolicy', {
bucket: bucket
});
// Add policy statements
bucketPolicy.document.addStatements(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.AccountPrincipal('123456789012')],
actions: ['s3:GetObject'],
resources: [bucket.arnForObjects('shared/*')]
}),
new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:*'],
resources: [bucket.bucketArn, bucket.arnForObjects('*')],
conditions: {
Bool: { 'aws:SecureTransport': 'false' } // Require HTTPS
}
})
);Most commonly, use the bucket's addToResourcePolicy method instead of creating a BucketPolicy directly:
bucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],
actions: ['s3:PutObject'],
resources: [bucket.arnForObjects('AWSLogs/*')],
conditions: {
StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' }
}
}));Enforce secure transport for all bucket operations:
const secureBucket = new s3.Bucket(this, 'SecureBucket', {
enforceSSL: true // Automatically creates a policy denying non-HTTPS requests
});
// Manual SSL enforcement via bucket policy
bucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:*'],
resources: [bucket.bucketArn, bucket.arnForObjects('*')],
conditions: {
Bool: { 'aws:SecureTransport': 'false' }
}
}));import * as s3 from '@aws-cdk/aws-s3';
import * as kms from '@aws-cdk/aws-kms';
import * as iam from '@aws-cdk/aws-iam';
export class SecureS3Stack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);
// Create a secure bucket with comprehensive security controls
const secureEncryptionKey = new kms.Key(this, 'SecureKey', {
description: 'S3 bucket encryption key',
enableKeyRotation: true, // Enable automatic key rotation
});
const secureBucket = new s3.Bucket(this, 'SecureBucket', {
// Encryption settings
encryption: s3.BucketEncryption.KMS,
encryptionKey: secureEncryptionKey,
bucketKeyEnabled: true,
// Access controls
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
// Data protection
versioned: true,
lifecycleRules: [{
id: 'DeleteOldVersions',
noncurrentVersionExpiration: cdk.Duration.days(90),
}],
// Logging and monitoring
serverAccessLogsPrefix: 'access-logs/',
// Deletion protection
removalPolicy: cdk.RemovalPolicy.RETAIN
});
// Create application role with minimal permissions
const appRole = new iam.Role(this, 'AppRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
description: 'Role for application S3 access'
});
// Grant scoped permissions
secureBucket.grantRead(appRole, 'public/*');
secureBucket.grantReadWrite(appRole, 'app-data/*');
// Add custom policy for specific requirements
secureBucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:DeleteBucket'],
resources: [secureBucket.bucketArn],
conditions: {
StringNotEquals: {
'aws:PrincipalServiceName': 'cloudformation.amazonaws.com'
}
}
}));
// Output key ARN for reference
new cdk.CfnOutput(this, 'EncryptionKeyArn', {
value: secureEncryptionKey.keyArn,
description: 'ARN of the S3 bucket encryption key'
});
}
}