@aws-solutions-constructs/aws-cloudfront-s3
Version:
CDK Constructs for AWS Cloudfront to AWS S3 integration.
847 lines • 108 kB
JavaScript
"use strict";
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const assertions_1 = require("aws-cdk-lib/assertions");
const s3 = require("aws-cdk-lib/aws-s3");
const cdk = require("aws-cdk-lib");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const lib_1 = require("../lib");
const acm = require("aws-cdk-lib/aws-certificatemanager");
const defaults = require("@aws-solutions-constructs/core");
const aws_kms_1 = require("aws-cdk-lib/aws-kms");
const origins = require("aws-cdk-lib/aws-cloudfront-origins");
const aws_cloudfront_1 = require("aws-cdk-lib/aws-cloudfront");
function deploy(stack, props) {
return new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
...props
});
}
test('construct defaults set properties correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
expect(construct.cloudFrontWebDistribution).toBeDefined();
expect(construct.cloudFrontFunction).toBeDefined();
expect(construct.cloudFrontLoggingBucket).toBeDefined();
expect(construct.s3Bucket).toBeDefined();
expect(construct.s3LoggingBucket).toBeDefined();
expect(construct.s3BucketInterface).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket).toBeDefined();
expect(construct.originAccessControl).toBeDefined();
});
test('check s3Bucket default encryption', () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
BucketEncryption: {
ServerSideEncryptionConfiguration: [{
ServerSideEncryptionByDefault: {
SSEAlgorithm: "AES256"
}
}]
}
});
});
test('check s3Bucket public access block configuration', () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true
}
});
});
test('test s3Bucket override publicAccessBlockConfiguration', () => {
const stack = new cdk.Stack();
const props = {
bucketProps: {
blockPublicAccess: {
blockPublicAcls: false,
blockPublicPolicy: true,
ignorePublicAcls: false,
restrictPublicBuckets: true
}
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
PublicAccessBlockConfiguration: {
BlockPublicAcls: false,
BlockPublicPolicy: true,
IgnorePublicAcls: false,
RestrictPublicBuckets: true
},
});
});
test('check existing bucket', () => {
const stack = new cdk.Stack();
const existingBucket = new s3.Bucket(stack, 'my-bucket', {
bucketName: 'my-bucket'
});
const props = {
existingBucketObj: existingBucket
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: "my-bucket"
});
});
test('check exception for Missing existingObj from props for deploy = false', () => {
const stack = new cdk.Stack();
try {
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
}
catch (e) {
expect(e).toBeInstanceOf(Error);
}
});
test('check properties', () => {
const stack = new cdk.Stack();
const construct = deploy(stack);
expect(construct.cloudFrontWebDistribution).toBeDefined();
expect(construct.s3Bucket).toBeDefined();
});
test("Confirm CheckS3Props is called", () => {
// Stack
const stack = new cdk.Stack();
const testBucket = new s3.Bucket(stack, 'test-bucket', {});
const app = () => {
// Helper declaration
new lib_1.CloudFrontToS3(stack, "bad-s3-args", {
existingBucketObj: testBucket,
bucketProps: {
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
},
});
};
// Assertion
expect(app).toThrowError('Error - Either provide bucketProps or existingBucketObj, but not both.\n');
});
test("Test existingBucketObj", () => {
// Stack
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, "existingIBucket", {
existingBucketObj: s3.Bucket.fromBucketName(stack, 'mybucket', 'mybucket')
});
// Assertion
expect(construct.cloudFrontWebDistribution).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
DomainName: {
"Fn::Join": [
"",
[
"mybucket.s3.",
{
Ref: "AWS::Region"
},
".",
{
Ref: "AWS::URLSuffix"
}
]
]
},
Id: "existingIBucketCloudFrontDistributionOrigin1D5849125",
OriginAccessControlId: { "Fn::GetAtt": ["existingIBucketCloudFrontOacEB42E98F", "Id"] },
S3OriginConfig: {}
}
]
}
});
});
test('test cloudfront with custom domain names', () => {
const stack = new cdk.Stack();
const certificate = acm.Certificate.fromCertificateArn(stack, 'Cert', 'arn:${Aws.PARTITION}:acm:us-east-1:123456789012:certificate/11112222-3333-1234-1234-123456789012');
const props = {
cloudFrontDistributionProps: {
domainNames: ['mydomains'],
certificate
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Aliases: [
"mydomains"
]
}
});
});
test('Cloudfront logging bucket with destroy removal policy and auto delete objects', () => {
const stack = new cdk.Stack();
const cloudfrontLogBucketName = 'cf-log-bucket';
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: cloudfrontLogBucketName
}
});
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket", {
OwnershipControls: { Rules: [{ ObjectOwnership: "ObjectWriter" }] },
BucketName: cloudfrontLogBucketName,
});
template.hasResourceProperties("Custom::S3AutoDeleteObjects", {
ServiceToken: {
"Fn::GetAtt": [
"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F",
"Arn"
]
},
BucketName: {
Ref: "cloudfronts3CloudfrontLoggingBucket5B845143"
}
});
});
test('s3 bucket with one content bucket and no access logging of CONTENT bucket', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
logS3AccessLogs: false
});
const template = assertions_1.Template.fromStack(stack);
// Content bucket+Cloudfront Logs bucket+
// Access Log bucket for Cloudfront Logs bucket = 3 buckets
template.resourceCountIs("AWS::S3::Bucket", 3);
expect(construct.s3LoggingBucket).toEqual(undefined);
});
test('CloudFront origin path present when provided', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
originPath: '/testPath'
});
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
OriginPath: "/testPath",
}
]
}
});
});
test('CloudFront origin path should not be present if not provided', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {});
defaults.expectNonexistence(stack, "AWS::CloudFront::Distribution", {
DistributionConfig: {
Origins: [
{
OriginPath: "/testPath",
}
]
}
});
});
test('Test the deployment with securityHeadersBehavior instead of HTTP security headers', () => {
// Initial setup
const stack = new aws_cdk_lib_1.Stack();
const cloudFrontToS3 = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
insertHttpSecurityHeaders: false,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: true,
preload: true
},
contentSecurityPolicy: {
contentSecurityPolicy: "upgrade-insecure-requests; default-src 'none';",
override: true
},
}
}
});
// Assertion
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::ResponseHeadersPolicy", {
ResponseHeadersPolicyConfig: {
SecurityHeadersConfig: {
ContentSecurityPolicy: {
ContentSecurityPolicy: "upgrade-insecure-requests; default-src 'none';",
Override: true
},
StrictTransportSecurity: {
AccessControlMaxAgeSec: 63072,
IncludeSubdomains: true,
Override: true,
Preload: true
}
}
}
});
expect(cloudFrontToS3.cloudFrontFunction).toEqual(undefined);
});
test("throw exception if insertHttpSecurityHeaders and responseHeadersPolicyProps are provided", () => {
const stack = new cdk.Stack();
expect(() => {
new lib_1.CloudFrontToS3(stack, "test-cloudfront-s3", {
insertHttpSecurityHeaders: true,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: false,
preload: true
}
}
}
});
}).toThrowError();
});
test("Confirm CheckCloudFrontProps is being called", () => {
const stack = new cdk.Stack();
expect(() => {
new lib_1.CloudFrontToS3(stack, "test-cloudfront-apigateway", {
insertHttpSecurityHeaders: true,
responseHeadersPolicyProps: {
securityHeadersBehavior: {
strictTransportSecurity: {
accessControlMaxAge: aws_cdk_lib_1.Duration.seconds(63072),
includeSubdomains: true,
override: false,
preload: true
}
}
}
});
}).toThrowError('responseHeadersPolicyProps.securityHeadersBehavior can only be passed if httpSecurityHeaders is set to `false`.');
});
test("Custom resource is provisioned if encryption key is provided as bucketProp", () => {
const stack = new cdk.Stack();
const encryptionKey = new aws_kms_1.Key(stack, 'cmkKey', {
enableKeyRotation: true,
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
});
deploy(stack, {
bucketProps: {
encryptionKey,
encryption: s3.BucketEncryption.KMS
}
});
const template = assertions_1.Template.fromStack(stack);
// 2 Functions - our custom resource and a function created by the CDK
template.resourceCountIs('AWS::Lambda::Function', 2);
template.hasResourceProperties('AWS::Lambda::Function', {
Description: "Custom resource function that updates a provided key policy to allow CloudFront access.",
Role: {
"Fn::GetAtt": ["testcloudfronts3LambdaFunctionServiceRole2A43EA92", "Arn"]
}
});
});
test("Custom resource is provisioned if CMK was used to encrypt an existing bucket", () => {
const stack = new cdk.Stack();
const encryptionKey = new aws_kms_1.Key(stack, 'cmkKey', {
enableKeyRotation: true,
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY
});
const existingBucketObj = defaults.buildS3Bucket(stack, {
bucketProps: {
encryption: s3.BucketEncryption.KMS,
encryptionKey
}
}, 'existing-s3-bucket-encrypted-with-cmk').bucket;
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
existingBucketObj
});
const template = assertions_1.Template.fromStack(stack);
// 2 Functions - our custom resource and a function created by the CDK
template.resourceCountIs('AWS::Lambda::Function', 2);
// ensure that our Function has the correct role attached
template.hasResourceProperties('AWS::Lambda::Function', {
Description: "Custom resource function that updates a provided key policy to allow CloudFront access.",
Role: {
"Fn::GetAtt": ["testcloudfronts3LambdaFunctionServiceRole2A43EA92", "Arn"]
}
});
});
test("Custom resource is not provisioned if encryption key is not provided as bucketProp", () => {
const stack = new cdk.Stack();
deploy(stack);
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs('AWS::Lambda::Function', 0);
});
test("Custom resource is not provisioned if CMK was not used to encrypt an existing bucket", () => {
const stack = new cdk.Stack();
const existingBucketObj = defaults.buildS3Bucket(stack, {}, 'existing-s3-bucket-encrypted-with-cmk').bucket;
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
existingBucketObj
});
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs('AWS::Lambda::Function', 0);
});
test("HttpOrigin is provisioned if a static website bucket is used", () => {
const stack = new cdk.Stack();
const blockPublicAccess = false;
const props = {
bucketProps: {
enforceSSL: false,
publicReadAccess: true, // <-- required for isWebsite
blockPublicAccess: {
blockPublicAcls: blockPublicAccess,
restrictPublicBuckets: blockPublicAccess,
blockPublicPolicy: blockPublicAccess,
ignorePublicAcls: blockPublicAccess
},
websiteIndexDocument: "index.html" // <-- required for isWebsite
},
insertHttpSecurityHeaders: false
};
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.resourceCountIs('AWS::CloudFront::OriginAccessControl', 0);
template.hasResourceProperties('AWS::CloudFront::Distribution', {
DistributionConfig: {
Origins: [
{
CustomOriginConfig: {
OriginProtocolPolicy: "http-only"
}
}
]
}
});
template.resourceCountIs('AWS::CloudFront::OriginAccessIdentity', 0);
// Assert pattern properties (output props)
expect(construct.originAccessControl).toBe(undefined);
});
test("OAC is provisioned in all other cases", () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {});
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.resourceCountIs('AWS::CloudFront::OriginAccessControl', 1);
template.resourceCountIs('AWS::CloudFront::OriginAccessIdentity', 0);
// Assert pattern properties (output props)
expect(construct.originAccessControl).not.toBe(undefined);
});
test("If a customer provides their own httpOrigin, or other origin type, use that one", () => {
const stack = new cdk.Stack();
const blockPublicAccess = false;
const props = {
bucketProps: {
enforceSSL: false,
publicReadAccess: true, // <-- required for isWebsite
blockPublicAccess: {
blockPublicAcls: blockPublicAccess,
restrictPublicBuckets: blockPublicAccess,
blockPublicPolicy: blockPublicAccess,
ignorePublicAcls: blockPublicAccess
},
websiteIndexDocument: "index.html" // <-- required for isWebsite
},
insertHttpSecurityHeaders: false,
cloudFrontDistributionProps: {
defaultBehavior: {
origin: new origins.HttpOrigin('example.com', {
originId: 'custom-http-origin-for-testing'
})
}
}
};
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', props);
const template = assertions_1.Template.fromStack(stack);
// Assert resources
template.hasResourceProperties('AWS::CloudFront::Distribution', {
DistributionConfig: {
Origins: [
{
DomainName: "example.com",
Id: "custom-http-origin-for-testing"
}
]
}
});
});
test('Test that we do not create an Access Log bucket for CF logs if one is provided', () => {
const stack = new cdk.Stack();
const cfS3AccessLogBucket = new s3.Bucket(stack, 'cf-s3-access-logs');
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: cfS3AccessLogBucket
}
});
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
});
// =====================
// S3 Content Bucket Access Logs Bucket
// =====================
test('Providing loggingBucketProps and existingLoggingBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
loggingBucketProps: {
bucketName: 'anything'
}
});
};
expect(app).toThrowError(/Error - bothlog bucket props and an existing log bucket were provided.\n/);
});
test('Providing existingLoggingBucket and logS3AccessLogs=false is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
logS3AccessLogs: false
});
};
expect(app).toThrowError(/Error - logS3AccessLogs is false, but a log bucket was provided in bucketProps.\n/);
});
test('Providing loggingBucketProps and logS3AccessLogs=false is an error', () => {
const stack = new cdk.Stack();
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
loggingBucketProps: {
bucketName: 'anything'
},
logS3AccessLogs: false
});
};
// NOTE: This error is thrown by CheckS3Props(), not CheckConstructSpecificProps()
expect(app).toThrowError(/Error - If logS3AccessLogs is false, supplying loggingBucketProps or existingLoggingBucketObj is invalid.\n/);
});
// test('No new loggingBucket is created if existingLoggingBucket is supplied', () => {
test('loggingBucketProps is supplied is integrated into architecture correctly', () => {
const stack = new cdk.Stack();
const testName = "test-name";
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
},
loggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: testName
}
});
expect(construct.s3LoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "cloudfronts3S3LoggingBucket52EEB708"
}
}
});
});
test('bucketProps:serverAccessLogsBucket is supplied is integrated into architecture correctly', () => {
const testName = 'some-name';
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'test-log', {
bucketName: testName,
});
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
bucketProps: {
serverAccessLogsBucket: logBucket,
},
});
expect(construct.s3LoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "testlogE88B4C6B"
}
}
});
});
// =====================
// CloudFront Log Bucket
// =====================
test('Providing cloudFrontLoggingBucketProps and a log bucket in cloudFrontDistrbutionProps is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontDistributionProps: {
logBucket
},
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
}
});
};
expect(app).toThrowError();
});
test('cloudFrontLoggingBucketProps are used correctly', () => {
const stack = new cdk.Stack();
const testName = "test-name";
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
bucketName: testName
}
});
expect(construct.cloudFrontLoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
});
test('Logging disabled in CloudFront props is handled correctly', () => {
const stack = new cdk.Stack();
const construct = deploy(stack, { cloudFrontDistributionProps: { enableLogging: false } });
const template = assertions_1.Template.fromStack(stack);
// Only the content bucket and it S3 Access Log bucket (no Cloudfront log bucket)
template.resourceCountIs("AWS::S3::Bucket", 2);
// No logging is configured
template.resourcePropertiesCountIs("AWS::CloudFront::Distribution", {
DistributionConfig: {
Logging: assertions_1.Match.anyValue()
}
}, 0);
expect(construct.cloudFrontLoggingBucket === undefined);
});
test('No new CloudFrontLoggingBucket is created if cloudFrontLoggingBucketProps:logBucket is supplied', () => {
const testName = 'random-value';
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {
bucketName: testName
});
// const construct =
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontDistributionProps: {
logBucket
},
});
expect(construct.cloudFrontLoggingBucket).toBeDefined();
const template = assertions_1.Template.fromStack(stack);
// Content bucket, Content bucket S3 Access Log bucket, cloudfront log bucket
template.resourceCountIs("AWS::S3::Bucket", 3);
// Ensure our existing bucket has been used for cloudfront logging
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
Logging: {
Bucket: {
"Fn::GetAtt": [
"cloudfrontlogbucketDF7058FB",
"RegionalDomainName"
]
}
}
}
});
});
// =====================
// CloudFront Logs Bucket Access Log Bucket
// =====================
test('Providing cloudFrontLoggingBucketAccessLogBucketProps and cloudFrontLoggingBucketProps:serverAccessLogsBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: logBucket,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
},
cloudFrontLoggingBucketAccessLogBucketProps: {
bucketName: 'specfic-name-is-inconsequential'
}
});
};
expect(app).toThrowError(/Error - an existing CloudFront log bucket S3 access log bucket and cloudFrontLoggingBucketAccessLogBucketProps were provided\n/);
});
test('Providing cloudFrontLoggingBucketAccessLogBucketProps and logCloudFrontAccessLog=false is an error', () => {
const stack = new cdk.Stack();
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
logCloudFrontAccessLog: false,
cloudFrontLoggingBucketAccessLogBucketProps: {
bucketName: 'specfic-name-is-inconsequential'
}
});
};
expect(app).toThrowError(/Error - cloudFrontLoggingBucketAccessLogBucketProps were provided but logCloudFrontAccessLog was false\n/);
});
test('Providing logCloudFrontAccessLog=false and cloudFrontLoggingBucketProps:serverAccessLogsBucket is an error', () => {
const stack = new cdk.Stack();
const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {});
const app = () => {
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: logBucket,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
},
logCloudFrontAccessLog: false,
});
};
expect(app).toThrowError(/Error - props.cloudFrontLoggingBucketProps.serverAccessLogsBucket was provided but logCloudFrontAccessLog was false\n/);
});
test('cloudFrontLoggingBucketAccessLogBucketProps are used correctly', () => {
const stack = new cdk.Stack();
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketAccessLogBucketProps: {
websiteErrorDocument: 'placeholder',
websiteIndexDocument: 'placeholde-two'
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
WebsiteConfiguration: {
ErrorDocument: 'placeholder',
IndexDocument: 'placeholde-two'
}
});
});
test('If existing CloudFront Log bucket S3 Access Logging bucket is provided, it is used correctly', () => {
const stack = new cdk.Stack();
const testName = 'cf-log-s3-log';
const cfLogS3AccessLogBucket = new s3.Bucket(stack, 'cf-log-s3-access-log-bucket', {
bucketName: testName
});
new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketProps: {
serverAccessLogsBucket: cfLogS3AccessLogBucket
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
BucketName: testName
});
template.hasResourceProperties("AWS::S3::Bucket", {
LoggingConfiguration: {
DestinationBucketName: {
Ref: "cflogs3accesslogbucketDE374C27"
}
}
});
});
test('cloudFrontLoggingBucketAccessLogBucket property is set correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
cloudFrontLoggingBucketAccessLogBucketProps: {
websiteErrorDocument: 'placeholder',
websiteIndexDocument: 'placeholde-two'
}
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 4);
template.hasResourceProperties("AWS::S3::Bucket", {
WebsiteConfiguration: {
ErrorDocument: 'placeholder',
IndexDocument: 'placeholde-two'
}
});
expect(construct.cloudFrontLoggingBucketAccessLogBucket).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket.bucketName).toBeDefined();
});
test('logCloudFrontAccessLog property is used correctly', () => {
const stack = new cdk.Stack();
const construct = new lib_1.CloudFrontToS3(stack, 'cloudfront-s3', {
logCloudFrontAccessLog: false
});
const template = assertions_1.Template.fromStack(stack);
// Content Bucket, Content Bucket S3 Access Log Bucket, CloudFront Log Bucket, CloudFront Log Bucket S3 Access Log Bucket
template.resourceCountIs("AWS::S3::Bucket", 3);
expect(construct.cloudFrontLoggingBucket).toBeDefined();
expect(construct.cloudFrontLoggingBucketAccessLogBucket).not.toBeDefined();
});
test('additionalBehaviors have correct origin', () => {
const stack = new cdk.Stack();
const additionalBucket = defaults.CreateScrapBucket(stack, "scrapBucket", {
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
const originAccessControl = new aws_cloudfront_1.CfnOriginAccessControl(stack, 'CloudFrontOac', {
originAccessControlConfig: {
name: defaults.generatePhysicalOacName('aws-cloudfront-s3-', [__filename]),
originAccessControlOriginType: 's3',
signingBehavior: 'always',
signingProtocol: 'sigv4',
description: 'Origin access control provisioned by aws-cloudfront-s3'
}
});
const additionalOrigin = new defaults.S3OacOrigin(additionalBucket, {
originAccessControl
});
new lib_1.CloudFrontToS3(stack, 'test-cloudfront-s3', {
cloudFrontDistributionProps: {
additionalBehaviors: {
'/assets/public/*': {
origin: additionalOrigin,
cachePolicy: aws_cloudfront_1.CachePolicy.CACHING_DISABLED,
},
'ngsw.json': {
cachePolicy: aws_cloudfront_1.CachePolicy.CACHING_DISABLED,
}
}
}
});
const template = assertions_1.Template.fromStack(stack);
template.hasResourceProperties("AWS::CloudFront::Distribution", {
DistributionConfig: {
CacheBehaviors: [
{
CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
Compress: true,
PathPattern: "/assets/public/*",
TargetOriginId: "testcloudfronts3CloudFrontDistributionOrigin21D78391C",
ViewerProtocolPolicy: "allow-all"
},
{
CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
Compress: true,
PathPattern: "ngsw.json",
TargetOriginId: "testcloudfronts3CloudFrontDistributionOrigin124051039",
ViewerProtocolPolicy: "allow-all"
}
],
}
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5jbG91ZGZyb250LXMzLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZXN0LmNsb3VkZnJvbnQtczMudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7O0dBV0c7O0FBRUgsdURBQXlEO0FBQ3pELHlDQUF5QztBQUN6QyxtQ0FBbUM7QUFDbkMsNkNBQTZEO0FBQzdELGdDQUE2RDtBQUM3RCwwREFBMEQ7QUFDMUQsMkRBQTJEO0FBQzNELGlEQUEwQztBQUMxQyw4REFBOEQ7QUFDOUQsK0RBQWlGO0FBRWpGLFNBQVMsTUFBTSxDQUFDLEtBQWdCLEVBQUUsS0FBMkI7SUFDM0QsT0FBTyxJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLG9CQUFvQixFQUFFO1FBQ3JELFdBQVcsRUFBRTtZQUNYLGFBQWEsRUFBRSxHQUFHLENBQUMsYUFBYSxDQUFDLE9BQU87U0FDekM7UUFDRCxHQUFHLEtBQUs7S0FDVCxDQUFDLENBQUM7QUFDTCxDQUFDO0FBRUQsSUFBSSxDQUFDLDZDQUE2QyxFQUFFLEdBQUcsRUFBRTtJQUN2RCxNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixNQUFNLFNBQVMsR0FBRyxJQUFJLG9CQUFjLENBQUMsS0FBSyxFQUFFLG9CQUFvQixFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBRXRFLE1BQU0sQ0FBQyxTQUFTLENBQUMseUJBQXlCLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUMxRCxNQUFNLENBQUMsU0FBUyxDQUFDLGtCQUFrQixDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDbkQsTUFBTSxDQUFDLFNBQVMsQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3hELE1BQU0sQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDekMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxlQUFlLENBQUMsQ0FB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