Skip to content

Commit 6d3c7c9

Browse files
feat(aws-cloudfront-s3): added logS3AccessLogs prop (#506)
* added logS3AccessLogs * updated integ test with no logging bucket * added cfn nag suppress rule for no logging bucket Co-authored-by: biffgaut <[email protected]>
1 parent b769f85 commit 6d3c7c9

File tree

6 files changed

+127
-166
lines changed

6 files changed

+127
-166
lines changed

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ _Parameters_
4545

4646
| **Name** | **Type** | **Description** |
4747
|:-------------|:----------------|-----------------|
48-
|existingBucketInterface?|[`s3.IBucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.IBucket.html)|Existing instance of S3 Bucket object or interface. If this is provided, then also providing bucketProps will cause an error. |
48+
|existingBucketObj?|[`s3.IBucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.IBucket.html)|Existing instance of S3 Bucket object or interface. If this is provided, then also providing bucketProps will cause an error. |
4949
|bucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for the S3 Bucket.|
5050
|cloudFrontDistributionProps?|[`cloudfront.DistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.DistributionProps.html)|Optional user provided props to override the default props for CloudFront Distribution|
5151
|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all responses from CloudFront|
5252
|loggingBucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for the S3 Logging Bucket.|
5353
|cloudFrontLoggingBucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for the CloudFront Logging Bucket.|
54+
|logS3AccessLogs?| boolean|Whether to turn on Access Logging for the S3 bucket. Creates an S3 bucket with associated storage costs for the logs. Enabling Access Logging is a best practice. default - true|
5455

5556
## Pattern Properties
5657

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts

+80-68
Original file line numberDiff line numberDiff line change
@@ -21,77 +21,89 @@ import * as defaults from '@aws-solutions-constructs/core';
2121
* @summary The properties for the CloudFrontToS3 Construct
2222
*/
2323
export interface CloudFrontToS3Props {
24-
/**
25-
* Existing instance of S3 Bucket object, providing both this and `bucketProps` will cause an error.
26-
*
27-
* @default - None
28-
*/
29-
readonly existingBucketInterface?: s3.IBucket,
30-
/**
31-
* Optional user provided props to override the default props for the S3 Bucket.
32-
*
33-
* @default - Default props are used
34-
*/
35-
readonly bucketProps?: s3.BucketProps,
36-
/**
37-
* Optional user provided props to override the default props
38-
*
39-
* @default - Default props are used
40-
*/
41-
readonly cloudFrontDistributionProps?: cloudfront.DistributionProps | any,
42-
/**
43-
* Optional user provided props to turn on/off the automatic injection of best practice HTTP
44-
* security headers in all responses from cloudfront
45-
*
46-
* @default - true
47-
*/
48-
readonly insertHttpSecurityHeaders?: boolean;
49-
/**
50-
* Optional user provided props to override the default props for the S3 Logging Bucket.
51-
*
52-
* @default - Default props are used
53-
*/
54-
readonly loggingBucketProps?: s3.BucketProps
55-
/**
56-
* Optional user provided props to override the default props for the CloudFront Logging Bucket.
57-
*
58-
* @default - Default props are used
59-
*/
60-
readonly cloudFrontLoggingBucketProps?: s3.BucketProps
61-
}
24+
/**
25+
* Existing instance of S3 Bucket object, providing both this and `bucketProps` will cause an error.
26+
*
27+
* @default - None
28+
*/
29+
readonly existingBucketObj?: s3.IBucket,
30+
/**
31+
* Optional user provided props to override the default props for the S3 Bucket.
32+
*
33+
* @default - Default props are used
34+
*/
35+
readonly bucketProps?: s3.BucketProps,
36+
/**
37+
* Optional user provided props to override the default props
38+
*
39+
* @default - Default props are used
40+
*/
41+
readonly cloudFrontDistributionProps?: cloudfront.DistributionProps | any,
42+
/**
43+
* Optional user provided props to turn on/off the automatic injection of best practice HTTP
44+
* security headers in all responses from cloudfront
45+
*
46+
* @default - true
47+
*/
48+
readonly insertHttpSecurityHeaders?: boolean;
49+
/**
50+
* Optional user provided props to override the default props for the S3 Logging Bucket.
51+
*
52+
* @default - Default props are used
53+
*/
54+
readonly loggingBucketProps?: s3.BucketProps
55+
/**
56+
* Optional user provided props to override the default props for the CloudFront Logging Bucket.
57+
*
58+
* @default - Default props are used
59+
*/
60+
readonly cloudFrontLoggingBucketProps?: s3.BucketProps
61+
/**
62+
* Whether to turn on Access Logs for the S3 bucket with the associated storage costs.
63+
* Enabling Access Logging is a best practice.
64+
*
65+
* @default - true
66+
*/
67+
readonly logS3AccessLogs?: boolean;
68+
}
6269

6370
export class CloudFrontToS3 extends Construct {
64-
public readonly cloudFrontWebDistribution: cloudfront.Distribution;
65-
public readonly cloudFrontFunction?: cloudfront.Function;
66-
public readonly cloudFrontLoggingBucket?: s3.Bucket;
67-
public readonly s3BucketInterface: s3.IBucket;
68-
public readonly s3Bucket?: s3.Bucket;
69-
public readonly s3LoggingBucket?: s3.Bucket;
71+
public readonly cloudFrontWebDistribution: cloudfront.Distribution;
72+
public readonly cloudFrontFunction?: cloudfront.Function;
73+
public readonly cloudFrontLoggingBucket?: s3.Bucket;
74+
public readonly s3BucketInterface: s3.IBucket;
75+
public readonly s3Bucket?: s3.Bucket;
76+
public readonly s3LoggingBucket?: s3.Bucket;
77+
78+
/**
79+
* @summary Constructs a new instance of the CloudFrontToS3 class.
80+
* @param {cdk.App} scope - represents the scope for all the resources.
81+
* @param {string} id - this is a a scope-unique id.
82+
* @param {CloudFrontToS3Props} props - user provided props for the construct
83+
* @since 0.8.0
84+
* @access public
85+
*/
86+
constructor(scope: Construct, id: string, props: CloudFrontToS3Props) {
87+
super(scope, id);
88+
defaults.CheckProps(props);
89+
90+
let bucket: s3.IBucket;
7091

71-
/**
72-
* @summary Constructs a new instance of the CloudFrontToS3 class.
73-
* @param {cdk.App} scope - represents the scope for all the resources.
74-
* @param {string} id - this is a a scope-unique id.
75-
* @param {CloudFrontToS3Props} props - user provided props for the construct
76-
* @since 0.8.0
77-
* @access public
78-
*/
79-
constructor(scope: Construct, id: string, props: CloudFrontToS3Props) {
80-
super(scope, id);
81-
defaults.CheckProps(props);
92+
if (!props.existingBucketObj) {
93+
[this.s3Bucket, this.s3LoggingBucket] = defaults.buildS3Bucket(this, {
94+
bucketProps: props.bucketProps,
95+
loggingBucketProps: props.loggingBucketProps,
96+
logS3AccessLogs: props.logS3AccessLogs
97+
});
98+
bucket = this.s3Bucket;
99+
} else {
100+
bucket = props.existingBucketObj;
101+
}
82102

83-
if (!props.existingBucketInterface) {
84-
[this.s3Bucket, this.s3LoggingBucket] = defaults.buildS3Bucket(this, {
85-
bucketProps: props.bucketProps,
86-
loggingBucketProps: props.loggingBucketProps
87-
});
88-
this.s3BucketInterface = this.s3Bucket;
89-
} else {
90-
this.s3BucketInterface = props.existingBucketInterface;
91-
}
103+
this.s3BucketInterface = bucket;
92104

93-
[this.cloudFrontWebDistribution, this.cloudFrontFunction, this.cloudFrontLoggingBucket] =
94-
defaults.CloudFrontDistributionForS3(this, this.s3BucketInterface,
95-
props.cloudFrontDistributionProps, props.insertHttpSecurityHeaders, props.cloudFrontLoggingBucketProps);
96-
}
105+
[this.cloudFrontWebDistribution, this.cloudFrontFunction, this.cloudFrontLoggingBucket] =
106+
defaults.CloudFrontDistributionForS3(this, this.s3BucketInterface,
107+
props.cloudFrontDistributionProps, props.insertHttpSecurityHeaders, props.cloudFrontLoggingBucketProps);
108+
}
97109
}

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.existing-bucket.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ let mybucket: s3.Bucket;
2929
mybucket = defaults.CreateScrapBucket(stack, { removalPolicy: RemovalPolicy.DESTROY });
3030

3131
const _construct = new CloudFrontToS3(stack, 'test-cloudfront-s3', {
32-
existingBucketInterface: mybucket,
32+
existingBucketObj: mybucket,
3333
});
3434

3535
// Add Cache Policy

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.expected.json

+11-90
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,6 @@
11
{
22
"Description": "Integration Test for aws-cloudfront-s3",
33
"Resources": {
4-
"testcloudfronts3S3LoggingBucket90D239DD": {
5-
"Type": "AWS::S3::Bucket",
6-
"Properties": {
7-
"AccessControl": "LogDeliveryWrite",
8-
"BucketEncryption": {
9-
"ServerSideEncryptionConfiguration": [
10-
{
11-
"ServerSideEncryptionByDefault": {
12-
"SSEAlgorithm": "AES256"
13-
}
14-
}
15-
]
16-
},
17-
"PublicAccessBlockConfiguration": {
18-
"BlockPublicAcls": true,
19-
"BlockPublicPolicy": true,
20-
"IgnorePublicAcls": true,
21-
"RestrictPublicBuckets": true
22-
},
23-
"VersioningConfiguration": {
24-
"Status": "Enabled"
25-
}
26-
},
27-
"UpdateReplacePolicy": "Delete",
28-
"DeletionPolicy": "Delete",
29-
"Metadata": {
30-
"cfn_nag": {
31-
"rules_to_suppress": [
32-
{
33-
"id": "W35",
34-
"reason": "This S3 bucket is used as the access logging bucket for another bucket"
35-
}
36-
]
37-
}
38-
}
39-
},
40-
"testcloudfronts3S3LoggingBucketPolicy529D4CFF": {
41-
"Type": "AWS::S3::BucketPolicy",
42-
"Properties": {
43-
"Bucket": {
44-
"Ref": "testcloudfronts3S3LoggingBucket90D239DD"
45-
},
46-
"PolicyDocument": {
47-
"Statement": [
48-
{
49-
"Action": "*",
50-
"Condition": {
51-
"Bool": {
52-
"aws:SecureTransport": "false"
53-
}
54-
},
55-
"Effect": "Deny",
56-
"Principal": {
57-
"AWS": "*"
58-
},
59-
"Resource": [
60-
{
61-
"Fn::Join": [
62-
"",
63-
[
64-
{
65-
"Fn::GetAtt": [
66-
"testcloudfronts3S3LoggingBucket90D239DD",
67-
"Arn"
68-
]
69-
},
70-
"/*"
71-
]
72-
]
73-
},
74-
{
75-
"Fn::GetAtt": [
76-
"testcloudfronts3S3LoggingBucket90D239DD",
77-
"Arn"
78-
]
79-
}
80-
],
81-
"Sid": "HttpsOnly"
82-
}
83-
],
84-
"Version": "2012-10-17"
85-
}
86-
}
87-
},
884
"testcloudfronts3S3BucketE0C5F76E": {
895
"Type": "AWS::S3::Bucket",
906
"Properties": {
@@ -110,11 +26,6 @@
11026
}
11127
]
11228
},
113-
"LoggingConfiguration": {
114-
"DestinationBucketName": {
115-
"Ref": "testcloudfronts3S3LoggingBucket90D239DD"
116-
}
117-
},
11829
"PublicAccessBlockConfiguration": {
11930
"BlockPublicAcls": true,
12031
"BlockPublicPolicy": true,
@@ -126,7 +37,17 @@
12637
}
12738
},
12839
"UpdateReplacePolicy": "Delete",
129-
"DeletionPolicy": "Delete"
40+
"DeletionPolicy": "Delete",
41+
"Metadata": {
42+
"cfn_nag": {
43+
"rules_to_suppress": [
44+
{
45+
"id": "W35",
46+
"reason": "This S3 bucket is created for unit/ integration testing purposes only."
47+
}
48+
]
49+
}
50+
}
13051
},
13152
"testcloudfronts3S3BucketPolicy250F1F61": {
13253
"Type": "AWS::S3::BucketPolicy",

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,27 @@
1515
import { App, Stack, RemovalPolicy } from "@aws-cdk/core";
1616
import { CloudFrontToS3 } from "../lib";
1717
import { generateIntegStackName } from '@aws-solutions-constructs/core';
18+
import * as s3 from "@aws-cdk/aws-s3";
19+
import * as defaults from '@aws-solutions-constructs/core';
1820

1921
// Setup
2022
const app = new App();
2123
const stack = new Stack(app, generateIntegStackName(__filename));
2224
stack.templateOptions.description = 'Integration Test for aws-cloudfront-s3';
2325

24-
new CloudFrontToS3(stack, 'test-cloudfront-s3', {
26+
const construct = new CloudFrontToS3(stack, 'test-cloudfront-s3', {
2527
bucketProps: {
2628
removalPolicy: RemovalPolicy.DESTROY,
27-
}
29+
},
30+
logS3AccessLogs: false
2831
});
2932

33+
const s3Bucket = construct.s3Bucket as s3.Bucket;
34+
35+
defaults.addCfnSuppressRules(s3Bucket, [
36+
{ id: 'W35',
37+
reason: 'This S3 bucket is created for unit/ integration testing purposes only.' },
38+
]);
39+
3040
// Synth
3141
app.synth();

source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ test('check existing bucket', () => {
8989
});
9090

9191
const props: CloudFrontToS3Props = {
92-
existingBucketInterface: existingBucket
92+
existingBucketObj: existingBucket
9393
};
9494

9595
new CloudFrontToS3(stack, 'test-cloudfront-s3', props);
@@ -143,7 +143,7 @@ test("Test bad call with existingBucket and bucketProps", () => {
143143
const app = () => {
144144
// Helper declaration
145145
new CloudFrontToS3(stack, "bad-s3-args", {
146-
existingBucketInterface: testBucket,
146+
existingBucketObj: testBucket,
147147
bucketProps: {
148148
removalPolicy: RemovalPolicy.DESTROY
149149
},
@@ -153,11 +153,11 @@ test("Test bad call with existingBucket and bucketProps", () => {
153153
expect(app).toThrowError();
154154
});
155155

156-
test("Test existingBucketInterface", () => {
156+
test("Test existingBucketObj", () => {
157157
// Stack
158158
const stack = new cdk.Stack();
159159
const construct: CloudFrontToS3 = new CloudFrontToS3(stack, "existingIBucket", {
160-
existingBucketInterface: s3.Bucket.fromBucketName(stack, 'mybucket', 'mybucket')
160+
existingBucketObj: s3.Bucket.fromBucketName(stack, 'mybucket', 'mybucket')
161161
});
162162
// Assertion
163163
expect(construct.cloudFrontWebDistribution !== null);
@@ -313,4 +313,21 @@ test('Cloudfront logging bucket error when providing existing log bucket and log
313313
};
314314

315315
expect(app).toThrowError();
316+
});
317+
318+
// --------------------------------------------------------------
319+
// s3 bucket with one content bucket and no logging bucket
320+
// --------------------------------------------------------------
321+
test('s3 bucket with one content bucket and no logging bucket', () => {
322+
const stack = new cdk.Stack();
323+
324+
const construct = new CloudFrontToS3(stack, 'cloudfront-s3', {
325+
bucketProps: {
326+
removalPolicy: cdk.RemovalPolicy.DESTROY,
327+
},
328+
logS3AccessLogs: false
329+
});
330+
331+
expect(stack).toCountResources("AWS::S3::Bucket", 2);
332+
expect(construct.s3LoggingBucket).toEqual(undefined);
316333
});

0 commit comments

Comments
 (0)