UNPKG

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

Version:

CDK Constructs for AWS Cloudfront to AWS S3 integration.

847 lines 108 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"); 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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