Skip to content

Commit

Permalink
feat(s3): add CORS Property to S3 Bucket (aws#2101)
Browse files Browse the repository at this point in the history
Add CORS Property to S3 Bucket for configuring bucket cross-origin
access rules.

You can either specify the metrics as properties:

     new Bucket(stack, 'Bucket', {
       cors: [
         {
           allowedHeaders: [
             "*"
           ],
           allowedMethods: [
             "GET"
           ],
           allowedOrigins: [
             "*"
           ],
           exposedHeaders: [
             "Date"
           ],
           id: "myCORSRuleId1",
           maxAge: 3600
         }
       ]
     });

Or use the `addCors` function:

    const bucket = new Bucket(stack, 'Bucket');
    bucket.addCors({
      allowedMethods: ["GET", "HEAD"],
      allowedOrigins: ["https://example.com"]
    });
  • Loading branch information
workeitel committed Jun 12, 2019
1 parent b80ef04 commit b28c7ee
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 1 deletion.
73 changes: 72 additions & 1 deletion packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,36 @@ export interface BucketMetrics {
readonly tagFilters?: {[tag: string]: any};
}

/**
* Specifies a cross-origin access rule for an Amazon S3 bucket.
*/
export interface CorsRule {
/**
* A unique identifier for this rule.
*/
readonly id?: string;
/**
* The time in seconds that your browser is to cache the preflight response for the specified resource.
*/
readonly maxAge?: number;
/**
* Headers that are specified in the Access-Control-Request-Headers header.
*/
readonly allowedHeaders?: string[];
/**
* An HTTP method that you allow the origin to execute.
*/
readonly allowedMethods: Array<"GET"|"PUT"|"HEAD"|"POST"|"DELETE">;
/**
* One or more origins you want customers to be able to access the bucket from.
*/
readonly allowedOrigins: string[];
/**
* One or more headers in the response that you want customers to be able to access from their applications.
*/
readonly exposedHeaders?: string[];
}

export interface BucketProps {
/**
* The kind of server-side encryption to apply to this bucket.
Expand Down Expand Up @@ -725,6 +755,15 @@ export interface BucketProps {
* @default - No metrics configuration.
*/
readonly metrics?: BucketMetrics[];

/**
* The CORS configuration of this bucket.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-cors.html
*
* @default - No CORS configuration.
*/
readonly cors?: CorsRule[];
}

/**
Expand Down Expand Up @@ -808,6 +847,7 @@ export class Bucket extends BucketBase {
private readonly versioned?: boolean;
private readonly notifications: BucketNotifications;
private readonly metrics: BucketMetrics[] = [];
private readonly cors: CorsRule[] = [];

constructor(scope: Construct, id: string, props: BucketProps = {}) {
super(scope, id, {
Expand All @@ -826,7 +866,8 @@ export class Bucket extends BucketBase {
lifecycleConfiguration: Lazy.anyValue({ produce: () => this.parseLifecycleConfiguration() }),
websiteConfiguration: this.renderWebsiteConfiguration(props),
publicAccessBlockConfiguration: props.blockPublicAccess,
metricsConfigurations: Lazy.anyValue({ produce: () => this.parseMetricConfiguration() })
metricsConfigurations: Lazy.anyValue({ produce: () => this.parseMetricConfiguration() }),
corsConfiguration: Lazy.anyValue({ produce: () => this.parseCorsConfiguration() })
});

applyRemovalPolicy(resource, props.removalPolicy !== undefined ? props.removalPolicy : RemovalPolicy.Orphan);
Expand Down Expand Up @@ -855,6 +896,8 @@ export class Bucket extends BucketBase {

// Add all bucket metric configurations rules
(props.metrics || []).forEach(this.addMetric.bind(this));
// Add all cors configuration rules
(props.cors || []).forEach(this.addCors.bind(this));

// Add all lifecycle rules
(props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this));
Expand Down Expand Up @@ -892,6 +935,15 @@ export class Bucket extends BucketBase {
this.metrics.push(metric);
}

/**
* Adds a cross-origin access configuration for objects in an Amazon S3 bucket
*
* @param rule The CORS configuration rule to add
*/
public addCors(rule: CorsRule) {
this.cors.push(rule);
}

/**
* Adds a bucket notification event destination.
* @param event The event to trigger the notification
Expand Down Expand Up @@ -1096,6 +1148,25 @@ export class Bucket extends BucketBase {
}
}

private parseCorsConfiguration(): CfnBucket.CorsConfigurationProperty | undefined {
if (!this.cors || this.cors.length === 0) {
return undefined;
}

return { corsRules: this.cors.map(parseCors) };

function parseCors(rule: CorsRule): CfnBucket.CorsRuleProperty {
return {
id: rule.id,
maxAge: rule.maxAge,
allowedHeaders: rule.allowedHeaders,
allowedMethods: rule.allowedMethods,
allowedOrigins: rule.allowedOrigins,
exposedHeaders: rule.exposedHeaders
};
}
}

private parseTagFilters(tagFilters?: {[tag: string]: any}) {
if (!tagFilters || tagFilters.length === 0) {
return undefined;
Expand Down
121 changes: 121 additions & 0 deletions packages/@aws-cdk/aws-s3/test/test.cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { Stack } from '@aws-cdk/cdk';
import { Test } from 'nodeunit';
import { Bucket } from '../lib';

export = {
'Can use addCors() to add a CORS configuration'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
const bucket = new Bucket(stack, 'Bucket');
bucket.addCors({
allowedMethods: ["GET", "HEAD"],
allowedOrigins: ["https://example.com"]
});

// THEN
expect(stack).to(haveResource('AWS::S3::Bucket', {
CorsConfiguration: {
CorsRules: [{
AllowedMethods: ["GET", "HEAD"],
AllowedOrigins: ["https://example.com"]
}]
}
}));

test.done();
},

'Bucket with multiple cors configurations'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
new Bucket(stack, 'Bucket', {
cors: [
{
allowedHeaders: [
"*"
],
allowedMethods: [
"GET"
],
allowedOrigins: [
"*"
],
exposedHeaders: [
"Date"
],
id: "myCORSRuleId1",
maxAge: 3600
},
{
allowedHeaders: [
"x-amz-*"
],
allowedMethods: [
"DELETE"
],
allowedOrigins: [
"http://www.example1.com",
"http://www.example2.com"
],
exposedHeaders: [
"Connection",
"Server",
"Date"
],
id: "myCORSRuleId2",
maxAge: 1800
}
]
});

// THEN
expect(stack).to(haveResource('AWS::S3::Bucket', {
CorsConfiguration: {
CorsRules: [
{
AllowedHeaders: [
"*"
],
AllowedMethods: [
"GET"
],
AllowedOrigins: [
"*"
],
ExposedHeaders: [
"Date"
],
Id: "myCORSRuleId1",
MaxAge: 3600
},
{
AllowedHeaders: [
"x-amz-*"
],
AllowedMethods: [
"DELETE"
],
AllowedOrigins: [
"http://www.example1.com",
"http://www.example2.com"
],
ExposedHeaders: [
"Connection",
"Server",
"Date"
],
Id: "myCORSRuleId2",
MaxAge: 1800
}
]
}
}));

test.done();
},
};

0 comments on commit b28c7ee

Please sign in to comment.