From 1e06862ad9f575b5f17b109707cdbbfacaacea44 Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Fri, 15 Oct 2021 20:05:36 +0300 Subject: [PATCH] fix(aws-cloudfront): handle bucket names with dots correctly --- .../__snapshots__/s3-origin.test.ts.snap | 354 ++++++++++++++++++ .../__tests__/s3-origin.test.ts | 110 ++++++ .../src/getBucketNameFromUrl.ts | 10 + .../aws-cloudfront/src/getOriginConfig.ts | 4 +- 4 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 packages/serverless-components/aws-cloudfront/src/getBucketNameFromUrl.ts diff --git a/packages/serverless-components/aws-cloudfront/__tests__/__snapshots__/s3-origin.test.ts.snap b/packages/serverless-components/aws-cloudfront/__tests__/__snapshots__/s3-origin.test.ts.snap index 5f4ba726d6..771b759b05 100644 --- a/packages/serverless-components/aws-cloudfront/__tests__/__snapshots__/s3-origin.test.ts.snap +++ b/packages/serverless-components/aws-cloudfront/__tests__/__snapshots__/s3-origin.test.ts.snap @@ -447,6 +447,360 @@ Object { } `; +exports[`S3 origins when origin is outside of us-east-1 and contains dots should use the origin's host at the DomainName 1`] = ` +Object { + "DistributionConfigWithTags": Object { + "DistributionConfig": Object { + "Aliases": Object { + "Items": Array [], + "Quantity": 0, + }, + "CacheBehaviors": Object { + "Items": Array [], + "Quantity": 0, + }, + "CallerReference": "1566599541192", + "Comment": "", + "CustomErrorResponses": Object { + "Items": Array [], + "Quantity": 0, + }, + "DefaultCacheBehavior": Object { + "AllowedMethods": Object { + "CachedMethods": Object { + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Compress": false, + "DefaultTTL": 86400, + "FieldLevelEncryptionId": "", + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "Headers": Object { + "Items": Array [], + "Quantity": 0, + }, + "QueryString": false, + "QueryStringCacheKeys": Object { + "Items": Array [], + "Quantity": 0, + }, + }, + "LambdaFunctionAssociations": Object { + "Items": Array [], + "Quantity": 0, + }, + "MaxTTL": 31536000, + "MinTTL": 0, + "SmoothStreaming": false, + "TargetOriginId": "mybucket.with.dots", + "TrustedSigners": Object { + "Enabled": false, + "Items": Array [], + "Quantity": 0, + }, + "ViewerProtocolPolicy": "redirect-to-https", + }, + "Enabled": true, + "HttpVersion": "http2", + "Origins": Object { + "Items": Array [ + Object { + "CustomHeaders": Object { + "Items": Array [], + "Quantity": 0, + }, + "DomainName": "mybucket.with.dots.s3.eu-west-1.amazonaws.com", + "Id": "mybucket.with.dots", + "OriginPath": "", + "S3OriginConfig": Object { + "OriginAccessIdentity": "", + }, + }, + ], + "Quantity": 1, + }, + "PriceClass": "PriceClass_All", + }, + "Tags": Object { + "Items": Array [], + }, + }, +} +`; + +exports[`S3 origins when origin is outside of us-east-1 and contains dots updates distribution 1`] = ` +Object { + "DistributionConfig": Object { + "CacheBehaviors": Object { + "Items": Array [], + "Quantity": 0, + }, + "Comment": "", + "CustomErrorResponses": Object { + "Items": Array [], + "Quantity": 0, + }, + "DefaultCacheBehavior": Object { + "AllowedMethods": Object { + "CachedMethods": Object { + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Compress": false, + "DefaultTTL": 86400, + "FieldLevelEncryptionId": "", + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "Headers": Object { + "Items": Array [], + "Quantity": 0, + }, + "QueryString": false, + "QueryStringCacheKeys": Object { + "Items": Array [], + "Quantity": 0, + }, + }, + "LambdaFunctionAssociations": Object { + "Items": Array [], + "Quantity": 0, + }, + "MaxTTL": 31536000, + "MinTTL": 0, + "SmoothStreaming": false, + "TargetOriginId": "anotherbucket", + "TrustedSigners": Object { + "Enabled": false, + "Items": Array [], + "Quantity": 0, + }, + "ViewerProtocolPolicy": "redirect-to-https", + }, + "Enabled": true, + "Origins": Object { + "Items": Array [ + Object { + "CustomHeaders": Object { + "Items": Array [], + "Quantity": 0, + }, + "DomainName": "anotherbucket.s3.eu-west-1.amazonaws.com", + "Id": "anotherbucket", + "OriginPath": "", + "S3OriginConfig": Object { + "OriginAccessIdentity": "", + }, + }, + ], + "Quantity": 1, + }, + "PriceClass": "PriceClass_All", + }, + "Id": "distributionwithS3origin", + "IfMatch": "etag", +} +`; + +exports[`S3 origins when origin is outside of us-east-1 and contains s3 and dots should use the origin's host at the DomainName 1`] = ` +Object { + "DistributionConfigWithTags": Object { + "DistributionConfig": Object { + "Aliases": Object { + "Items": Array [], + "Quantity": 0, + }, + "CacheBehaviors": Object { + "Items": Array [], + "Quantity": 0, + }, + "CallerReference": "1566599541192", + "Comment": "", + "CustomErrorResponses": Object { + "Items": Array [], + "Quantity": 0, + }, + "DefaultCacheBehavior": Object { + "AllowedMethods": Object { + "CachedMethods": Object { + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Compress": false, + "DefaultTTL": 86400, + "FieldLevelEncryptionId": "", + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "Headers": Object { + "Items": Array [], + "Quantity": 0, + }, + "QueryString": false, + "QueryStringCacheKeys": Object { + "Items": Array [], + "Quantity": 0, + }, + }, + "LambdaFunctionAssociations": Object { + "Items": Array [], + "Quantity": 0, + }, + "MaxTTL": 31536000, + "MinTTL": 0, + "SmoothStreaming": false, + "TargetOriginId": "mybucket.s3.s3", + "TrustedSigners": Object { + "Enabled": false, + "Items": Array [], + "Quantity": 0, + }, + "ViewerProtocolPolicy": "redirect-to-https", + }, + "Enabled": true, + "HttpVersion": "http2", + "Origins": Object { + "Items": Array [ + Object { + "CustomHeaders": Object { + "Items": Array [], + "Quantity": 0, + }, + "DomainName": "mybucket.s3.s3.s3.eu-west-1.amazonaws.com", + "Id": "mybucket.s3.s3", + "OriginPath": "", + "S3OriginConfig": Object { + "OriginAccessIdentity": "", + }, + }, + ], + "Quantity": 1, + }, + "PriceClass": "PriceClass_All", + }, + "Tags": Object { + "Items": Array [], + }, + }, +} +`; + +exports[`S3 origins when origin is outside of us-east-1 and contains s3 and dots updates distribution 1`] = ` +Object { + "DistributionConfig": Object { + "CacheBehaviors": Object { + "Items": Array [], + "Quantity": 0, + }, + "Comment": "", + "CustomErrorResponses": Object { + "Items": Array [], + "Quantity": 0, + }, + "DefaultCacheBehavior": Object { + "AllowedMethods": Object { + "CachedMethods": Object { + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Items": Array [ + "HEAD", + "GET", + ], + "Quantity": 2, + }, + "Compress": false, + "DefaultTTL": 86400, + "FieldLevelEncryptionId": "", + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "Headers": Object { + "Items": Array [], + "Quantity": 0, + }, + "QueryString": false, + "QueryStringCacheKeys": Object { + "Items": Array [], + "Quantity": 0, + }, + }, + "LambdaFunctionAssociations": Object { + "Items": Array [], + "Quantity": 0, + }, + "MaxTTL": 31536000, + "MinTTL": 0, + "SmoothStreaming": false, + "TargetOriginId": "anotherbucket", + "TrustedSigners": Object { + "Enabled": false, + "Items": Array [], + "Quantity": 0, + }, + "ViewerProtocolPolicy": "redirect-to-https", + }, + "Enabled": true, + "Origins": Object { + "Items": Array [ + Object { + "CustomHeaders": Object { + "Items": Array [], + "Quantity": 0, + }, + "DomainName": "anotherbucket.s3.eu-west-1.amazonaws.com", + "Id": "anotherbucket", + "OriginPath": "", + "S3OriginConfig": Object { + "OriginAccessIdentity": "", + }, + }, + ], + "Quantity": 1, + }, + "PriceClass": "PriceClass_All", + }, + "Id": "distributionwithS3origin", + "IfMatch": "etag", +} +`; + exports[`S3 origins when origin is outside of us-east-1 should use the origin's host at the DomainName 1`] = ` Object { "DistributionConfigWithTags": Object { diff --git a/packages/serverless-components/aws-cloudfront/__tests__/s3-origin.test.ts b/packages/serverless-components/aws-cloudfront/__tests__/s3-origin.test.ts index f0679dea0b..23c4aadca6 100644 --- a/packages/serverless-components/aws-cloudfront/__tests__/s3-origin.test.ts +++ b/packages/serverless-components/aws-cloudfront/__tests__/s3-origin.test.ts @@ -300,4 +300,114 @@ describe("S3 origins", () => { expect(mockUpdateDistribution.mock.calls[0][0]).toMatchSnapshot(); }); }); + + describe("when origin is outside of us-east-1 and contains dots", () => { + it("should use the origin's host at the DomainName", async () => { + await component.default({ + origins: ["https://mybucket.with.dots.s3.eu-west-1.amazonaws.com"] + }); + + assertCDWTHasOrigin(mockCreateDistributionWithTags, { + Id: "mybucket.with.dots", + DomainName: "mybucket.with.dots.s3.eu-west-1.amazonaws.com", + S3OriginConfig: { + OriginAccessIdentity: "" + }, + CustomHeaders: { + Quantity: 0, + Items: [] + }, + OriginPath: "" + }); + + expect(mockCreateDistributionWithTags.mock.calls[0][0]).toMatchSnapshot(); + }); + + it("updates distribution", async () => { + mockGetDistributionConfigPromise.mockResolvedValueOnce({ + ETag: "etag", + DistributionConfig: { + Origins: { + Quantity: 0, + Items: [] + } + } + }); + mockUpdateDistributionPromise.mockResolvedValueOnce({ + Distribution: { + Id: "distributionwithS3originupdated" + } + }); + + await component.default({ + origins: ["https://mybucket.with.dots.s3.eu-west-1.amazonaws.com"] + }); + + await component.default({ + origins: ["https://anotherbucket.s3.eu-west-1.amazonaws.com"] + }); + + assertHasOrigin(mockUpdateDistribution, { + Id: "anotherbucket", + DomainName: "anotherbucket.s3.eu-west-1.amazonaws.com" + }); + + expect(mockUpdateDistribution.mock.calls[0][0]).toMatchSnapshot(); + }); + }); + + describe("when origin is outside of us-east-1 and contains s3 and dots", () => { + it("should use the origin's host at the DomainName", async () => { + await component.default({ + origins: ["https://mybucket.s3.s3.s3.eu-west-1.amazonaws.com"] + }); + + assertCDWTHasOrigin(mockCreateDistributionWithTags, { + Id: "mybucket.s3.s3", + DomainName: "mybucket.s3.s3.s3.eu-west-1.amazonaws.com", + S3OriginConfig: { + OriginAccessIdentity: "" + }, + CustomHeaders: { + Quantity: 0, + Items: [] + }, + OriginPath: "" + }); + + expect(mockCreateDistributionWithTags.mock.calls[0][0]).toMatchSnapshot(); + }); + + it("updates distribution", async () => { + mockGetDistributionConfigPromise.mockResolvedValueOnce({ + ETag: "etag", + DistributionConfig: { + Origins: { + Quantity: 0, + Items: [] + } + } + }); + mockUpdateDistributionPromise.mockResolvedValueOnce({ + Distribution: { + Id: "distributionwithS3originupdated" + } + }); + + await component.default({ + origins: ["https://mybucket.s3.s3.s3.eu-west-1.amazonaws.com"] + }); + + await component.default({ + origins: ["https://anotherbucket.s3.eu-west-1.amazonaws.com"] + }); + + assertHasOrigin(mockUpdateDistribution, { + Id: "anotherbucket", + DomainName: "anotherbucket.s3.eu-west-1.amazonaws.com" + }); + + expect(mockUpdateDistribution.mock.calls[0][0]).toMatchSnapshot(); + }); + }); }); diff --git a/packages/serverless-components/aws-cloudfront/src/getBucketNameFromUrl.ts b/packages/serverless-components/aws-cloudfront/src/getBucketNameFromUrl.ts new file mode 100644 index 0000000000..d807b7e258 --- /dev/null +++ b/packages/serverless-components/aws-cloudfront/src/getBucketNameFromUrl.ts @@ -0,0 +1,10 @@ +/** + * Returns a bucket name for given S3 bucket url + * + * @param url S3 website URL + * + * @returns Bucket name + */ +export const getBucketNameFromUrl = (url: string): string => { + return url.substring(0, url.lastIndexOf(".s3")); +}; diff --git a/packages/serverless-components/aws-cloudfront/src/getOriginConfig.ts b/packages/serverless-components/aws-cloudfront/src/getOriginConfig.ts index b5506453cc..b93427b398 100644 --- a/packages/serverless-components/aws-cloudfront/src/getOriginConfig.ts +++ b/packages/serverless-components/aws-cloudfront/src/getOriginConfig.ts @@ -1,3 +1,5 @@ +import { getBucketNameFromUrl } from "./getBucketNameFromUrl"; + export type OriginConfig = { Id: string; DomainName: string; @@ -47,7 +49,7 @@ export const getOriginConfig = ( }; if (originUrl.includes("s3")) { - const bucketName = hostname.split(".")[0]; + const bucketName = getBucketNameFromUrl(hostname); originConfig.Id = bucketName; originConfig.DomainName = hostname; originConfig.S3OriginConfig = {