|
| 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 | +} |
0 commit comments