From 038bc4df0ab6edc84b7819d08f5331deb7c21d6e Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 24 Dec 2020 19:47:30 +0000 Subject: [PATCH 01/11] chore: modify bucketName to include custom endpoint --- .../src/bucketEndpointMiddleware.ts | 3 + .../src/bucketHostname.ts | 59 +++++++++++-------- .../src/bucketHostnameUtils.ts | 1 + 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts index 7e1062a04432..8184be0ff5ff 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts @@ -42,6 +42,7 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): useArnRegion, clientPartition: partition, clientSigningRegion: signingRegion, + region: clientRegion, }); // If the request needs to use a region or service name inferred from ARN that different from client region, we @@ -56,8 +57,10 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): request.hostname = hostname; replaceBucketInPath = bucketEndpoint; } else { + const clientRegion = getPseudoRegion(await options.region()); const { hostname, bucketEndpoint } = bucketHostname({ bucketName, + region: clientRegion, baseHostname: request.hostname, accelerateEndpoint: options.useAccelerateEndpoint, dualstackEndpoint: options.useDualstackEndpoint, diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index 442b1904537c..f2ed72103b61 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -28,23 +28,28 @@ export interface BucketHostname { } export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams): BucketHostname => { - const { baseHostname } = options; - if (!S3_HOSTNAME_PATTERN.test(baseHostname)) { - return { - bucketEndpoint: false, - hostname: baseHostname, - }; + const { baseHostname, dualstackEndpoint, accelerateEndpoint } = options; + const isCustomEndpoint = !S3_HOSTNAME_PATTERN.test(baseHostname); + + if (isCustomEndpoint) { + if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); + if (accelerateEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); } + return isBucketNameOptions(options) ? // Construct endpoint when bucketName is a string referring to a bucket name - getEndpointFromBucketName(options) + getEndpointFromBucketName(isCustomEndpoint, options) : // Construct endpoint when bucketName is an ARN referring to an S3 resource like Access Point - getEndpointFromArn(options); + getEndpointFromArn(isCustomEndpoint, options); }; -const getEndpointFromArn = (options: ArnHostnameParams): BucketHostname => { - // Infer client region and hostname suffix from hostname from endpoints.json, like `s3.us-west-2.amazonaws.com` - const [clientRegion, hostnameSuffix] = getSuffixForArnEndpoint(options.baseHostname); +const getEndpointFromArn = (isCustomEndpoint: boolean, options: ArnHostnameParams): BucketHostname => { + const { baseHostname } = options; + const [clientRegion, hostnameSuffix] = isCustomEndpoint + ? [options.region, baseHostname] + : // Infer client region and hostname suffix from hostname from endpoints.json, like `s3.us-west-2.amazonaws.com` + getSuffixForArnEndpoint(baseHostname); + const { pathStyleEndpoint, dualstackEndpoint = false, @@ -75,33 +80,39 @@ const getEndpointFromArn = (options: ArnHostnameParams): BucketHostname => { validateDNSHostLabel(outpostId, { tlsCompatible }); validateNoDualstack(dualstackEndpoint); validateNoFIPS(endpointRegion); + const hostnamePrefix = `${accesspointName}-${accountId}.${outpostId}`; return { bucketEndpoint: true, - hostname: `${accesspointName}-${accountId}.${outpostId}.s3-outposts.${endpointRegion}.${hostnameSuffix}`, + hostname: `${hostnamePrefix}${isCustomEndpoint ? "" : `.s3-outposts.${endpointRegion}`}.${hostnameSuffix}`, signingRegion, signingService: "s3-outposts", }; } // construct endpoint from Accesspoint ARN validateS3Service(service); + const hostnamePrefix = `${accesspointName}-${accountId}`; return { bucketEndpoint: true, - hostname: `${accesspointName}-${accountId}.s3-accesspoint${ - dualstackEndpoint ? ".dualstack" : "" - }.${endpointRegion}.${hostnameSuffix}`, + hostname: `${hostnamePrefix}${ + isCustomEndpoint ? "" : `.s3-accesspoint${dualstackEndpoint ? ".dualstack" : ""}.${endpointRegion}` + }.${hostnameSuffix}`, signingRegion, }; }; -const getEndpointFromBucketName = ({ - accelerateEndpoint = false, - baseHostname, - bucketName, - dualstackEndpoint = false, - pathStyleEndpoint = false, - tlsCompatible = true, -}: BucketHostnameParams): BucketHostname => { - const [clientRegion, hostnameSuffix] = getSuffix(baseHostname); +const getEndpointFromBucketName = ( + isCustomEndpoint: boolean, + { + accelerateEndpoint = false, + region, + baseHostname, + bucketName, + dualstackEndpoint = false, + pathStyleEndpoint = false, + tlsCompatible = true, + }: BucketHostnameParams +): BucketHostname => { + const [clientRegion, hostnameSuffix] = isCustomEndpoint ? [region, baseHostname] : getSuffix(baseHostname); if (pathStyleEndpoint || !isDnsCompatibleBucketName(bucketName) || (tlsCompatible && DOT_PATTERN.test(bucketName))) { return { bucketEndpoint: false, diff --git a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts index 974093e173f9..2b42b849b766 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts @@ -15,6 +15,7 @@ export interface AccessPointArn extends ARN { export interface BucketHostnameParams { baseHostname: string; bucketName: string; + region: string; accelerateEndpoint?: boolean; dualstackEndpoint?: boolean; pathStyleEndpoint?: boolean; From ffe37d9bb0bd632d9077b1c267bcfbf43743faa2 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 24 Dec 2020 17:41:16 +0000 Subject: [PATCH 02/11] test: prepare bucketHostname tests for custom endpoint --- .../src/bucketEndpointMiddleware.spec.ts | 12 +- .../src/bucketHostname.spec.ts | 213 ++++++++++-------- 2 files changed, 133 insertions(+), 92 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts index d841c52fff7c..2a18c974b14a 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts @@ -18,6 +18,7 @@ import { bucketEndpointMiddleware } from "./bucketEndpointMiddleware"; describe("bucketEndpointMiddleware", () => { const input = { Bucket: "bucket" }; + const mockRegion = "us-foo-1"; const requestInput = { method: "GET", headers: {}, @@ -27,10 +28,10 @@ describe("bucketEndpointMiddleware", () => { }; const next = jest.fn(); const previouslyResolvedConfig = { - region: jest.fn().mockResolvedValue("us-foo-1"), + region: jest.fn().mockResolvedValue(mockRegion), regionInfoProvider: jest .fn() - .mockResolvedValue({ hostname: "foo.us-foo-2.amazonaws.com", partition: "aws-foo", signingRegion: "us-foo-1" }), + .mockResolvedValue({ hostname: "foo.us-foo-2.amazonaws.com", partition: "aws-foo", signingRegion: mockRegion }), useArnRegion: jest.fn().mockResolvedValue(false), }; @@ -61,6 +62,7 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: input.Bucket, baseHostname: requestInput.hostname, + region: mockRegion, accelerateEndpoint: false, dualstackEndpoint: false, pathStyleEndpoint: false, @@ -85,6 +87,7 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: input.Bucket, baseHostname: requestInput.hostname, + region: mockRegion, accelerateEndpoint: true, dualstackEndpoint: true, pathStyleEndpoint: true, @@ -118,12 +121,13 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: mockBucketArn, baseHostname: requestInput.hostname, + region: mockRegion, accelerateEndpoint: false, dualstackEndpoint: false, pathStyleEndpoint: false, tlsCompatible: true, clientPartition: "aws-foo", - clientSigningRegion: "us-foo-1", + clientSigningRegion: mockRegion, useArnRegion: false, }); expect(previouslyResolvedConfig.region).toBeCalled(); @@ -144,7 +148,7 @@ describe("bucketEndpointMiddleware", () => { request, }); expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled(); - expect(previouslyResolvedConfig.regionInfoProvider.mock.calls[0][0]).toBe("us-foo-1"); + expect(previouslyResolvedConfig.regionInfoProvider.mock.calls[0][0]).toBe(mockRegion); }); it("should supply bucketHostname in ARN object if bucket name string is a valid ARN", async () => { diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 3315da1d79fb..6d92051fecc2 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -3,64 +3,98 @@ import { parse as parseArn } from "@aws-sdk/util-arn-parser"; import { bucketHostname } from "./bucketHostname"; describe("bucketHostname", () => { + const region = "us-west-2"; describe("from bucket name", () => { - it("should use a virtual-hosted-style endpoint by default", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo", - baseHostname, - }); + ["s3.us-west-2.amazonaws.com"].forEach((baseHostname) => { + describe(`baseHostname: ${baseHostname}`, () => { + it("should use a virtual-hosted-style endpoint by default", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo", + baseHostname, + region, + }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe(`foo.${baseHostname}`); - }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(`foo.${baseHostname}`); + }); - it("should use a path-style endpoint when requested", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo", - baseHostname, - pathStyleEndpoint: true, - }); + it("should use a path-style endpoint when requested", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo", + baseHostname, + region, + pathStyleEndpoint: true, + }); - expect(bucketEndpoint).toBe(false); - expect(hostname).toBe(baseHostname); - }); + expect(bucketEndpoint).toBe(false); + expect(hostname).toBe(baseHostname); + }); - it("should ignore transfer acceleration when a path-style endpoint is requested", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo", - baseHostname, - pathStyleEndpoint: true, - accelerateEndpoint: true, - }); + it("should ignore transfer acceleration when a path-style endpoint is requested", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo", + baseHostname, + region, + pathStyleEndpoint: true, + accelerateEndpoint: true, + }); - expect(bucketEndpoint).toBe(false); - expect(hostname).toBe(baseHostname); - }); + expect(bucketEndpoint).toBe(false); + expect(hostname).toBe(baseHostname); + }); - it("should use a path-style endpoint when the bucket name contains a dot", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo.bar", - baseHostname, - }); + it("should use a path-style endpoint when the bucket name contains a dot", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo.bar", + baseHostname, + region, + }); - expect(bucketEndpoint).toBe(false); - expect(hostname).toBe(baseHostname); - }); + expect(bucketEndpoint).toBe(false); + expect(hostname).toBe(baseHostname); + }); - it("should use a virtual-hosted-style endpoint when SSL compatibility is not requested and the bucket name contains a dot", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo.bar", - baseHostname, - tlsCompatible: false, - }); + it("should use a virtual-hosted-style endpoint when SSL compatibility is not requested and the bucket name contains a dot", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo.bar", + baseHostname, + region, + tlsCompatible: false, + }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe(`foo.bar.${baseHostname}`); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(`foo.bar.${baseHostname}`); + }); + + for (const nonDnsCompliantBucketName of [ + // too short + "fo", + // too long + // eslint-disable-next-line @typescript-eslint/no-unused-vars + new Array(64).map((_) => "a").join(""), + // leading period + ".myawsbucket", + // trailing period + "myawsbucket.", + // sequential periods + "my..examplebucket", + // capital letters + "MyAWSBucket", + // IP address + "192.168.5.4", + ]) { + it(`should use a path-style endpoint for the non-DNS-compliant bucket name of ${nonDnsCompliantBucketName}`, () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: nonDnsCompliantBucketName, + baseHostname, + region, + }); + + expect(bucketEndpoint).toBe(false); + expect(hostname).toBe(baseHostname); + }); + } + }); }); for (const [baseHostname, dualstackHostname] of [ @@ -73,6 +107,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + region, accelerateEndpoint: true, }); @@ -84,6 +119,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + region, accelerateEndpoint: true, dualstackEndpoint: true, }); @@ -96,6 +132,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + region, dualstackEndpoint: true, }); @@ -107,6 +144,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + region, dualstackEndpoint: true, pathStyleEndpoint: true, }); @@ -116,46 +154,17 @@ describe("bucketHostname", () => { }); } - for (const nonDnsCompliantBucketName of [ - // too short - "fo", - // too long - // eslint-disable-next-line @typescript-eslint/no-unused-vars - new Array(64).map((_) => "a").join(""), - // leading period - ".myawsbucket", - // trailing period - "myawsbucket.", - // sequential periods - "my..examplebucket", - // capital letters - "MyAWSBucket", - // IP address - "192.168.5.4", - ]) { - it(`should use a path-style endpoint for the non-DNS-compliant bucket name of ${nonDnsCompliantBucketName}`, () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: nonDnsCompliantBucketName, - baseHostname, - }); - - expect(bucketEndpoint).toBe(false); - expect(hostname).toBe(baseHostname); - }); - } - - it("should perform no transformations when provided a non-S3 hostname", () => { - expect( - bucketHostname({ - bucketName: "foo", - baseHostname: "example.com", - }) - ).toEqual({ - bucketEndpoint: false, - hostname: "example.com", - }); - }); + // it("should perform no transformations when provided a non-S3 hostname", () => { + // expect( + // bucketHostname({ + // bucketName: "foo", + // baseHostname: "example.com", + // }) + // ).toEqual({ + // bucketEndpoint: false, + // hostname: "example.com", + // }); + // }); }); describe("from Access Point ARN", () => { @@ -165,6 +174,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname, + region, }); expect(bucketEndpoint).toBe(true); expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com"); @@ -175,6 +185,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + region, useArnRegion: true, }); expect(bucketEndpoint).toBe(true); @@ -189,6 +200,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + region, clientSigningRegion: "us-east-1", }); expect(bucketEndpoint).toBe(true); @@ -202,6 +214,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + region, clientSigningRegion: "us-east-1", useArnRegion: true, }); @@ -216,6 +229,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); }); @@ -224,6 +238,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + region, useArnRegion: true, dualstackEndpoint: true, }); @@ -238,6 +253,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-west-2.amazonaws.com", + region, useArnRegion: true, }); }).toThrow(`Partition in ARN is incompatible, got "aws-cn" but expected "aws"`); @@ -247,6 +263,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.cn-north-1.amazonaws.com.cn", + region: "cn-north-1", clientPartition: "aws-cn", useArnRegion: true, }); @@ -258,6 +275,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.cn-north-1.amazonaws.com.cn", + region: "cn-north-1", clientPartition: "aws-cn", }); expect(bucketEndpoint).toBe(true); @@ -271,6 +289,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", }); expect(bucketEndpoint).toBe(true); @@ -281,6 +300,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -292,6 +312,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, dualstackEndpoint: true, @@ -306,6 +327,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + region, accelerateEndpoint: true, }); }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); @@ -355,6 +377,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(bucketArn), baseHostname: "s3.us-west-2.amazonaws.com", + region, }); }).toThrow(message); }); @@ -367,6 +390,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", + region: "us-east-1", useArnRegion: true, }).signingRegion ).toBe("us-west-2"); @@ -376,6 +400,7 @@ describe("bucketHostname", () => { describe("from Outpost ARN", () => { describe("populates access point endpoint from ARN", () => { it("should use client region", () => { + const region = "us-west-2"; const baseHostname = "s3.us-west-2.amazonaws.com"; const expectedEndpoint = "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com"; [ @@ -385,6 +410,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + region, }); expect(bucketEndpoint).toBe(true); expect(hostname).toBe(expectedEndpoint); @@ -392,6 +418,7 @@ describe("bucketHostname", () => { }); it("should use ARN region", () => { + const region = "us-west-2"; const baseHostname = "s3.us-west-2.amazonaws.com"; const expectedEndpoint = "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-east-1.amazonaws.com"; [ @@ -401,6 +428,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + region, useArnRegion: true, }); expect(bucketEndpoint).toBe(true); @@ -416,6 +444,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); }); @@ -427,6 +456,7 @@ describe("bucketHostname", () => { "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + region, useArnRegion: true, }); }).toThrow(`Partition in ARN is incompatible, got "aws-cn" but expected "aws"`); @@ -441,6 +471,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", }); }).toThrow("FIPS region is not supported with Outpost, got fips-us-gov-east-1"); @@ -451,6 +482,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:fips-us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -463,6 +495,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + region: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -480,6 +513,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + region, dualstackEndpoint: true, }); }).toThrow("Dualstack endpoint is not supported with Outpost"); @@ -492,6 +526,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + region, accelerateEndpoint: true, }); }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); @@ -539,6 +574,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(outpostArn), baseHostname: "s3.us-west-2.amazonaws.com", + region, }); }).toThrow(message); }); @@ -552,6 +588,7 @@ describe("bucketHostname", () => { const { signingRegion, signingService } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", + region: "us-east-1", useArnRegion: true, }); expect(signingRegion).toBe("us-west-2"); From 6482584d8dc5c9c04c4883d3615d6ffaf3fadc8d Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:15:28 +0000 Subject: [PATCH 03/11] test: add tests for bucket name --- .../src/bucketHostname.spec.ts | 54 ++++++++++--------- .../src/bucketHostname.ts | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 6d92051fecc2..17f67d7f8c5e 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -5,7 +5,7 @@ import { bucketHostname } from "./bucketHostname"; describe("bucketHostname", () => { const region = "us-west-2"; describe("from bucket name", () => { - ["s3.us-west-2.amazonaws.com"].forEach((baseHostname) => { + ["s3.us-west-2.amazonaws.com", "beta.example.com"].forEach((baseHostname) => { describe(`baseHostname: ${baseHostname}`, () => { it("should use a virtual-hosted-style endpoint by default", () => { const { bucketEndpoint, hostname } = bucketHostname({ @@ -30,19 +30,6 @@ describe("bucketHostname", () => { expect(hostname).toBe(baseHostname); }); - it("should ignore transfer acceleration when a path-style endpoint is requested", () => { - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: "foo", - baseHostname, - region, - pathStyleEndpoint: true, - accelerateEndpoint: true, - }); - - expect(bucketEndpoint).toBe(false); - expect(hostname).toBe(baseHostname); - }); - it("should use a path-style endpoint when the bucket name contains a dot", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo.bar", @@ -97,6 +84,20 @@ describe("bucketHostname", () => { }); }); + it("should ignore transfer acceleration when a path-style endpoint is requested", () => { + const baseHostname = "s3.us-west-2.amazonaws.com"; + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: "foo", + baseHostname, + region, + pathStyleEndpoint: true, + accelerateEndpoint: true, + }); + + expect(bucketEndpoint).toBe(false); + expect(hostname).toBe(baseHostname); + }); + for (const [baseHostname, dualstackHostname] of [ ["s3.amazonaws.com", "s3.dualstack.us-east-1.amazonaws.com"], ["s3-external-1.amazonaws.com", "s3.dualstack.us-east-1.amazonaws.com"], @@ -154,17 +155,20 @@ describe("bucketHostname", () => { }); } - // it("should perform no transformations when provided a non-S3 hostname", () => { - // expect( - // bucketHostname({ - // bucketName: "foo", - // baseHostname: "example.com", - // }) - // ).toEqual({ - // bucketEndpoint: false, - // hostname: "example.com", - // }); - // }); + describe("should throw when provided a non-S3 hostname with", () => { + ["dualstackEndpoint", "accelerateEndpoint"].forEach((option) => { + it(`${option} enabled`, () => { + expect(() => { + bucketHostname({ + bucketName: "foo", + baseHostname: "example.com", + region, + [option]: true, + }); + }).toThrow("endpoint is not supported with custom endpoint"); + }); + }); + }); }); describe("from Access Point ARN", () => { diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index f2ed72103b61..988b47bd74dc 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -33,7 +33,7 @@ export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams if (isCustomEndpoint) { if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); - if (accelerateEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); + if (accelerateEndpoint) throw new Error("Accelarate endpoint is not supported with custom endpoint"); } return isBucketNameOptions(options) From f0811ac817bcfc0819005ec8fc888e39a234658c Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 25 Dec 2020 02:06:31 +0000 Subject: [PATCH 04/11] test: add Access Point ARN tests --- .../src/bucketHostname.spec.ts | 150 ++++++++++-------- 1 file changed, 86 insertions(+), 64 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 17f67d7f8c5e..ccbb0e846e81 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -173,27 +173,47 @@ describe("bucketHostname", () => { describe("from Access Point ARN", () => { describe("populates access point endpoint from ARN", () => { - it("should use client region", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), - baseHostname, - region, + const s3Hostname = "s3.us-west-2.amazonaws.com"; + const customHostname = "example.com"; + + describe(`baseHostname: ${s3Hostname}`, () => { + const baseHostname = s3Hostname; + it("should use client region", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), + baseHostname, + region, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com"); + }); + + it("should use ARN region", () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), + baseHostname, + region, + useArnRegion: true, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com"); }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com"); }); - it("should use ARN region", () => { - const baseHostname = "s3.us-west-2.amazonaws.com"; - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), - baseHostname, - region, - useArnRegion: true, + describe(`baseHostname: ${customHostname}`, () => { + const baseHostname = customHostname; + [true, false].forEach((useArnRegion) => { + it(`should ignore useArnRegion=${useArnRegion}`, () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), + baseHostname, + region: "us-east-1", + useArnRegion, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(`myendpoint-123456789012.${baseHostname}`); + }); }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com"); }); }); @@ -337,53 +357,55 @@ describe("bucketHostname", () => { }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); }); - describe("should validate Access Point ARN", () => { - [ - { - bucketArn: "arn:aws:sqs:us-west-2:123456789012:someresource", - message: "Expect 's3' or 's3-outposts' in ARN service component", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123456789012:bucket_name:mybucket", - message: "ARN resource should begin with 'accesspoint:' or 'outpost:'", - }, - { - bucketArn: "arn:aws:s3::123456789012:accesspoint:myendpoint", - message: "ARN region is empty", - }, - { - bucketArn: "arn:aws:s3:us-west-2::accesspoint:myendpoint", - message: "Access point ARN accountID does not match regex '[0-9]{12}'", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123.45678.9012:accesspoint:mybucket", - message: "Access point ARN accountID does not match regex '[0-9]{12}'", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:", - message: "Access Point ARN should have one resource accesspoint:{accesspointname}", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:*", - message: "Invalid DNS label *-123456789012", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:my.bucket", - message: "Invalid DNS label my.bucket-123456789012", - }, - { - bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:mybucket:object:foo ", - message: "Access Point ARN should have one resource accesspoint:{accesspointname}", - }, - ].forEach(({ bucketArn, message }) => { - it(`should throw "${message}"`, () => { - expect(() => { - bucketHostname({ - bucketName: parseArn(bucketArn), - baseHostname: "s3.us-west-2.amazonaws.com", - region, - }); - }).toThrow(message); + ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + describe(`should validate Access Point ARN with baseHostname: ${baseHostname}`, () => { + [ + { + bucketArn: "arn:aws:sqs:us-west-2:123456789012:someresource", + message: "Expect 's3' or 's3-outposts' in ARN service component", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123456789012:bucket_name:mybucket", + message: "ARN resource should begin with 'accesspoint:' or 'outpost:'", + }, + { + bucketArn: "arn:aws:s3::123456789012:accesspoint:myendpoint", + message: "ARN region is empty", + }, + { + bucketArn: "arn:aws:s3:us-west-2::accesspoint:myendpoint", + message: "Access point ARN accountID does not match regex '[0-9]{12}'", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123.45678.9012:accesspoint:mybucket", + message: "Access point ARN accountID does not match regex '[0-9]{12}'", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:", + message: "Access Point ARN should have one resource accesspoint:{accesspointname}", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:*", + message: "Invalid DNS label *-123456789012", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:my.bucket", + message: "Invalid DNS label my.bucket-123456789012", + }, + { + bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:mybucket:object:foo ", + message: "Access Point ARN should have one resource accesspoint:{accesspointname}", + }, + ].forEach(({ bucketArn, message }) => { + it(`should throw "${message}"`, () => { + expect(() => { + bucketHostname({ + bucketName: parseArn(bucketArn), + baseHostname, + region, + }); + }).toThrow(message); + }); }); }); }); From 59a175ddfce7ba4a9b9fe8fc7296082f0fea4c89 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 25 Dec 2020 02:25:52 +0000 Subject: [PATCH 05/11] test: update tests for Outposts endpoint --- .../src/bucketHostname.spec.ts | 224 ++++++++++-------- .../src/bucketHostname.ts | 2 +- 2 files changed, 132 insertions(+), 94 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index ccbb0e846e81..93bf543f33ac 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -425,40 +425,68 @@ describe("bucketHostname", () => { describe("from Outpost ARN", () => { describe("populates access point endpoint from ARN", () => { - it("should use client region", () => { - const region = "us-west-2"; - const baseHostname = "s3.us-west-2.amazonaws.com"; - const expectedEndpoint = "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com"; - [ - "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", - "arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint", - ].forEach((outpostArn) => { - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: parseArn(outpostArn), - baseHostname, - region, + const s3Hostname = "s3.us-west-2.amazonaws.com"; + const customHostname = "example.com"; + + describe(`baseHostname: ${s3Hostname}`, () => { + const baseHostname = s3Hostname; + it("should use client region", () => { + const region = "us-west-2"; + const expectedEndpoint = + "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com"; + [ + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", + "arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint", + ].forEach((outpostArn) => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn(outpostArn), + baseHostname, + region, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(expectedEndpoint); + }); + }); + + it("should use ARN region", () => { + const region = "us-west-2"; + const expectedEndpoint = + "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-east-1.amazonaws.com"; + [ + "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", + "arn:aws:s3-outposts:us-east-1:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint", + ].forEach((outpostArn) => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn(outpostArn), + baseHostname, + region, + useArnRegion: true, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(expectedEndpoint); }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe(expectedEndpoint); }); }); - it("should use ARN region", () => { - const region = "us-west-2"; - const baseHostname = "s3.us-west-2.amazonaws.com"; - const expectedEndpoint = "myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-east-1.amazonaws.com"; - [ - "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", - "arn:aws:s3-outposts:us-east-1:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint", - ].forEach((outpostArn) => { - const { bucketEndpoint, hostname } = bucketHostname({ - bucketName: parseArn(outpostArn), - baseHostname, - region, - useArnRegion: true, + describe(`baseHostname: ${customHostname}`, () => { + const baseHostname = customHostname; + [true, false].forEach((useArnRegion) => { + [ + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", + "arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint", + ].forEach((outpostArn) => { + it(`should ignore useArnRegion=${useArnRegion}`, () => { + const region = "us-west-2"; + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn(outpostArn), + baseHostname, + region, + useArnRegion, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe(`myaccesspoint-123456789012.op-01234567890123456.${baseHostname}`); + }); }); - expect(bucketEndpoint).toBe(true); - expect(hostname).toBe(expectedEndpoint); }); }); }); @@ -532,77 +560,87 @@ describe("bucketHostname", () => { }); }); - it("should throw if dualstack is set", () => { - expect(() => { - bucketHostname({ - bucketName: parseArn( - "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" - ), - baseHostname: "s3.us-west-2.amazonaws.com", - region, - dualstackEndpoint: true, - }); - }).toThrow("Dualstack endpoint is not supported with Outpost"); - }); - - it("should throw if accelerate endpoint is set", () => { - expect(() => { - bucketHostname({ - bucketName: parseArn( - "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" - ), - baseHostname: "s3.us-west-2.amazonaws.com", - region, - accelerateEndpoint: true, + describe("should throw if dualstack is set", () => { + ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + it(`with baseHostname: ${baseHostname}`, () => { + expect(() => { + bucketHostname({ + bucketName: parseArn( + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" + ), + baseHostname, + region, + dualstackEndpoint: true, + }); + }).toThrow("Dualstack endpoint is not supported"); }); - }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); + }); }); - describe("should validate Access Point ARN", () => { - [ - { - outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost", - message: "Outpost ARN should have resource outpost/{outpostId}/accesspoint/{accesspointName}", - }, - { - outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456", - message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", - }, - { - outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:myaccesspoint", - message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", - }, - { - outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost::accesspoint:myaccesspoint", - message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", - }, - { - outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint", - message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", - }, - { - outpostArn: - "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:mybucket:object:foo", - message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", - }, - { - outpostArn: - "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-0123456.890123456:accesspoint:myaccesspoint", - message: "Invalid DNS label op-0123456.890123456", - }, - { - outpostArn: "arn:aws:s3:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", - message: "Expect 's3-posts' in Outpost ARN service component", - }, - ].forEach(({ outpostArn, message }) => { - it(`should throw "${message}"`, () => { + describe("should throw if accelerate endpoint is set", () => { + ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + it(`with baseHostname: ${baseHostname}`, () => { expect(() => { bucketHostname({ - bucketName: parseArn(outpostArn), - baseHostname: "s3.us-west-2.amazonaws.com", + bucketName: parseArn( + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" + ), + baseHostname, region, + accelerateEndpoint: true, }); - }).toThrow(message); + }).toThrow("Accelerate endpoint is not supported"); + }); + }); + }); + + ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + describe(`should validate Outpost ARN with baseHostname: ${baseHostname}`, () => { + [ + { + outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost", + message: "Outpost ARN should have resource outpost/{outpostId}/accesspoint/{accesspointName}", + }, + { + outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456", + message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", + }, + { + outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:myaccesspoint", + message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", + }, + { + outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost::accesspoint:myaccesspoint", + message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", + }, + { + outpostArn: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint", + message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", + }, + { + outpostArn: + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:mybucket:object:foo", + message: "Outpost ARN should have resource outpost:{outpostId}:accesspoint:{accesspointName}", + }, + { + outpostArn: + "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-0123456.890123456:accesspoint:myaccesspoint", + message: "Invalid DNS label op-0123456.890123456", + }, + { + outpostArn: "arn:aws:s3:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", + message: "Expect 's3-posts' in Outpost ARN service component", + }, + ].forEach(({ outpostArn, message }) => { + it(`should throw "${message}"`, () => { + expect(() => { + bucketHostname({ + bucketName: parseArn(outpostArn), + baseHostname, + region, + }); + }).toThrow(message); + }); }); }); }); diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index 988b47bd74dc..b4efd2d267db 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -33,7 +33,7 @@ export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams if (isCustomEndpoint) { if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); - if (accelerateEndpoint) throw new Error("Accelarate endpoint is not supported with custom endpoint"); + if (accelerateEndpoint) throw new Error("Accelerate endpoint is not supported with custom endpoint"); } return isBucketNameOptions(options) From a84cc81bc12cd0929a35ec6d89a990e51aaaded8 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 8 Jan 2021 22:02:32 +0000 Subject: [PATCH 06/11] chore: use clientRegion in BucketHostnameParams --- .../src/bucketEndpointMiddleware.spec.ts | 6 +- .../src/bucketEndpointMiddleware.ts | 4 +- .../src/bucketHostname.spec.ts | 78 +++++++++---------- .../src/bucketHostname.ts | 4 +- .../src/bucketHostnameUtils.ts | 2 +- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts index 2a18c974b14a..6342bea68f7c 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts @@ -62,7 +62,7 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: input.Bucket, baseHostname: requestInput.hostname, - region: mockRegion, + clientRegion: mockRegion, accelerateEndpoint: false, dualstackEndpoint: false, pathStyleEndpoint: false, @@ -87,7 +87,7 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: input.Bucket, baseHostname: requestInput.hostname, - region: mockRegion, + clientRegion: mockRegion, accelerateEndpoint: true, dualstackEndpoint: true, pathStyleEndpoint: true, @@ -121,7 +121,7 @@ describe("bucketEndpointMiddleware", () => { expect(param).toEqual({ bucketName: mockBucketArn, baseHostname: requestInput.hostname, - region: mockRegion, + clientRegion: mockRegion, accelerateEndpoint: false, dualstackEndpoint: false, pathStyleEndpoint: false, diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts index 8184be0ff5ff..dc7a383aa808 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts @@ -42,7 +42,7 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): useArnRegion, clientPartition: partition, clientSigningRegion: signingRegion, - region: clientRegion, + clientRegion: clientRegion, }); // If the request needs to use a region or service name inferred from ARN that different from client region, we @@ -60,7 +60,7 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): const clientRegion = getPseudoRegion(await options.region()); const { hostname, bucketEndpoint } = bucketHostname({ bucketName, - region: clientRegion, + clientRegion, baseHostname: request.hostname, accelerateEndpoint: options.useAccelerateEndpoint, dualstackEndpoint: options.useDualstackEndpoint, diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 93bf543f33ac..05b77cc7c91f 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -11,7 +11,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, }); expect(bucketEndpoint).toBe(true); @@ -22,7 +22,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, pathStyleEndpoint: true, }); @@ -34,7 +34,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo.bar", baseHostname, - region, + clientRegion: region, }); expect(bucketEndpoint).toBe(false); @@ -45,7 +45,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo.bar", baseHostname, - region, + clientRegion: region, tlsCompatible: false, }); @@ -74,7 +74,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: nonDnsCompliantBucketName, baseHostname, - region, + clientRegion: region, }); expect(bucketEndpoint).toBe(false); @@ -89,7 +89,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, pathStyleEndpoint: true, accelerateEndpoint: true, }); @@ -108,7 +108,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, accelerateEndpoint: true, }); @@ -120,7 +120,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, accelerateEndpoint: true, dualstackEndpoint: true, }); @@ -133,7 +133,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, dualstackEndpoint: true, }); @@ -145,7 +145,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, - region, + clientRegion: region, dualstackEndpoint: true, pathStyleEndpoint: true, }); @@ -162,7 +162,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: "foo", baseHostname: "example.com", - region, + clientRegion: region, [option]: true, }); }).toThrow("endpoint is not supported with custom endpoint"); @@ -182,7 +182,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname, - region, + clientRegion: region, }); expect(bucketEndpoint).toBe(true); expect(hostname).toBe("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com"); @@ -192,7 +192,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, - region, + clientRegion: region, useArnRegion: true, }); expect(bucketEndpoint).toBe(true); @@ -207,7 +207,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, - region: "us-east-1", + clientRegion: "us-east-1", useArnRegion, }); expect(bucketEndpoint).toBe(true); @@ -224,7 +224,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, - region, + clientRegion: region, clientSigningRegion: "us-east-1", }); expect(bucketEndpoint).toBe(true); @@ -238,7 +238,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, - region, + clientRegion: region, clientSigningRegion: "us-east-1", useArnRegion: true, }); @@ -253,7 +253,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); }); @@ -262,7 +262,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, useArnRegion: true, dualstackEndpoint: true, }); @@ -277,7 +277,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, useArnRegion: true, }); }).toThrow(`Partition in ARN is incompatible, got "aws-cn" but expected "aws"`); @@ -287,7 +287,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.cn-north-1.amazonaws.com.cn", - region: "cn-north-1", + clientRegion: "cn-north-1", clientPartition: "aws-cn", useArnRegion: true, }); @@ -299,7 +299,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.cn-north-1.amazonaws.com.cn", - region: "cn-north-1", + clientRegion: "cn-north-1", clientPartition: "aws-cn", }); expect(bucketEndpoint).toBe(true); @@ -313,7 +313,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", }); expect(bucketEndpoint).toBe(true); @@ -324,7 +324,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -336,7 +336,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, dualstackEndpoint: true, @@ -351,7 +351,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, accelerateEndpoint: true, }); }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); @@ -402,7 +402,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(bucketArn), baseHostname, - region, + clientRegion: region, }); }).toThrow(message); }); @@ -416,7 +416,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", - region: "us-east-1", + clientRegion: "us-east-1", useArnRegion: true, }).signingRegion ).toBe("us-west-2"); @@ -441,7 +441,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, - region, + clientRegion: region, }); expect(bucketEndpoint).toBe(true); expect(hostname).toBe(expectedEndpoint); @@ -459,7 +459,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, - region, + clientRegion: region, useArnRegion: true, }); expect(bucketEndpoint).toBe(true); @@ -480,7 +480,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, - region, + clientRegion: region, useArnRegion, }); expect(bucketEndpoint).toBe(true); @@ -498,7 +498,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); }); @@ -510,7 +510,7 @@ describe("bucketHostname", () => { "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", - region, + clientRegion: region, useArnRegion: true, }); }).toThrow(`Partition in ARN is incompatible, got "aws-cn" but expected "aws"`); @@ -525,7 +525,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", }); }).toThrow("FIPS region is not supported with Outpost, got fips-us-gov-east-1"); @@ -536,7 +536,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:fips-us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -549,7 +549,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", - region: "us-gov-east-1", + clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, }); @@ -569,7 +569,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname, - region, + clientRegion: region, dualstackEndpoint: true, }); }).toThrow("Dualstack endpoint is not supported"); @@ -586,7 +586,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname, - region, + clientRegion: region, accelerateEndpoint: true, }); }).toThrow("Accelerate endpoint is not supported"); @@ -637,7 +637,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, - region, + clientRegion: region, }); }).toThrow(message); }); @@ -652,7 +652,7 @@ describe("bucketHostname", () => { const { signingRegion, signingService } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", - region: "us-east-1", + clientRegion: "us-east-1", useArnRegion: true, }); expect(signingRegion).toBe("us-west-2"); diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index b4efd2d267db..b46e5b299008 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -46,7 +46,7 @@ export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams const getEndpointFromArn = (isCustomEndpoint: boolean, options: ArnHostnameParams): BucketHostname => { const { baseHostname } = options; const [clientRegion, hostnameSuffix] = isCustomEndpoint - ? [options.region, baseHostname] + ? [options.clientRegion, baseHostname] : // Infer client region and hostname suffix from hostname from endpoints.json, like `s3.us-west-2.amazonaws.com` getSuffixForArnEndpoint(baseHostname); @@ -104,7 +104,7 @@ const getEndpointFromBucketName = ( isCustomEndpoint: boolean, { accelerateEndpoint = false, - region, + clientRegion: region, baseHostname, bucketName, dualstackEndpoint = false, diff --git a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts index 2b42b849b766..8826b191325e 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts @@ -15,7 +15,7 @@ export interface AccessPointArn extends ARN { export interface BucketHostnameParams { baseHostname: string; bucketName: string; - region: string; + clientRegion: string; accelerateEndpoint?: boolean; dualstackEndpoint?: boolean; pathStyleEndpoint?: boolean; From efe91bbf3888589497a24454410c6d4e510418a5 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 9 Jan 2021 00:29:06 +0000 Subject: [PATCH 07/11] chore: move isBucketEndpoint inside options --- .../src/bucketHostname.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index b46e5b299008..6dafddd29d4e 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -38,13 +38,13 @@ export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams return isBucketNameOptions(options) ? // Construct endpoint when bucketName is a string referring to a bucket name - getEndpointFromBucketName(isCustomEndpoint, options) + getEndpointFromBucketName({ ...options, isCustomEndpoint }) : // Construct endpoint when bucketName is an ARN referring to an S3 resource like Access Point - getEndpointFromArn(isCustomEndpoint, options); + getEndpointFromArn({ ...options, isCustomEndpoint }); }; -const getEndpointFromArn = (isCustomEndpoint: boolean, options: ArnHostnameParams): BucketHostname => { - const { baseHostname } = options; +const getEndpointFromArn = (options: ArnHostnameParams & { isCustomEndpoint: boolean }): BucketHostname => { + const { isCustomEndpoint, baseHostname } = options; const [clientRegion, hostnameSuffix] = isCustomEndpoint ? [options.clientRegion, baseHostname] : // Infer client region and hostname suffix from hostname from endpoints.json, like `s3.us-west-2.amazonaws.com` @@ -100,18 +100,16 @@ const getEndpointFromArn = (isCustomEndpoint: boolean, options: ArnHostnameParam }; }; -const getEndpointFromBucketName = ( - isCustomEndpoint: boolean, - { - accelerateEndpoint = false, - clientRegion: region, - baseHostname, - bucketName, - dualstackEndpoint = false, - pathStyleEndpoint = false, - tlsCompatible = true, - }: BucketHostnameParams -): BucketHostname => { +const getEndpointFromBucketName = ({ + accelerateEndpoint = false, + clientRegion: region, + baseHostname, + bucketName, + dualstackEndpoint = false, + pathStyleEndpoint = false, + tlsCompatible = true, + isCustomEndpoint = false, +}: BucketHostnameParams & { isCustomEndpoint: boolean }): BucketHostname => { const [clientRegion, hostnameSuffix] = isCustomEndpoint ? [region, baseHostname] : getSuffix(baseHostname); if (pathStyleEndpoint || !isDnsCompatibleBucketName(bucketName) || (tlsCompatible && DOT_PATTERN.test(bucketName))) { return { From e446b85ae7e39eddf2cdcb91e61a924195fe33d5 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 11 Jan 2021 22:15:10 +0000 Subject: [PATCH 08/11] fix: pass isCustomEndpoint from config --- .../src/bucketEndpointMiddleware.spec.ts | 5 ++ .../src/bucketEndpointMiddleware.ts | 2 + .../src/bucketHostname.spec.ts | 64 +++++++++++++++++-- .../src/bucketHostname.ts | 4 +- .../src/bucketHostnameUtils.ts | 1 + .../src/configurations.ts | 2 + 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts index 6342bea68f7c..53bf929a5ce7 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts @@ -28,6 +28,7 @@ describe("bucketEndpointMiddleware", () => { }; const next = jest.fn(); const previouslyResolvedConfig = { + isCustomEndpoint: false, region: jest.fn().mockResolvedValue(mockRegion), regionInfoProvider: jest .fn() @@ -67,6 +68,7 @@ describe("bucketEndpointMiddleware", () => { dualstackEndpoint: false, pathStyleEndpoint: false, tlsCompatible: true, + isCustomEndpoint: false, }); }); @@ -79,6 +81,7 @@ describe("bucketEndpointMiddleware", () => { useAccelerateEndpoint: true, useDualstackEndpoint: true, forcePathStyle: true, + isCustomEndpoint: true, }) )(next, {} as any); await handler({ input, request }); @@ -92,6 +95,7 @@ describe("bucketEndpointMiddleware", () => { dualstackEndpoint: true, pathStyleEndpoint: true, tlsCompatible: false, + isCustomEndpoint: true, }); }); }); @@ -129,6 +133,7 @@ describe("bucketEndpointMiddleware", () => { clientPartition: "aws-foo", clientSigningRegion: mockRegion, useArnRegion: false, + isCustomEndpoint: false, }); expect(previouslyResolvedConfig.region).toBeCalled(); expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled(); diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts index dc7a383aa808..01323c2467f7 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts @@ -43,6 +43,7 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): clientPartition: partition, clientSigningRegion: signingRegion, clientRegion: clientRegion, + isCustomEndpoint: options.isCustomEndpoint, }); // If the request needs to use a region or service name inferred from ARN that different from client region, we @@ -66,6 +67,7 @@ export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): dualstackEndpoint: options.useDualstackEndpoint, pathStyleEndpoint: options.forcePathStyle, tlsCompatible: request.protocol === "https:", + isCustomEndpoint: options.isCustomEndpoint, }); request.hostname = hostname; diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 05b77cc7c91f..673555c517c8 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -5,12 +5,16 @@ import { bucketHostname } from "./bucketHostname"; describe("bucketHostname", () => { const region = "us-west-2"; describe("from bucket name", () => { - ["s3.us-west-2.amazonaws.com", "beta.example.com"].forEach((baseHostname) => { + [ + { baseHostname: "s3.us-west-2.amazonaws.com", isCustomEndpoint: false }, + { baseHostname: "beta.example.com", isCustomEndpoint: true }, + ].forEach(({ baseHostname, isCustomEndpoint }) => { describe(`baseHostname: ${baseHostname}`, () => { it("should use a virtual-hosted-style endpoint by default", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint, clientRegion: region, }); @@ -22,6 +26,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint, clientRegion: region, pathStyleEndpoint: true, }); @@ -34,6 +39,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo.bar", baseHostname, + isCustomEndpoint, clientRegion: region, }); @@ -45,6 +51,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo.bar", baseHostname, + isCustomEndpoint, clientRegion: region, tlsCompatible: false, }); @@ -74,6 +81,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: nonDnsCompliantBucketName, baseHostname, + isCustomEndpoint, clientRegion: region, }); @@ -89,6 +97,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint: false, clientRegion: region, pathStyleEndpoint: true, accelerateEndpoint: true, @@ -108,6 +117,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint: false, clientRegion: region, accelerateEndpoint: true, }); @@ -120,6 +130,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint: false, clientRegion: region, accelerateEndpoint: true, dualstackEndpoint: true, @@ -133,6 +144,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint: false, clientRegion: region, dualstackEndpoint: true, }); @@ -145,6 +157,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: "foo", baseHostname, + isCustomEndpoint: false, clientRegion: region, dualstackEndpoint: true, pathStyleEndpoint: true, @@ -162,6 +175,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: "foo", baseHostname: "example.com", + isCustomEndpoint: true, clientRegion: region, [option]: true, }); @@ -182,6 +196,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname, + isCustomEndpoint: false, clientRegion: region, }); expect(bucketEndpoint).toBe(true); @@ -192,6 +207,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + isCustomEndpoint: false, clientRegion: region, useArnRegion: true, }); @@ -207,6 +223,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + isCustomEndpoint: true, clientRegion: "us-east-1", useArnRegion, }); @@ -224,6 +241,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + isCustomEndpoint: false, clientRegion: region, clientSigningRegion: "us-east-1", }); @@ -238,6 +256,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname, + isCustomEndpoint: false, clientRegion: region, clientSigningRegion: "us-east-1", useArnRegion: true, @@ -253,6 +272,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); @@ -262,6 +282,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, useArnRegion: true, dualstackEndpoint: true, @@ -277,6 +298,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, useArnRegion: true, }); @@ -287,6 +309,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn("arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint:myendpoint"), baseHostname: "s3.cn-north-1.amazonaws.com.cn", + isCustomEndpoint: false, clientRegion: "cn-north-1", clientPartition: "aws-cn", useArnRegion: true, @@ -299,6 +322,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.cn-north-1.amazonaws.com.cn", + isCustomEndpoint: false, clientRegion: "cn-north-1", clientPartition: "aws-cn", }); @@ -313,6 +337,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", }); @@ -324,6 +349,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, @@ -336,6 +362,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, @@ -351,13 +378,17 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn("arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint"), baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, accelerateEndpoint: true, }); }).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); }); - ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + [ + { baseHostname: "s3.us-west-2.amazonaws.com", isCustomEndpoint: false }, + { baseHostname: "beta.example.com", isCustomEndpoint: true }, + ].forEach(({ baseHostname, isCustomEndpoint }) => { describe(`should validate Access Point ARN with baseHostname: ${baseHostname}`, () => { [ { @@ -402,6 +433,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(bucketArn), baseHostname, + isCustomEndpoint, clientRegion: region, }); }).toThrow(message); @@ -416,6 +448,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-east-1", useArnRegion: true, }).signingRegion @@ -441,6 +474,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + isCustomEndpoint: false, clientRegion: region, }); expect(bucketEndpoint).toBe(true); @@ -459,6 +493,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + isCustomEndpoint: false, clientRegion: region, useArnRegion: true, }); @@ -480,6 +515,7 @@ describe("bucketHostname", () => { const { bucketEndpoint, hostname } = bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + isCustomEndpoint: true, clientRegion: region, useArnRegion, }); @@ -498,6 +534,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, }); }).toThrow("Region in ARN is incompatible, got us-east-1 but expected us-west-2"); @@ -510,6 +547,7 @@ describe("bucketHostname", () => { "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.us-west-2.amazonaws.com", + isCustomEndpoint: false, clientRegion: region, useArnRegion: true, }); @@ -525,6 +563,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", }); @@ -536,6 +575,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:fips-us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, @@ -549,6 +589,7 @@ describe("bucketHostname", () => { "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname: "s3.fips-us-gov-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-gov-east-1", clientPartition: "aws-us-gov", useArnRegion: true, @@ -561,7 +602,10 @@ describe("bucketHostname", () => { }); describe("should throw if dualstack is set", () => { - ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + [ + { baseHostname: "s3.us-west-2.amazonaws.com", isCustomEndpoint: false }, + { baseHostname: "beta.example.com", isCustomEndpoint: true }, + ].forEach(({ baseHostname, isCustomEndpoint }) => { it(`with baseHostname: ${baseHostname}`, () => { expect(() => { bucketHostname({ @@ -569,6 +613,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname, + isCustomEndpoint, clientRegion: region, dualstackEndpoint: true, }); @@ -578,7 +623,10 @@ describe("bucketHostname", () => { }); describe("should throw if accelerate endpoint is set", () => { - ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + [ + { baseHostname: "s3.us-west-2.amazonaws.com", isCustomEndpoint: false }, + { baseHostname: "beta.example.com", isCustomEndpoint: true }, + ].forEach(({ baseHostname, isCustomEndpoint }) => { it(`with baseHostname: ${baseHostname}`, () => { expect(() => { bucketHostname({ @@ -586,6 +634,7 @@ describe("bucketHostname", () => { "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint" ), baseHostname, + isCustomEndpoint, clientRegion: region, accelerateEndpoint: true, }); @@ -594,7 +643,10 @@ describe("bucketHostname", () => { }); }); - ["s3.us-west-2.amazonaws.com", "example.com"].forEach((baseHostname) => { + [ + { baseHostname: "s3.us-west-2.amazonaws.com", isCustomEndpoint: false }, + { baseHostname: "beta.example.com", isCustomEndpoint: true }, + ].forEach(({ baseHostname, isCustomEndpoint }) => { describe(`should validate Outpost ARN with baseHostname: ${baseHostname}`, () => { [ { @@ -637,6 +689,7 @@ describe("bucketHostname", () => { bucketHostname({ bucketName: parseArn(outpostArn), baseHostname, + isCustomEndpoint, clientRegion: region, }); }).toThrow(message); @@ -652,6 +705,7 @@ describe("bucketHostname", () => { const { signingRegion, signingService } = bucketHostname({ bucketName: bucketArn, baseHostname: "s3.us-east-1.amazonaws.com", + isCustomEndpoint: false, clientRegion: "us-east-1", useArnRegion: true, }); diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index 6dafddd29d4e..23d8cfbfcf75 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -7,7 +7,6 @@ import { getSuffixForArnEndpoint, isBucketNameOptions, isDnsCompatibleBucketName, - S3_HOSTNAME_PATTERN, validateAccountId, validateArnEndpointOptions, validateDNSHostLabel, @@ -28,8 +27,7 @@ export interface BucketHostname { } export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams): BucketHostname => { - const { baseHostname, dualstackEndpoint, accelerateEndpoint } = options; - const isCustomEndpoint = !S3_HOSTNAME_PATTERN.test(baseHostname); + const { isCustomEndpoint, baseHostname, dualstackEndpoint, accelerateEndpoint } = options; if (isCustomEndpoint) { if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); diff --git a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts index 8826b191325e..4a78e0b28eff 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts @@ -13,6 +13,7 @@ export interface AccessPointArn extends ARN { } export interface BucketHostnameParams { + isCustomEndpoint: boolean; baseHostname: string; bucketName: string; clientRegion: string; diff --git a/packages/middleware-bucket-endpoint/src/configurations.ts b/packages/middleware-bucket-endpoint/src/configurations.ts index a067ac17f04e..29b95c861c71 100644 --- a/packages/middleware-bucket-endpoint/src/configurations.ts +++ b/packages/middleware-bucket-endpoint/src/configurations.ts @@ -25,11 +25,13 @@ export interface BucketEndpointInputConfig { } interface PreviouslyResolved { + isCustomEndpoint: boolean; region: Provider; regionInfoProvider: RegionInfoProvider; } export interface BucketEndpointResolvedConfig { + isCustomEndpoint: boolean; bucketEndpoint: boolean; forcePathStyle: boolean; useAccelerateEndpoint: boolean; From 6d8385a5cdd050133fd1b543293769e21d10520b Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 12 Jan 2021 02:19:57 +0000 Subject: [PATCH 09/11] fix: remove limit of 64 characters on valid hostname --- packages/protocol-http/src/isValidHostname.spec.ts | 2 +- packages/protocol-http/src/isValidHostname.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/protocol-http/src/isValidHostname.spec.ts b/packages/protocol-http/src/isValidHostname.spec.ts index ee073934c214..aa280d55cf28 100644 --- a/packages/protocol-http/src/isValidHostname.spec.ts +++ b/packages/protocol-http/src/isValidHostname.spec.ts @@ -9,7 +9,7 @@ describe("implementation selection", () => { }); it("should return false for invalid hostnames", () => { - const invalidHostnames = ["foo.com/?bar", ".foo", `${new Array(64).fill("a").join("")}`]; + const invalidHostnames = ["foo.com/?bar", ".foo"]; for (const hostname of invalidHostnames) { expect(isValidHostname(hostname)).toBe(false); } diff --git a/packages/protocol-http/src/isValidHostname.ts b/packages/protocol-http/src/isValidHostname.ts index e4c673942d38..2f9fdfb1f591 100644 --- a/packages/protocol-http/src/isValidHostname.ts +++ b/packages/protocol-http/src/isValidHostname.ts @@ -1,4 +1,4 @@ export function isValidHostname(hostname: string): boolean { - const hostPattern = /^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/; + const hostPattern = /^[a-z0-9][a-z0-9\.\-]*[a-z0-9]$/; return hostPattern.test(hostname); } From 65944f1c4982935900ca9cc0df590bc741aa9451 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 12 Jan 2021 04:50:28 +0000 Subject: [PATCH 10/11] feat: support S3Control customizations with custom endpoints --- .../src/configurations.ts | 2 ++ .../src/process-arnables-plugin/plugin.ts | 2 +- .../update-arnables-request.ts | 24 ++++++++++++++----- .../src/redirect-from-postid.ts | 7 ++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/middleware-sdk-s3-control/src/configurations.ts b/packages/middleware-sdk-s3-control/src/configurations.ts index 774326040f4e..14f1405483e2 100644 --- a/packages/middleware-sdk-s3-control/src/configurations.ts +++ b/packages/middleware-sdk-s3-control/src/configurations.ts @@ -13,11 +13,13 @@ export interface S3ControlInputConfig { } interface PreviouslyResolved { + isCustomEndpoint: boolean; region: Provider; regionInfoProvider: RegionInfoProvider; } export interface S3ControlResolvedConfig { + isCustomEndpoint: boolean; useDualstackEndpoint: boolean; useArnRegion: Provider; region: Provider; diff --git a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.ts b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.ts index e6602872fa91..ea2e8f700224 100644 --- a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.ts +++ b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.ts @@ -7,6 +7,6 @@ import { updateArnablesRequestMiddleware, updateArnablesRequestMiddlewareOptions export const getProcessArnablesPlugin = (options: S3ControlResolvedConfig): Pluggable => ({ applyToStack: (clientStack) => { clientStack.add(parseOutpostArnablesMiddleaware(options), parseOutpostArnablesMiddleawareOptions); - clientStack.add(updateArnablesRequestMiddleware, updateArnablesRequestMiddlewareOptions); + clientStack.add(updateArnablesRequestMiddleware(options), updateArnablesRequestMiddlewareOptions); }, }); diff --git a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts index da2f415e34de..8a1bea2ef67e 100644 --- a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts +++ b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts @@ -1,6 +1,7 @@ import { HttpRequest } from "@aws-sdk/protocol-http"; import { BuildHandlerOptions, BuildMiddleware } from "@aws-sdk/types"; +import { S3ControlResolvedConfig } from "../configurations"; import { CONTEXT_ACCOUNT_ID, CONTEXT_ARN_REGION, CONTEXT_OUTPOST_ID } from "../constants"; const ACCOUNT_ID_HEADER = "x-amz-account-id"; @@ -11,23 +12,34 @@ const REGEX_S3CONTROL_HOSTNAME = /^(.+\.)?s3-control[.-]([a-z0-9-]+)\./; * After outpost request is constructed, redirect request to outpost endpoint and set `x-amz-account-id` and * `x-amz-outpost-id` headers. */ -export const updateArnablesRequestMiddleware: BuildMiddleware = (next, context) => (args) => { +export const updateArnablesRequestMiddleware = (options: S3ControlResolvedConfig): BuildMiddleware => ( + next, + context +) => (args) => { const { request } = args; if (!HttpRequest.isInstance(request)) return next(args); if (context[CONTEXT_ACCOUNT_ID]) request.headers[ACCOUNT_ID_HEADER] = context[CONTEXT_ACCOUNT_ID]; if (context[CONTEXT_OUTPOST_ID]) { request.headers[OUTPOST_ID_HEADER] = context[CONTEXT_OUTPOST_ID]; - request.hostname = getOutpostEndpoint(request.hostname, context[CONTEXT_ARN_REGION]); + request.hostname = getOutpostEndpoint(request.hostname, { + isCustomEndpoint: options.isCustomEndpoint, + regionOverride: context[CONTEXT_ARN_REGION], + }); } return next(args); }; -export const getOutpostEndpoint = (hostname: string, regionOverride?: string): string => { +export const getOutpostEndpoint = ( + hostname: string, + { isCustomEndpoint, regionOverride }: { isCustomEndpoint?: boolean; regionOverride?: string } = {} +): string => { const [matched, prefix, region] = hostname.match(REGEX_S3CONTROL_HOSTNAME)!; // hostname prefix will be ignored even if presents - return ["s3-outposts", regionOverride || region, hostname.replace(new RegExp(`^${matched}`), "")] - .filter((part) => part !== undefined) - .join("."); + return isCustomEndpoint + ? hostname + : ["s3-outposts", regionOverride || region, hostname.replace(new RegExp(`^${matched}`), "")] + .filter((part) => part !== undefined) + .join("."); }; export const updateArnablesRequestMiddlewareOptions: BuildHandlerOptions = { diff --git a/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts b/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts index e7fd16916aff..34bfac3d5e64 100644 --- a/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts +++ b/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts @@ -13,7 +13,10 @@ type InputType = { * If OutpostId is set, redirect hostname to Outpost one, and change signing service to `s3-outposts`. * Applied to S3Control.CreateBucket and S3Control.ListRegionalBuckets */ -export const redirectFromPostIdMiddleware: BuildMiddleware = (next, context) => (args) => { +export const redirectFromPostIdMiddleware = (options: S3ControlResolvedConfig): BuildMiddleware => ( + next, + context +) => (args) => { const { input, request } = args; if (!HttpRequest.isInstance(request)) return next(args); if (input.OutpostId) { @@ -32,6 +35,6 @@ export const redirectFromPostIdMiddlewareOptions: BuildHandlerOptions = { export const getRedirectFromPostIdPlugin = (options: S3ControlResolvedConfig): Pluggable => ({ applyToStack: (clientStack) => { - clientStack.add(redirectFromPostIdMiddleware, redirectFromPostIdMiddlewareOptions); + clientStack.add(redirectFromPostIdMiddleware(options), redirectFromPostIdMiddlewareOptions); }, }); From 90e8b5b2ec3dfc32d63720d4993d5c08dfa6e1ea Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 12 Jan 2021 05:06:57 +0000 Subject: [PATCH 11/11] test: update tests --- .../src/process-arnables-plugin/plugin.spec.ts | 1 + .../update-arnables-request.ts | 11 ++++++----- .../src/redirect-from-postid.spec.ts | 5 +---- .../src/redirect-from-postid.ts | 11 ++++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.spec.ts b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.spec.ts index 668fe60e1285..8e14d905f73a 100644 --- a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.spec.ts +++ b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.spec.ts @@ -20,6 +20,7 @@ describe("getProcessArnablesMiddleware", () => { regionInfoProvider: options.regionInfoProvider ?? jest.fn().mockResolvedValue({ partition: "aws" }), region: jest.fn().mockResolvedValue(options.region), useArnRegion: jest.fn().mockResolvedValue(options.useArnRegion ?? false), + isCustomEndpoint: false, }; }; diff --git a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts index 8a1bea2ef67e..3db0d00d00a3 100644 --- a/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts +++ b/packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts @@ -12,17 +12,18 @@ const REGEX_S3CONTROL_HOSTNAME = /^(.+\.)?s3-control[.-]([a-z0-9-]+)\./; * After outpost request is constructed, redirect request to outpost endpoint and set `x-amz-account-id` and * `x-amz-outpost-id` headers. */ -export const updateArnablesRequestMiddleware = (options: S3ControlResolvedConfig): BuildMiddleware => ( - next, - context -) => (args) => { +export const updateArnablesRequestMiddleware = ({ + isCustomEndpoint, +}: { + isCustomEndpoint: boolean; +}): BuildMiddleware => (next, context) => (args) => { const { request } = args; if (!HttpRequest.isInstance(request)) return next(args); if (context[CONTEXT_ACCOUNT_ID]) request.headers[ACCOUNT_ID_HEADER] = context[CONTEXT_ACCOUNT_ID]; if (context[CONTEXT_OUTPOST_ID]) { request.headers[OUTPOST_ID_HEADER] = context[CONTEXT_OUTPOST_ID]; request.hostname = getOutpostEndpoint(request.hostname, { - isCustomEndpoint: options.isCustomEndpoint, + isCustomEndpoint, regionOverride: context[CONTEXT_ARN_REGION], }); } diff --git a/packages/middleware-sdk-s3-control/src/redirect-from-postid.spec.ts b/packages/middleware-sdk-s3-control/src/redirect-from-postid.spec.ts index 20165770bdd2..590e5795cb3f 100644 --- a/packages/middleware-sdk-s3-control/src/redirect-from-postid.spec.ts +++ b/packages/middleware-sdk-s3-control/src/redirect-from-postid.spec.ts @@ -6,10 +6,7 @@ describe("redirectFromPostIdMiddleware", () => { it("should redirect request if Bucket is a valid ARN", async () => { const next: any = (args: any) => ({ output: args.request }); const context: any = {}; - const { output } = await redirectFromPostIdMiddleware( - next, - context - )({ + const { output } = await redirectFromPostIdMiddleware({ isCustomEndpoint: false })(next, context)({ input: { OutpostId: "op-123" }, request: new HttpRequest({ hostname: "123456789012.s3-control.us-west-2.amazonaws.com" }), }); diff --git a/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts b/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts index 34bfac3d5e64..0cbd38711756 100644 --- a/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts +++ b/packages/middleware-sdk-s3-control/src/redirect-from-postid.ts @@ -13,14 +13,15 @@ type InputType = { * If OutpostId is set, redirect hostname to Outpost one, and change signing service to `s3-outposts`. * Applied to S3Control.CreateBucket and S3Control.ListRegionalBuckets */ -export const redirectFromPostIdMiddleware = (options: S3ControlResolvedConfig): BuildMiddleware => ( - next, - context -) => (args) => { +export const redirectFromPostIdMiddleware = ({ + isCustomEndpoint, +}: { + isCustomEndpoint: boolean; +}): BuildMiddleware => (next, context) => (args) => { const { input, request } = args; if (!HttpRequest.isInstance(request)) return next(args); if (input.OutpostId) { - request.hostname = getOutpostEndpoint(request.hostname); + request.hostname = getOutpostEndpoint(request.hostname, { isCustomEndpoint }); context[CONTEXT_SIGNING_SERVICE] = "s3-outposts"; } return next(args);