Skip to content

Commit 41d3762

Browse files
authored
feat(client-s3-control): support FIPS in S3 Outposts Control Plane (#2985)
1 parent 45365ac commit 41d3762

File tree

8 files changed

+87
-73
lines changed

8 files changed

+87
-73
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { getOutpostEndpoint } from "./getOutpostEndpoint";
2+
3+
describe(getOutpostEndpoint.name, () => {
4+
const mockRegion = "region";
5+
const mockDnsSuffix = "mockDnsSuffix";
6+
const mockHostname = `s3-control.${mockRegion}.${mockDnsSuffix}`;
7+
const mockInput = { isCustomEndpoint: false, useFipsEndpoint: false };
8+
9+
it("returns hostname if custom endpoint is set", () => {
10+
expect(getOutpostEndpoint(mockHostname, { ...mockInput, isCustomEndpoint: true })).toStrictEqual(mockHostname);
11+
});
12+
13+
describe("returns outpost endpoint", () => {
14+
it("uses region from hostname if regionOverride if provided", () => {
15+
expect(getOutpostEndpoint(mockHostname, mockInput)).toStrictEqual(`s3-outposts.${mockRegion}.${mockDnsSuffix}`);
16+
});
17+
18+
it("uses region from regionOverride if provided", () => {
19+
const mockRegionOverride = "mockRegionOverride";
20+
expect(getOutpostEndpoint(mockHostname, { ...mockInput, regionOverride: mockRegionOverride })).toStrictEqual(
21+
`s3-outposts.${mockRegionOverride}.${mockDnsSuffix}`
22+
);
23+
});
24+
25+
it(`adds suffix "-fips" if useFipsEndpoint is set`, () => {
26+
expect(getOutpostEndpoint(mockHostname, { ...mockInput, useFipsEndpoint: true })).toStrictEqual(
27+
`s3-outposts-fips.${mockRegion}.${mockDnsSuffix}`
28+
);
29+
});
30+
});
31+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const REGEX_S3CONTROL_HOSTNAME = /^(.+\.)?s3-control[.-]([a-z0-9-]+)\./;
2+
3+
export interface GetOutpostEndpointOptions {
4+
isCustomEndpoint?: boolean;
5+
regionOverride?: string;
6+
useFipsEndpoint: boolean;
7+
}
8+
9+
export const getOutpostEndpoint = (
10+
hostname: string,
11+
{ isCustomEndpoint, regionOverride, useFipsEndpoint }: GetOutpostEndpointOptions
12+
): string => {
13+
const [matched, prefix, region] = hostname.match(REGEX_S3CONTROL_HOSTNAME)!;
14+
// hostname prefix will be ignored even if presents
15+
return isCustomEndpoint
16+
? hostname
17+
: [
18+
`s3-outposts${useFipsEndpoint ? "-fips" : ""}`,
19+
regionOverride || region,
20+
hostname.replace(new RegExp(`^${matched}`), ""),
21+
]
22+
.filter((part) => part !== undefined)
23+
.join(".");
24+
};
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
export { getProcessArnablesPlugin } from "./plugin";
22
export { parseOutpostArnablesMiddleaware, parseOutpostArnablesMiddleawareOptions } from "./parse-outpost-arnables";
3-
export {
4-
updateArnablesRequestMiddleware,
5-
updateArnablesRequestMiddlewareOptions,
6-
getOutpostEndpoint,
7-
} from "./update-arnables-request";
3+
export { updateArnablesRequestMiddleware, updateArnablesRequestMiddlewareOptions } from "./update-arnables-request";
4+
export { getOutpostEndpoint } from "./getOutpostEndpoint";

packages/middleware-sdk-s3-control/src/process-arnables-plugin/parse-outpost-arnables.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
getPseudoRegion,
44
validateAccountId,
55
validateNoDualstack,
6-
validateNoFIPS,
76
validateOutpostService,
87
validatePartition,
98
validateRegion,
@@ -114,7 +113,6 @@ const validateOutpostsArn = (
114113
clientSigningRegion: signingRegion,
115114
});
116115
validateNoDualstack(useDualstackEndpoint);
117-
if (!useArnRegion) validateNoFIPS(clientRegion);
118116
};
119117

120118
const parseOutpostsAccessPointArnResource = (

packages/middleware-sdk-s3-control/src/process-arnables-plugin/plugin.spec.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -169,27 +169,6 @@ describe("getProcessArnablesMiddleware", () => {
169169
expect(context).toMatchObject({ signing_service: "s3-outposts", signing_region: "us-gov-east-1" });
170170
});
171171

172-
it("should validate when client region is fips region", async () => {
173-
expect.assertions(1);
174-
const clientRegion = "fips-us-gov-east-1";
175-
const hostname = `s3-control.${clientRegion}.amazonaws.com`;
176-
const options = setupPluginOptions({
177-
region: clientRegion,
178-
regionInfoProvider: () => Promise.resolve({ hostname, partition: "aws-us-gov" }),
179-
});
180-
const stack = getStack(hostname, options);
181-
const handler = stack.resolve((() => {}) as any, {});
182-
try {
183-
await handler({
184-
input: {
185-
Name: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
186-
},
187-
});
188-
} catch (e) {
189-
expect(e.message).toContain("FIPS region is not supported");
190-
}
191-
});
192-
193172
it("should validate when arn region is fips region", async () => {
194173
expect.assertions(1);
195174
const clientRegion = "fips-us-gov-east-1";
@@ -411,28 +390,6 @@ describe("getProcessArnablesMiddleware", () => {
411390
expect(context).toMatchObject({ signing_service: "s3-outposts", signing_region: "us-gov-east-1" });
412391
});
413392

414-
it("should validate when client region is fips region", async () => {
415-
expect.assertions(1);
416-
const clientRegion = "fips-us-gov-east-1";
417-
const hostname = `s3-control.${clientRegion}.amazonaws.com`;
418-
const options = setupPluginOptions({
419-
region: clientRegion,
420-
regionInfoProvider: () => Promise.resolve({ hostname, partition: "aws-us-gov" }),
421-
});
422-
const stack = getStack(hostname, options);
423-
const handler = stack.resolve((() => {}) as any, {});
424-
try {
425-
await handler({
426-
input: {
427-
Bucket:
428-
"arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket",
429-
},
430-
});
431-
} catch (e) {
432-
expect(e.message).toContain("FIPS region is not supported");
433-
}
434-
});
435-
436393
it("should validate when arn region is fips region", async () => {
437394
expect.assertions(1);
438395
const clientRegion = "fips-us-gov-east-1";

packages/middleware-sdk-s3-control/src/process-arnables-plugin/update-arnables-request.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,41 @@
11
import { HttpRequest } from "@aws-sdk/protocol-http";
2-
import { BuildHandlerOptions, BuildMiddleware } from "@aws-sdk/types";
2+
import { BuildHandlerOptions, BuildMiddleware, Provider } from "@aws-sdk/types";
33

4-
import { S3ControlResolvedConfig } from "../configurations";
54
import { CONTEXT_ACCOUNT_ID, CONTEXT_ARN_REGION, CONTEXT_OUTPOST_ID } from "../constants";
5+
import { getOutpostEndpoint } from "./getOutpostEndpoint";
66

77
const ACCOUNT_ID_HEADER = "x-amz-account-id";
88
const OUTPOST_ID_HEADER = "x-amz-outpost-id";
9-
const REGEX_S3CONTROL_HOSTNAME = /^(.+\.)?s3-control[.-]([a-z0-9-]+)\./;
9+
10+
export interface UpdateArnablesRequestMiddlewareConfig {
11+
isCustomEndpoint: boolean;
12+
useFipsEndpoint: Provider<boolean>;
13+
}
1014

1115
/**
1216
* After outpost request is constructed, redirect request to outpost endpoint and set `x-amz-account-id` and
1317
* `x-amz-outpost-id` headers.
1418
*/
1519
export const updateArnablesRequestMiddleware =
16-
({ isCustomEndpoint }: { isCustomEndpoint: boolean }): BuildMiddleware<any, any> =>
20+
(config: UpdateArnablesRequestMiddlewareConfig): BuildMiddleware<any, any> =>
1721
(next, context) =>
18-
(args) => {
22+
async (args) => {
1923
const { request } = args;
2024
if (!HttpRequest.isInstance(request)) return next(args);
2125
if (context[CONTEXT_ACCOUNT_ID]) request.headers[ACCOUNT_ID_HEADER] = context[CONTEXT_ACCOUNT_ID];
2226
if (context[CONTEXT_OUTPOST_ID]) {
27+
const { isCustomEndpoint } = config;
28+
const useFipsEndpoint = await config.useFipsEndpoint();
2329
request.headers[OUTPOST_ID_HEADER] = context[CONTEXT_OUTPOST_ID];
2430
request.hostname = getOutpostEndpoint(request.hostname, {
2531
isCustomEndpoint,
2632
regionOverride: context[CONTEXT_ARN_REGION],
33+
useFipsEndpoint,
2734
});
2835
}
2936
return next(args);
3037
};
3138

32-
export const getOutpostEndpoint = (
33-
hostname: string,
34-
{ isCustomEndpoint, regionOverride }: { isCustomEndpoint?: boolean; regionOverride?: string } = {}
35-
): string => {
36-
const [matched, prefix, region] = hostname.match(REGEX_S3CONTROL_HOSTNAME)!;
37-
// hostname prefix will be ignored even if presents
38-
return isCustomEndpoint
39-
? hostname
40-
: ["s3-outposts", regionOverride || region, hostname.replace(new RegExp(`^${matched}`), "")]
41-
.filter((part) => part !== undefined)
42-
.join(".");
43-
};
44-
4539
export const updateArnablesRequestMiddlewareOptions: BuildHandlerOptions = {
4640
step: "build",
4741
name: "updateArnablesRequestMiddleware",

packages/middleware-sdk-s3-control/src/redirect-from-postid.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ describe("redirectFromPostIdMiddleware", () => {
66
it("should redirect request if Bucket is a valid ARN", async () => {
77
const next: any = (args: any) => ({ output: args.request });
88
const context: any = {};
9-
const { output } = await redirectFromPostIdMiddleware({ isCustomEndpoint: false })(next, context)({
9+
const { output } = await redirectFromPostIdMiddleware({
10+
isCustomEndpoint: false,
11+
useFipsEndpoint: () => Promise.resolve(false),
12+
})(
13+
next,
14+
context
15+
)({
1016
input: { OutpostId: "op-123" },
1117
request: new HttpRequest({ hostname: "123456789012.s3-control.us-west-2.amazonaws.com" }),
1218
});

packages/middleware-sdk-s3-control/src/redirect-from-postid.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HttpRequest } from "@aws-sdk/protocol-http";
2-
import { BuildHandlerOptions, BuildMiddleware, Pluggable } from "@aws-sdk/types";
2+
import { BuildHandlerOptions, BuildMiddleware, Pluggable, Provider } from "@aws-sdk/types";
33

44
import { S3ControlResolvedConfig } from "./configurations";
55
import { CONTEXT_SIGNING_SERVICE } from "./constants";
@@ -9,18 +9,25 @@ type InputType = {
99
OutpostId?: string;
1010
};
1111

12+
export interface RedirectFromPostIdMiddlewareConfig {
13+
isCustomEndpoint: boolean;
14+
useFipsEndpoint: Provider<boolean>;
15+
}
16+
1217
/**
1318
* If OutpostId is set, redirect hostname to Outpost one, and change signing service to `s3-outposts`.
1419
* Applied to S3Control.CreateBucket and S3Control.ListRegionalBuckets
1520
*/
1621
export const redirectFromPostIdMiddleware =
17-
({ isCustomEndpoint }: { isCustomEndpoint: boolean }): BuildMiddleware<InputType, any> =>
22+
(config: RedirectFromPostIdMiddlewareConfig): BuildMiddleware<InputType, any> =>
1823
(next, context) =>
19-
(args) => {
24+
async (args) => {
2025
const { input, request } = args;
2126
if (!HttpRequest.isInstance(request)) return next(args);
2227
if (input.OutpostId) {
23-
request.hostname = getOutpostEndpoint(request.hostname, { isCustomEndpoint });
28+
const { isCustomEndpoint } = config;
29+
const useFipsEndpoint = await config.useFipsEndpoint();
30+
request.hostname = getOutpostEndpoint(request.hostname, { isCustomEndpoint, useFipsEndpoint });
2431
context[CONTEXT_SIGNING_SERVICE] = "s3-outposts";
2532
}
2633
return next(args);

0 commit comments

Comments
 (0)