• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ec2.transform.internal;
17 
18 import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS;
19 import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME;
20 
21 import java.net.URI;
22 import java.time.Clock;
23 import software.amazon.awssdk.annotations.SdkInternalApi;
24 import software.amazon.awssdk.annotations.SdkTestInternalApi;
25 import software.amazon.awssdk.auth.credentials.AwsCredentials;
26 import software.amazon.awssdk.auth.credentials.CredentialUtils;
27 import software.amazon.awssdk.auth.signer.Aws4Signer;
28 import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
29 import software.amazon.awssdk.awscore.util.AwsHostNameUtils;
30 import software.amazon.awssdk.core.SdkRequest;
31 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
32 import software.amazon.awssdk.core.client.config.SdkClientOption;
33 import software.amazon.awssdk.core.exception.SdkClientException;
34 import software.amazon.awssdk.core.interceptor.Context;
35 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
36 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
37 import software.amazon.awssdk.http.SdkHttpFullRequest;
38 import software.amazon.awssdk.http.SdkHttpMethod;
39 import software.amazon.awssdk.http.SdkHttpRequest;
40 import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
41 import software.amazon.awssdk.protocols.query.AwsEc2ProtocolFactory;
42 import software.amazon.awssdk.regions.Region;
43 import software.amazon.awssdk.services.ec2.Ec2Client;
44 import software.amazon.awssdk.services.ec2.model.CopySnapshotRequest;
45 import software.amazon.awssdk.services.ec2.transform.CopySnapshotRequestMarshaller;
46 import software.amazon.awssdk.utils.CompletableFutureUtils;
47 
48 /**
49  * ExecutionInterceptor that generates a pre-signed URL for copying encrypted snapshots
50  */
51 @SdkInternalApi
52 public final class GeneratePreSignUrlInterceptor implements ExecutionInterceptor {
53 
54     private static final URI CUSTOM_ENDPOINT_LOCALHOST = URI.create("http://localhost");
55 
56     private static final AwsEc2ProtocolFactory PROTOCOL_FACTORY = AwsEc2ProtocolFactory
57         .builder()
58         // Need an endpoint to marshall but this will be overwritten in modifyHttpRequest
59         .clientConfiguration(SdkClientConfiguration.builder()
60                                                    .option(SdkClientOption.ENDPOINT, CUSTOM_ENDPOINT_LOCALHOST)
61                                                    .build())
62         .build();
63 
64     private static final CopySnapshotRequestMarshaller MARSHALLER = new CopySnapshotRequestMarshaller(PROTOCOL_FACTORY);
65 
66     private final Clock testClock; // for testing only
67 
GeneratePreSignUrlInterceptor()68     public GeneratePreSignUrlInterceptor() {
69         testClock = null;
70     }
71 
72     @SdkTestInternalApi
GeneratePreSignUrlInterceptor(Clock testClock)73     GeneratePreSignUrlInterceptor(Clock testClock) {
74         this.testClock = testClock;
75     }
76 
77     @Override
modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes)78     public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
79         SdkHttpRequest request = context.httpRequest();
80         SdkRequest originalRequest = context.request();
81 
82         if (originalRequest instanceof CopySnapshotRequest) {
83 
84             CopySnapshotRequest originalCopySnapshotRequest = (CopySnapshotRequest) originalRequest;
85 
86             // Return if presigned url is already specified by the user.
87             if (originalCopySnapshotRequest.presignedUrl() != null) {
88                 return request;
89             }
90 
91             String serviceName = "ec2";
92 
93             // The source regions where the snapshot currently resides.
94             String sourceRegion = originalCopySnapshotRequest.sourceRegion();
95             String sourceSnapshotId = originalCopySnapshotRequest
96                     .sourceSnapshotId();
97 
98             /*
99              * The region where the snapshot has to be copied from the source.
100              * The original copy snap shot request will have the end point set
101              * as the destination region in the client before calling this
102              * request.
103              */
104             String destinationRegion = originalCopySnapshotRequest.destinationRegion();
105 
106             if (destinationRegion == null) {
107                 destinationRegion =
108                         AwsHostNameUtils.parseSigningRegion(request.host(), serviceName)
109                                         .orElseThrow(() -> new IllegalArgumentException("Could not determine region for " +
110                                                                                         request.host()))
111                                         .id();
112             }
113 
114             URI endPointSource = createEndpoint(sourceRegion, serviceName);
115 
116             SdkHttpFullRequest requestForPresigning = generateRequestForPresigning(
117                     sourceSnapshotId, sourceRegion, destinationRegion)
118                     .toBuilder()
119                     .uri(endPointSource)
120                     .method(SdkHttpMethod.GET)
121                     .build();
122 
123             Aws4Signer signer = Aws4Signer.create();
124             Aws4PresignerParams signingParams = getPresignerParams(executionAttributes, sourceRegion, serviceName);
125 
126             SdkHttpFullRequest presignedRequest = signer.presign(requestForPresigning, signingParams);
127 
128             return request.toBuilder()
129                           .putRawQueryParameter("DestinationRegion", destinationRegion)
130                           .putRawQueryParameter("PresignedUrl", presignedRequest.getUri().toString())
131                           .build();
132         }
133 
134         return request;
135     }
136 
getPresignerParams(ExecutionAttributes attributes, String signingRegion, String signingName)137     private Aws4PresignerParams getPresignerParams(ExecutionAttributes attributes, String signingRegion, String signingName) {
138         return Aws4PresignerParams.builder()
139                                   .signingRegion(Region.of(signingRegion))
140                                   .signingName(signingName)
141                                   .awsCredentials(resolveCredentials(attributes))
142                                   .signingClockOverride(testClock)
143                                   .build();
144     }
145 
146     // TODO(sra-identity-and-auth): add test case for SELECTED_AUTH_SCHEME case
resolveCredentials(ExecutionAttributes attributes)147     private AwsCredentials resolveCredentials(ExecutionAttributes attributes) {
148         return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME)
149                          .map(selectedAuthScheme -> selectedAuthScheme.identity())
150                          .map(identityFuture -> CompletableFutureUtils.joinLikeSync(identityFuture))
151                          .filter(identity -> identity instanceof AwsCredentialsIdentity)
152                          .map(identity -> {
153                              AwsCredentialsIdentity awsCredentialsIdentity = (AwsCredentialsIdentity) identity;
154                              return CredentialUtils.toCredentials(awsCredentialsIdentity);
155                          }).orElse(attributes.getAttribute(AWS_CREDENTIALS));
156     }
157 
158     /**
159      * Generates a Request object for the pre-signed URL.
160      */
generateRequestForPresigning(String sourceSnapshotId, String sourceRegion, String destinationRegion)161     private SdkHttpFullRequest generateRequestForPresigning(String sourceSnapshotId,
162                                                             String sourceRegion,
163                                                             String destinationRegion) {
164 
165         CopySnapshotRequest copySnapshotRequest = CopySnapshotRequest.builder()
166                                                                      .sourceSnapshotId(sourceSnapshotId)
167                                                                      .sourceRegion(sourceRegion)
168                                                                      .destinationRegion(destinationRegion)
169                                                                      .build();
170 
171         return MARSHALLER.marshall(copySnapshotRequest);
172     }
173 
createEndpoint(String regionName, String serviceName)174     private URI createEndpoint(String regionName, String serviceName) {
175 
176         Region region = Region.of(regionName);
177 
178         if (region == null) {
179             throw SdkClientException.builder()
180                                     .message("{" + serviceName + ", " + regionName + "} was not "
181                                              + "found in region metadata. Update to latest version of SDK and try again.")
182                                     .build();
183         }
184 
185         URI endpoint = Ec2Client.serviceMetadata().endpointFor(region);
186         if (endpoint.getScheme() == null) {
187             return URI.create("https://" + endpoint);
188         }
189         return endpoint;
190     }
191 }
192