Skip to content

Commit c1bed9d

Browse files
authored
feat(client-s3): support generating endpoints from multi-region access point (#2796)
* Revert "fix(client-s3): revert MRAP customizations (#2759)" This reverts commit cfb3fff. * chore: update mrap packages dependencies * feat(middleware-sdk-s3): dynamically import aws-crt package optional dependencies * feat(middleware-user-agent): track if aws-crt is available at runtime * chore: update crt signer loading error message * chore(middleware-sdk-s3): add crt package to dev dependency and track usage This makes sure the type of CRT package is available and the Lerna builds packages in right order--build crt signer package before buiding s3 middleware package. * docs(client-s3): add docs to Bucket members on using MRAP * chore(signature-v4-crt): update crt version to ^1.9.7 * fix(client-s3): address PR feedbacks
1 parent 66de569 commit c1bed9d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2750
-260
lines changed

Diff for: clients/client-s3/models/models_0.ts

+168
Large diffs are not rendered by default.

Diff for: clients/client-s3/models/models_1.ts

+8
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,8 @@ export interface RestoreObjectRequest {
514514
* <p>The bucket name containing the object to restore. </p>
515515
* <p>When using this action with an access point, you must direct requests to the access point hostname. The access point hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.s3-accesspoint.<i>Region</i>.amazonaws.com. When using this action with an access point through the Amazon Web Services SDKs, you provide the access point ARN in place of the bucket name. For more information about access point ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html">Using access points</a> in the <i>Amazon S3 User Guide</i>.</p>
516516
* <p>When using this action with Amazon S3 on Outposts, you must direct requests to the S3 on Outposts hostname. The S3 on Outposts hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.<i>outpostID</i>.s3-outposts.<i>Region</i>.amazonaws.com. When using this action using S3 on Outposts through the Amazon Web Services SDKs, you provide the Outposts bucket ARN in place of the bucket name. For more information about S3 on Outposts ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3onOutposts.html">Using S3 on Outposts</a> in the <i>Amazon S3 User Guide</i>.</p>
517+
* <p>Note: To supply the Multi-region Access Point (MRAP) to Bucket, you need to install the "@aws-sdk/signature-v4-crt" package to your project dependencies.
518+
* For more information, please go to https://github.com/aws/aws-sdk-js-v3#known-issues</p>
517519
*/
518520
Bucket: string | undefined;
519521

@@ -898,6 +900,8 @@ export namespace ScanRange {
898900
export interface SelectObjectContentRequest {
899901
/**
900902
* <p>The S3 bucket.</p>
903+
* <p>Note: To supply the Multi-region Access Point (MRAP) to Bucket, you need to install the "@aws-sdk/signature-v4-crt" package to your project dependencies.
904+
* For more information, please go to https://github.com/aws/aws-sdk-js-v3#known-issues</p>
901905
*/
902906
Bucket: string | undefined;
903907

@@ -1055,6 +1059,8 @@ export interface UploadPartRequest {
10551059
* <p>The name of the bucket to which the multipart upload was initiated.</p>
10561060
* <p>When using this action with an access point, you must direct requests to the access point hostname. The access point hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.s3-accesspoint.<i>Region</i>.amazonaws.com. When using this action with an access point through the Amazon Web Services SDKs, you provide the access point ARN in place of the bucket name. For more information about access point ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html">Using access points</a> in the <i>Amazon S3 User Guide</i>.</p>
10571061
* <p>When using this action with Amazon S3 on Outposts, you must direct requests to the S3 on Outposts hostname. The S3 on Outposts hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.<i>outpostID</i>.s3-outposts.<i>Region</i>.amazonaws.com. When using this action using S3 on Outposts through the Amazon Web Services SDKs, you provide the Outposts bucket ARN in place of the bucket name. For more information about S3 on Outposts ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3onOutposts.html">Using S3 on Outposts</a> in the <i>Amazon S3 User Guide</i>.</p>
1062+
* <p>Note: To supply the Multi-region Access Point (MRAP) to Bucket, you need to install the "@aws-sdk/signature-v4-crt" package to your project dependencies.
1063+
* For more information, please go to https://github.com/aws/aws-sdk-js-v3#known-issues</p>
10581064
*/
10591065
Bucket: string | undefined;
10601066

@@ -1221,6 +1227,8 @@ export interface UploadPartCopyRequest {
12211227
* <p>The bucket name.</p>
12221228
* <p>When using this action with an access point, you must direct requests to the access point hostname. The access point hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.s3-accesspoint.<i>Region</i>.amazonaws.com. When using this action with an access point through the Amazon Web Services SDKs, you provide the access point ARN in place of the bucket name. For more information about access point ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html">Using access points</a> in the <i>Amazon S3 User Guide</i>.</p>
12231229
* <p>When using this action with Amazon S3 on Outposts, you must direct requests to the S3 on Outposts hostname. The S3 on Outposts hostname takes the form <i>AccessPointName</i>-<i>AccountId</i>.<i>outpostID</i>.s3-outposts.<i>Region</i>.amazonaws.com. When using this action using S3 on Outposts through the Amazon Web Services SDKs, you provide the Outposts bucket ARN in place of the bucket name. For more information about S3 on Outposts ARNs, see <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3onOutposts.html">Using S3 on Outposts</a> in the <i>Amazon S3 User Guide</i>.</p>
1230+
* <p>Note: To supply the Multi-region Access Point (MRAP) to Bucket, you need to install the "@aws-sdk/signature-v4-crt" package to your project dependencies.
1231+
* For more information, please go to https://github.com/aws/aws-sdk-js-v3#known-issues</p>
12241232
*/
12251233
Bucket: string | undefined;
12261234

Diff for: clients/client-s3/runtimeConfig.shared.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { defaultRegionInfoProvider } from "./endpoints";
2+
import { S3SignatureV4 } from "@aws-sdk/middleware-sdk-s3";
23
import { Logger as __Logger } from "@aws-sdk/types";
34
import { parseUrl } from "@aws-sdk/url-parser";
45
import { S3ClientConfig } from "./S3Client";
@@ -12,6 +13,7 @@ export const getRuntimeConfig = (config: S3ClientConfig) => ({
1213
logger: config?.logger ?? ({} as __Logger),
1314
regionInfoProvider: config?.regionInfoProvider ?? defaultRegionInfoProvider,
1415
serviceId: config?.serviceId ?? "S3",
16+
signerConstructor: config?.signerConstructor ?? S3SignatureV4,
1517
signingEscapePath: config?.signingEscapePath ?? false,
1618
urlParser: config?.urlParser ?? parseUrl,
1719
useArnRegion: config?.useArnRegion ?? false,

Diff for: codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java

+49
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@
1919
import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE;
2020

2121
import java.util.Collections;
22+
import java.util.HashSet;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
2526
import java.util.function.Consumer;
27+
import java.util.logging.Logger;
2628
import software.amazon.smithy.aws.traits.ServiceTrait;
29+
import software.amazon.smithy.build.PluginContext;
2730
import software.amazon.smithy.codegen.core.SymbolProvider;
2831
import software.amazon.smithy.model.Model;
2932
import software.amazon.smithy.model.knowledge.OperationIndex;
33+
import software.amazon.smithy.model.shapes.MemberShape;
3034
import software.amazon.smithy.model.shapes.OperationShape;
35+
import software.amazon.smithy.model.shapes.ServiceShape;
3136
import software.amazon.smithy.model.shapes.Shape;
37+
import software.amazon.smithy.model.shapes.ShapeId;
38+
import software.amazon.smithy.model.shapes.StructureShape;
39+
import software.amazon.smithy.model.traits.DocumentationTrait;
3240
import software.amazon.smithy.typescript.codegen.LanguageTarget;
3341
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
3442
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
@@ -45,6 +53,7 @@
4553
*/
4654
@SmithyInternalApi
4755
public final class AddS3Config implements TypeScriptIntegration {
56+
private static final Logger LOGGER = Logger.getLogger(AddS3Config.class.getName());
4857

4958
private static final Set<String> SSEC_OPERATIONS = SetUtils.of("SSECustomerKey", "CopySourceSSECustomerKey");
5059

@@ -60,6 +69,42 @@ public final class AddS3Config implements TypeScriptIntegration {
6069
"CompleteMultipartUpload"
6170
);
6271

72+
private static final String CRT_NOTIFICATION = "<p>Note: To supply the Multi-region Access Point (MRAP) to Bucket,"
73+
+ " you need to install the \"@aws-sdk/signature-v4-crt\" package to your project dependencies. \n"
74+
+ "For more information, please go to https://github.com/aws/aws-sdk-js-v3#known-issues</p>";
75+
76+
@Override
77+
public Model preprocessModel(PluginContext context, TypeScriptSettings settings) {
78+
Model model = context.getModel();
79+
ServiceShape serviceShape = settings.getService(model);
80+
if (!testServiceId(serviceShape)) {
81+
return model;
82+
}
83+
Model.Builder modelBuilder = model.toBuilder();
84+
Set<StructureShape> inputShapes = new HashSet<>();
85+
for (ShapeId operationId : serviceShape.getAllOperations()) {
86+
OperationShape operationShape = model.expectShape(operationId, OperationShape.class);
87+
if (NON_BUCKET_ENDPOINT_OPERATIONS.contains(operationShape.getId().getName(serviceShape))) {
88+
continue;
89+
}
90+
operationShape.getInput().ifPresent(inputShapeId -> {
91+
StructureShape inputShape = model.expectShape(inputShapeId, StructureShape.class);
92+
inputShape.getMember("Bucket").ifPresent(bucketMember -> {
93+
bucketMember.getTrait(DocumentationTrait.class).ifPresent(documentationTrait -> {
94+
StructureShape.Builder inputShapeBuilder = inputShape.toBuilder();
95+
MemberShape.Builder builder = MemberShape.shapeToBuilder(bucketMember);
96+
String newDocString = documentationTrait.getValue() + "\n" + CRT_NOTIFICATION;
97+
MemberShape newMemberShape = builder.addTrait(new DocumentationTrait(newDocString)).build();
98+
inputShapeBuilder.addMember(newMemberShape);
99+
inputShapes.add(inputShapeBuilder.build());
100+
});
101+
});
102+
});
103+
}
104+
LOGGER.info("Patching " + inputShapes.size() + " input shapes with CRT notification");
105+
return modelBuilder.addShapes(inputShapes).build();
106+
}
107+
63108
@Override
64109
public void addConfigInterfaceFields(TypeScriptSettings settings, Model model, SymbolProvider symbolProvider,
65110
TypeScriptWriter writer) {
@@ -87,6 +132,10 @@ public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(TypeScrip
87132
writer.write("false");
88133
}, "useArnRegion", writer -> {
89134
writer.write("false");
135+
}, "signerConstructor", writer -> {
136+
writer.addDependency(AwsDependency.S3_MIDDLEWARE)
137+
.addImport("S3SignatureV4", "S3SignatureV4", AwsDependency.S3_MIDDLEWARE.packageName)
138+
.write("S3SignatureV4");
90139
});
91140
case NODE:
92141
return MapUtils.of("useArnRegion", writer -> {

Diff for: packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ describe("bucketEndpointMiddleware", () => {
134134
clientSigningRegion: mockRegion,
135135
useArnRegion: false,
136136
isCustomEndpoint: false,
137+
disableMultiregionAccessPoints: false,
137138
});
138139
expect(previouslyResolvedConfig.region).toBeCalled();
139140
expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled();

Diff for: packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts

+63-68
Original file line numberDiff line numberDiff line change
@@ -15,81 +15,76 @@ import { bucketHostname } from "./bucketHostname";
1515
import { getPseudoRegion } from "./bucketHostnameUtils";
1616
import { BucketEndpointResolvedConfig } from "./configurations";
1717

18-
export const bucketEndpointMiddleware =
19-
(options: BucketEndpointResolvedConfig): BuildMiddleware<any, any> =>
20-
<Output extends MetadataBearer>(
21-
next: BuildHandler<any, Output>,
22-
context: HandlerExecutionContext
23-
): BuildHandler<any, Output> =>
24-
async (args: BuildHandlerArguments<any>): Promise<BuildHandlerOutput<Output>> => {
25-
const { Bucket: bucketName } = args.input as { Bucket: string };
26-
let replaceBucketInPath = options.bucketEndpoint;
27-
const request = args.request;
28-
if (HttpRequest.isInstance(request)) {
29-
if (options.bucketEndpoint) {
30-
request.hostname = bucketName;
31-
} else if (validateArn(bucketName)) {
32-
const bucketArn = parseArn(bucketName);
33-
const clientRegion = getPseudoRegion(await options.region());
34-
const { partition, signingRegion = clientRegion } = (await options.regionInfoProvider(clientRegion)) || {};
35-
const useArnRegion = await options.useArnRegion();
36-
const {
37-
hostname,
38-
bucketEndpoint,
39-
signingRegion: modifiedSigningRegion,
40-
signingService,
41-
} = bucketHostname({
42-
bucketName: bucketArn,
43-
baseHostname: request.hostname,
44-
accelerateEndpoint: options.useAccelerateEndpoint,
45-
dualstackEndpoint: options.useDualstackEndpoint,
46-
pathStyleEndpoint: options.forcePathStyle,
47-
tlsCompatible: request.protocol === "https:",
48-
useArnRegion,
49-
clientPartition: partition,
50-
clientSigningRegion: signingRegion,
51-
clientRegion: clientRegion,
52-
isCustomEndpoint: options.isCustomEndpoint,
53-
});
18+
export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): BuildMiddleware<any, any> => <
19+
Output extends MetadataBearer
20+
>(
21+
next: BuildHandler<any, Output>,
22+
context: HandlerExecutionContext
23+
): BuildHandler<any, Output> => async (args: BuildHandlerArguments<any>): Promise<BuildHandlerOutput<Output>> => {
24+
const { Bucket: bucketName } = args.input as { Bucket: string };
25+
let replaceBucketInPath = options.bucketEndpoint;
26+
const request = args.request;
27+
if (HttpRequest.isInstance(request)) {
28+
if (options.bucketEndpoint) {
29+
request.hostname = bucketName;
30+
} else if (validateArn(bucketName)) {
31+
const bucketArn = parseArn(bucketName);
32+
const clientRegion = getPseudoRegion(await options.region());
33+
const { partition, signingRegion = clientRegion } = (await options.regionInfoProvider(clientRegion)) || {};
34+
const useArnRegion = await options.useArnRegion();
35+
const { hostname, bucketEndpoint, signingRegion: modifiedSigningRegion, signingService } = bucketHostname({
36+
bucketName: bucketArn,
37+
baseHostname: request.hostname,
38+
accelerateEndpoint: options.useAccelerateEndpoint,
39+
dualstackEndpoint: options.useDualstackEndpoint,
40+
pathStyleEndpoint: options.forcePathStyle,
41+
tlsCompatible: request.protocol === "https:",
42+
useArnRegion,
43+
clientPartition: partition,
44+
clientSigningRegion: signingRegion,
45+
clientRegion: clientRegion,
46+
isCustomEndpoint: options.isCustomEndpoint,
47+
disableMultiregionAccessPoints: await options.disableMultiregionAccessPoints(),
48+
});
5449

55-
// If the request needs to use a region or service name inferred from ARN that different from client region, we
56-
// need to set them in the handler context so the signer will use them
57-
if (modifiedSigningRegion && modifiedSigningRegion !== signingRegion) {
58-
context["signing_region"] = modifiedSigningRegion;
59-
}
60-
if (signingService && signingService !== "s3") {
61-
context["signing_service"] = signingService;
62-
}
50+
// If the request needs to use a region or service name inferred from ARN that different from client region, we
51+
// need to set them in the handler context so the signer will use them
52+
if (modifiedSigningRegion && modifiedSigningRegion !== signingRegion) {
53+
context["signing_region"] = modifiedSigningRegion;
54+
}
55+
if (signingService && signingService !== "s3") {
56+
context["signing_service"] = signingService;
57+
}
6358

64-
request.hostname = hostname;
65-
replaceBucketInPath = bucketEndpoint;
66-
} else {
67-
const clientRegion = getPseudoRegion(await options.region());
68-
const { hostname, bucketEndpoint } = bucketHostname({
69-
bucketName,
70-
clientRegion,
71-
baseHostname: request.hostname,
72-
accelerateEndpoint: options.useAccelerateEndpoint,
73-
dualstackEndpoint: options.useDualstackEndpoint,
74-
pathStyleEndpoint: options.forcePathStyle,
75-
tlsCompatible: request.protocol === "https:",
76-
isCustomEndpoint: options.isCustomEndpoint,
77-
});
59+
request.hostname = hostname;
60+
replaceBucketInPath = bucketEndpoint;
61+
} else {
62+
const clientRegion = getPseudoRegion(await options.region());
63+
const { hostname, bucketEndpoint } = bucketHostname({
64+
bucketName,
65+
clientRegion,
66+
baseHostname: request.hostname,
67+
accelerateEndpoint: options.useAccelerateEndpoint,
68+
dualstackEndpoint: options.useDualstackEndpoint,
69+
pathStyleEndpoint: options.forcePathStyle,
70+
tlsCompatible: request.protocol === "https:",
71+
isCustomEndpoint: options.isCustomEndpoint,
72+
});
7873

79-
request.hostname = hostname;
80-
replaceBucketInPath = bucketEndpoint;
81-
}
74+
request.hostname = hostname;
75+
replaceBucketInPath = bucketEndpoint;
76+
}
8277

83-
if (replaceBucketInPath) {
84-
request.path = request.path.replace(/^(\/)?[^\/]+/, "");
85-
if (request.path === "") {
86-
request.path = "/";
87-
}
78+
if (replaceBucketInPath) {
79+
request.path = request.path.replace(/^(\/)?[^\/]+/, "");
80+
if (request.path === "") {
81+
request.path = "/";
8882
}
8983
}
84+
}
9085

91-
return next({ ...args, request });
92-
};
86+
return next({ ...args, request });
87+
};
9388

9489
export const bucketEndpointMiddlewareOptions: RelativeMiddlewareOptions = {
9590
tags: ["BUCKET_ENDPOINT"],

0 commit comments

Comments
 (0)