Skip to content
15 changes: 11 additions & 4 deletions packages/aws-cdk-lib/aws-s3/lib/bucket-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export interface BucketPolicyProps {
* @default - RemovalPolicy.DESTROY.
*/
readonly removalPolicy?: RemovalPolicy;

/**
* Policy document to apply to the bucket.
*
* @default - A new empty PolicyDocument will be created.
*/
readonly document?: PolicyDocument;
}

/**
Expand Down Expand Up @@ -83,10 +90,9 @@ export class BucketPolicy extends Resource implements IBucketPolicyRef {
bucket = Bucket.fromBucketName(cfnBucketPolicy, '@FromCfnBucket', cfnBucketPolicy.bucket);
}

const ret = new class extends BucketPolicy {
public readonly document = PolicyDocument.fromJson(cfnBucketPolicy.policyDocument);
}(cfnBucketPolicy, id, {
const ret = new BucketPolicy(cfnBucketPolicy, id, {
bucket,
document: PolicyDocument.fromJson(cfnBucketPolicy.policyDocument),
});

// mark the Bucket as having this Policy
Expand All @@ -101,7 +107,7 @@ export class BucketPolicy extends Resource implements IBucketPolicyRef {
* For more information, see Access Policy Language Overview in the Amazon
* Simple Storage Service Developer Guide.
*/
public readonly document = new PolicyDocument();
public readonly document: PolicyDocument;

/** The Bucket this Policy applies to. */
public readonly bucket: IBucket;
Expand All @@ -114,6 +120,7 @@ export class BucketPolicy extends Resource implements IBucketPolicyRef {
addConstructMetadata(this, props);

this.bucket = props.bucket;
this.document = props.document ?? new PolicyDocument();

this.resource = new CfnBucketPolicy(this, 'Resource', {
bucket: this.bucket.bucketName,
Expand Down
37 changes: 37 additions & 0 deletions packages/aws-cdk-lib/aws-s3/test/bucket-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,43 @@ describe('bucket policy', () => {
expect(bucketPolicy.bucket.bucketName).toBe('hardcoded-name');
});

test('should synthesize without errors and create duplicate resources', () => {
const app = new App();
const testStack = new Stack(app, 'TestStack');
const cfnBucketPolicy = new s3.CfnBucketPolicy(testStack, 'TestBucketPolicy', {
policyDocument: {
'Statement': [
{
'Action': 's3:*',
'Effect': 'Deny',
'Principal': {
'AWS': '*',
},
'Resource': '*',
},
],
'Version': '2012-10-17',
},
bucket: 'test-bucket',
});

s3.BucketPolicy.fromCfnBucketPolicy(cfnBucketPolicy);

// This should not throw - the synthesis bug is fixed
expect(() => app.synth()).not.toThrow();

// Verify that two CfnBucketPolicy resources are created (expected behavior)
const template = Template.fromStack(testStack);
const bucketPolicies = template.findResources('AWS::S3::BucketPolicy');
expect(Object.keys(bucketPolicies)).toHaveLength(2);

// Both should have valid policy documents
Object.values(bucketPolicies).forEach((policy: any) => {
expect(policy.Properties.PolicyDocument).toBeDefined();
expect(policy.Properties.PolicyDocument.Statement).toBeDefined();
});
});

function bucketPolicyForBucketNamed(name: string): CfnBucketPolicy {
return new s3.CfnBucketPolicy(stack, `CfnBucketPolicy-${name}`, {
policyDocument: {
Expand Down
Loading