UNPKG

@aws-solutions-constructs/aws-cloudfront-s3

Version:

CDK Constructs for AWS Cloudfront to AWS S3 integration.

793 lines 101 kB
"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"); 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(); }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5jbG91ZGZyb250LXMzLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZXN0LmNsb3VkZnJvbnQtczMudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7O0dBV0c7O0FBRUgsdURBQXlEO0FBQ3pELHlDQUF5QztBQUN6QyxtQ0FBbUM7QUFDbkMsNkNBQTZEO0FBQzdELGdDQUE2RDtBQUM3RCwwREFBMEQ7QUFDMUQsMkRBQTJEO0FBQzNELGlEQUEwQztBQUMxQyw4REFBOEQ7QUFFOUQsU0FBUyxNQUFNLENBQUMsS0FBZ0IsRUFBRSxLQUEyQjtJQUMzRCxPQUFPLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUU7UUFDckQsV0FBVyxFQUFFO1lBQ1gsYUFBYSxFQUFFLEdBQUcsQ0FBQyxhQUFhLENBQUMsT0FBTztTQUN6QztRQUNELEdBQUcsS0FBSztLQUNULENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxJQUFJLENBQUMsNkNBQTZDLEVBQUUsR0FBRyxFQUFFO0lBQ3ZELE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzlCLE1BQU0sU0FBUyxHQUFHLElBQUksb0JBQWMsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFdEUsTUFBTSxDQUFDLFNBQVMsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQzFELE1BQU0sQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNuRCxNQUFNLENBQUMsU0FBUyxDQUFDLHVCQUF1QixDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDeEQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUN6QyxNQUFNLENBQUMsU0FBUyxDQUFDLGVBQWUsQ0FBQyxDQUFDLFdBQVcsRUFBRSxD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