Skip to content

Commit f48fa01

Browse files
John Viegasjoviegas
John Viegas
authored andcommitted
Cross-Region Copying Pre-Signer for DocDB and Neptune
1 parent 9f3e7c7 commit f48fa01

File tree

16 files changed

+1179
-2
lines changed

16 files changed

+1179
-2
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon DocumentDB with MongoDB compatibility",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Cross-Region Copying Pre-Signer for CopyDBClusterSnapshot and CreateDBCluster API of DocDB."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon Neptune",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Cross-Region Copying Pre-Signer for CopyDBClusterSnapshot and CreateDBCluster API of Neptune."
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.docdb.internal;
17+
18+
import java.time.Clock;
19+
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.annotations.SdkTestInternalApi;
21+
import software.amazon.awssdk.http.SdkHttpFullRequest;
22+
import software.amazon.awssdk.services.docdb.model.CopyDbClusterSnapshotRequest;
23+
import software.amazon.awssdk.services.docdb.transform.CopyDbClusterSnapshotRequestMarshaller;
24+
25+
26+
/**
27+
* Handler for pre-signing {@link CopyDbClusterSnapshotRequest}.
28+
*/
29+
@SdkInternalApi
30+
public final class CopyDbClusterSnapshotPresignInterceptor extends RdsPresignInterceptor<CopyDbClusterSnapshotRequest> {
31+
32+
public static final CopyDbClusterSnapshotRequestMarshaller MARSHALLER =
33+
new CopyDbClusterSnapshotRequestMarshaller(PROTOCOL_FACTORY);
34+
35+
public CopyDbClusterSnapshotPresignInterceptor() {
36+
super(CopyDbClusterSnapshotRequest.class);
37+
}
38+
39+
@SdkTestInternalApi
40+
CopyDbClusterSnapshotPresignInterceptor(Clock signingDateOverride) {
41+
super(CopyDbClusterSnapshotRequest.class, signingDateOverride);
42+
}
43+
44+
@Override
45+
protected PresignableRequest adaptRequest(final CopyDbClusterSnapshotRequest originalRequest) {
46+
return new PresignableRequest() {
47+
@Override
48+
public String getSourceRegion() {
49+
return originalRequest.sourceRegion();
50+
}
51+
52+
@Override
53+
public SdkHttpFullRequest marshall() {
54+
return MARSHALLER.marshall(originalRequest);
55+
}
56+
};
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.docdb.internal;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.http.SdkHttpFullRequest;
20+
import software.amazon.awssdk.services.docdb.model.CreateDbClusterRequest;
21+
import software.amazon.awssdk.services.docdb.transform.CreateDbClusterRequestMarshaller;
22+
23+
/**
24+
* Handler for pre-signing {@link CreateDbClusterRequest}.
25+
*/
26+
@SdkInternalApi
27+
public final class CreateDbClusterPresignInterceptor extends RdsPresignInterceptor<CreateDbClusterRequest> {
28+
29+
public static final CreateDbClusterRequestMarshaller MARSHALLER =
30+
new CreateDbClusterRequestMarshaller(PROTOCOL_FACTORY);
31+
32+
public CreateDbClusterPresignInterceptor() {
33+
super(CreateDbClusterRequest.class);
34+
}
35+
36+
@Override
37+
protected PresignableRequest adaptRequest(final CreateDbClusterRequest originalRequest) {
38+
return new PresignableRequest() {
39+
@Override
40+
public String getSourceRegion() {
41+
return originalRequest.sourceRegion();
42+
}
43+
44+
@Override
45+
public SdkHttpFullRequest marshall() {
46+
return MARSHALLER.marshall(originalRequest);
47+
}
48+
};
49+
}
50+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.docdb.internal;
17+
18+
import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS;
19+
20+
import java.net.URI;
21+
import java.time.Clock;
22+
import software.amazon.awssdk.annotations.SdkInternalApi;
23+
import software.amazon.awssdk.auth.signer.Aws4Signer;
24+
import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
25+
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
26+
import software.amazon.awssdk.awscore.endpoint.DefaultServiceEndpointBuilder;
27+
import software.amazon.awssdk.core.Protocol;
28+
import software.amazon.awssdk.core.SdkRequest;
29+
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
30+
import software.amazon.awssdk.core.client.config.SdkClientOption;
31+
import software.amazon.awssdk.core.exception.SdkClientException;
32+
import software.amazon.awssdk.core.interceptor.Context;
33+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
34+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
35+
import software.amazon.awssdk.http.SdkHttpFullRequest;
36+
import software.amazon.awssdk.http.SdkHttpMethod;
37+
import software.amazon.awssdk.http.SdkHttpRequest;
38+
import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory;
39+
import software.amazon.awssdk.regions.Region;
40+
import software.amazon.awssdk.services.docdb.model.DocDbRequest;
41+
42+
/**
43+
* Abstract pre-sign handler that follows the pre-signing scheme outlined in the 'RDS Presigned URL for Cross-Region Copying'
44+
* SEP.
45+
*
46+
* @param <T> The request type.
47+
*/
48+
@SdkInternalApi
49+
public abstract class RdsPresignInterceptor<T extends DocDbRequest> implements ExecutionInterceptor {
50+
51+
private static final URI CUSTOM_ENDPOINT_LOCALHOST = URI.create("http://localhost");
52+
53+
protected static final AwsQueryProtocolFactory PROTOCOL_FACTORY = AwsQueryProtocolFactory
54+
.builder()
55+
// Need an endpoint to marshall but this will be overwritten in modifyHttpRequest
56+
.clientConfiguration(SdkClientConfiguration.builder()
57+
.option(SdkClientOption.ENDPOINT, CUSTOM_ENDPOINT_LOCALHOST)
58+
.build())
59+
.build();
60+
61+
private static final String SERVICE_NAME = "rds";
62+
private static final String PARAM_SOURCE_REGION = "SourceRegion";
63+
private static final String PARAM_DESTINATION_REGION = "DestinationRegion";
64+
private static final String PARAM_PRESIGNED_URL = "PreSignedUrl";
65+
66+
67+
public interface PresignableRequest {
68+
String getSourceRegion();
69+
70+
SdkHttpFullRequest marshall();
71+
}
72+
73+
private final Class<T> requestClassToPreSign;
74+
75+
private final Clock signingOverrideClock;
76+
77+
protected RdsPresignInterceptor(Class<T> requestClassToPreSign) {
78+
this(requestClassToPreSign, null);
79+
}
80+
81+
protected RdsPresignInterceptor(Class<T> requestClassToPreSign, Clock signingOverrideClock) {
82+
this.requestClassToPreSign = requestClassToPreSign;
83+
this.signingOverrideClock = signingOverrideClock;
84+
}
85+
86+
@Override
87+
public final SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
88+
ExecutionAttributes executionAttributes) {
89+
SdkHttpRequest request = context.httpRequest();
90+
SdkRequest originalRequest = context.request();
91+
if (!requestClassToPreSign.isInstance(originalRequest)) {
92+
return request;
93+
}
94+
95+
if (request.rawQueryParameters().containsKey(PARAM_PRESIGNED_URL)) {
96+
return request;
97+
}
98+
99+
PresignableRequest presignableRequest = adaptRequest(requestClassToPreSign.cast(originalRequest));
100+
101+
String sourceRegion = presignableRequest.getSourceRegion();
102+
if (sourceRegion == null) {
103+
return request;
104+
}
105+
106+
String destinationRegion = executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION).id();
107+
108+
URI endpoint = createEndpoint(sourceRegion, SERVICE_NAME);
109+
SdkHttpFullRequest.Builder marshalledRequest = presignableRequest.marshall().toBuilder().uri(endpoint);
110+
111+
SdkHttpFullRequest requestToPresign =
112+
marshalledRequest.method(SdkHttpMethod.GET)
113+
.putRawQueryParameter(PARAM_DESTINATION_REGION, destinationRegion)
114+
.removeQueryParameter(PARAM_SOURCE_REGION)
115+
.build();
116+
117+
requestToPresign = presignRequest(requestToPresign, executionAttributes, sourceRegion);
118+
119+
String presignedUrl = requestToPresign.getUri().toString();
120+
121+
return request.toBuilder()
122+
.putRawQueryParameter(PARAM_PRESIGNED_URL, presignedUrl)
123+
// Remove the unmodeled params to stop them getting onto the wire
124+
.removeQueryParameter(PARAM_SOURCE_REGION)
125+
.build();
126+
}
127+
128+
/**
129+
* Adapts the request to the {@link PresignableRequest}.
130+
*
131+
* @param originalRequest the original request
132+
* @return a PresignableRequest
133+
*/
134+
protected abstract PresignableRequest adaptRequest(T originalRequest);
135+
136+
private SdkHttpFullRequest presignRequest(SdkHttpFullRequest request,
137+
ExecutionAttributes attributes,
138+
String signingRegion) {
139+
140+
Aws4Signer signer = Aws4Signer.create();
141+
Aws4PresignerParams presignerParams = Aws4PresignerParams.builder()
142+
.signingRegion(Region.of(signingRegion))
143+
.signingName(SERVICE_NAME)
144+
.signingClockOverride(signingOverrideClock)
145+
.awsCredentials(attributes.getAttribute(AWS_CREDENTIALS))
146+
.build();
147+
148+
return signer.presign(request, presignerParams);
149+
}
150+
151+
private URI createEndpoint(String regionName, String serviceName) {
152+
Region region = Region.of(regionName);
153+
154+
if (region == null) {
155+
throw SdkClientException.builder()
156+
.message("{" + serviceName + ", " + regionName + "} was not "
157+
+ "found in region metadata. Update to latest version of SDK and try again.")
158+
.build();
159+
}
160+
161+
return new DefaultServiceEndpointBuilder(SERVICE_NAME, Protocol.HTTPS.toString())
162+
.withRegion(region)
163+
.getServiceEndpoint();
164+
}
165+
}

services/docdb/src/main/resources/codegen-resources/customization.config

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,29 @@
99
"describeEventCategories",
1010
"describeEvents",
1111
"describePendingMaintenanceActions"
12-
]
12+
],
13+
"shapeModifiers" : {
14+
"CopyDBClusterSnapshotMessage" : {
15+
"inject" : [
16+
{
17+
// This is for enabling automatic request presigning only; it should not be marshalled
18+
"SourceRegion" : {
19+
"shape" : "String",
20+
"documentation" : "If PreSignedUrl is not specified, this is the region where the source snapshot is located. A PreSignedUrl will be generated automatically by the SDK."
21+
}
22+
}
23+
]
24+
},
25+
"CreateDBClusterMessage" : {
26+
"inject" : [
27+
{
28+
// This is for enabling automatic request presigning only; it should not be marshalled
29+
"SourceRegion" : {
30+
"shape" : "String",
31+
"documentation" : "If PreSignedUrl is not specified, this is the region where the source snapshot is located. A PreSignedUrl will be generated automatically by the SDK."
32+
}
33+
}
34+
]
35+
}
36+
}
1337
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
software.amazon.awssdk.services.docdb.internal.CopyDbClusterSnapshotPresignInterceptor
2+
software.amazon.awssdk.services.docdb.internal.CreateDbClusterPresignInterceptor

0 commit comments

Comments
 (0)