Skip to content

Commit 7aefbb7

Browse files
authored
Merge pull request #1920 from dagnir/no-http-token
Only sign requests that have HTTPS endpoints
2 parents 73efc77 + 8567b62 commit 7aefbb7

File tree

5 files changed

+462
-0
lines changed

5 files changed

+462
-0
lines changed

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseAsyncClientHandler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,15 @@ private <InputT extends SdkRequest, OutputT extends SdkResponse, ReturnT> Comple
204204
clientConfiguration);
205205

206206
SdkHttpFullRequest marshalled = (SdkHttpFullRequest) finalizeSdkHttpRequestContext.httpRequest();
207+
208+
// Ensure that the signing configuration is still valid after the
209+
// request has been potentially transformed.
210+
try {
211+
validateSigningConfiguration(marshalled, executionContext.signer());
212+
} catch (Exception e) {
213+
return CompletableFutureUtils.failedFuture(e);
214+
}
215+
207216
Optional<RequestBody> requestBody = finalizeSdkHttpRequestContext.requestBody();
208217

209218
// For non-streaming requests, RequestBody can be modified in the interceptors. eg:

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
2828
import software.amazon.awssdk.core.client.config.SdkClientOption;
2929
import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
30+
import software.amazon.awssdk.core.exception.SdkClientException;
3031
import software.amazon.awssdk.core.http.ExecutionContext;
3132
import software.amazon.awssdk.core.http.HttpResponseHandler;
3233
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
@@ -36,10 +37,12 @@
3637
import software.amazon.awssdk.core.internal.InternalCoreExecutionAttribute;
3738
import software.amazon.awssdk.core.internal.util.MetricUtils;
3839
import software.amazon.awssdk.core.metrics.CoreMetric;
40+
import software.amazon.awssdk.core.signer.Signer;
3941
import software.amazon.awssdk.core.sync.RequestBody;
4042
import software.amazon.awssdk.http.ContentStreamProvider;
4143
import software.amazon.awssdk.http.SdkHttpFullRequest;
4244
import software.amazon.awssdk.http.SdkHttpFullResponse;
45+
import software.amazon.awssdk.http.SdkHttpRequest;
4346
import software.amazon.awssdk.metrics.MetricCollector;
4447
import software.amazon.awssdk.utils.Pair;
4548
import software.amazon.awssdk.utils.StringUtils;
@@ -211,6 +214,21 @@ protected boolean isCalculateCrc32FromCompressedData() {
211214
return clientConfiguration.option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED);
212215
}
213216

217+
protected void validateSigningConfiguration(SdkHttpRequest request, Signer signer) {
218+
if (signer == null) {
219+
return;
220+
}
221+
222+
if (signer.credentialType() != Signer.CredentialType.BEARER_TOKEN) {
223+
return;
224+
}
225+
226+
URI endpoint = request.getUri();
227+
if (!"https".equals(endpoint.getScheme())) {
228+
throw SdkClientException.create("Cannot use bearer token signer with a plaintext HTTP endpoint: " + endpoint);
229+
}
230+
}
231+
214232
/**
215233
* Decorate response handlers by running after unmarshalling Interceptors and adding http response metadata.
216234
*/

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ private <InputT extends SdkRequest, OutputT, ReturnT> ReturnT doExecute(
155155

156156
SdkHttpFullRequest marshalled = (SdkHttpFullRequest) sdkHttpFullRequestContext.httpRequest();
157157

158+
// Ensure that the signing configuration is still valid after the
159+
// request has been potentially transformed.
160+
validateSigningConfiguration(marshalled, executionContext.signer());
161+
158162
// TODO Pass requestBody as separate arg to invoke
159163
Optional<RequestBody> requestBody = sdkHttpFullRequestContext.requestBody();
160164

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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.core.client.handler;
17+
18+
import static org.assertj.core.api.Assertions.assertThatNoException;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.mockito.Matchers.any;
21+
import static org.mockito.Mockito.when;
22+
23+
import java.util.Arrays;
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.concurrent.CompletableFuture;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.mockito.ArgumentCaptor;
33+
import org.mockito.Mock;
34+
import org.mockito.runners.MockitoJUnitRunner;
35+
import software.amazon.awssdk.core.SdkRequest;
36+
import software.amazon.awssdk.core.SdkResponse;
37+
import software.amazon.awssdk.core.async.EmptyPublisher;
38+
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
39+
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
40+
import software.amazon.awssdk.core.client.config.SdkClientOption;
41+
import software.amazon.awssdk.core.exception.SdkClientException;
42+
import software.amazon.awssdk.core.http.HttpResponseHandler;
43+
import software.amazon.awssdk.core.interceptor.Context;
44+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
45+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
46+
import software.amazon.awssdk.core.protocol.VoidSdkResponse;
47+
import software.amazon.awssdk.core.runtime.transform.Marshaller;
48+
import software.amazon.awssdk.core.signer.Signer;
49+
import software.amazon.awssdk.http.SdkHttpFullRequest;
50+
import software.amazon.awssdk.http.SdkHttpFullResponse;
51+
import software.amazon.awssdk.http.SdkHttpRequest;
52+
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
53+
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
54+
import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;
55+
import utils.HttpTestUtils;
56+
import utils.ValidSdkObjects;
57+
58+
@RunWith(MockitoJUnitRunner.class)
59+
public class AsyncClientHandlerSignerValidationTest {
60+
private final SdkRequest request = ValidSdkObjects.sdkRequest();
61+
62+
@Mock
63+
private Marshaller<SdkRequest> marshaller;
64+
65+
@Mock
66+
private SdkAsyncHttpClient httpClient;
67+
68+
@Mock
69+
private Signer signer;
70+
71+
@Mock
72+
private HttpResponseHandler<SdkResponse> responseHandler;
73+
74+
private CompletableFuture<Void> httpClientFuture = CompletableFuture.completedFuture(null);
75+
76+
private ArgumentCaptor<AsyncExecuteRequest> executeRequestCaptor = ArgumentCaptor.forClass(AsyncExecuteRequest.class);
77+
78+
@Before
79+
public void setup() {
80+
when(httpClient.execute(executeRequestCaptor.capture())).thenReturn(httpClientFuture);
81+
when(signer.credentialType()).thenReturn(Signer.CredentialType.BEARER_TOKEN);
82+
}
83+
84+
@Test
85+
public void execute_requestHasHttpEndpoint_usesBearerAuth_fails() {
86+
SdkHttpFullRequest httpRequest = ValidSdkObjects.sdkHttpFullRequest().protocol("http").build();
87+
when(marshaller.marshall(any(SdkRequest.class))).thenReturn(httpRequest);
88+
89+
SdkClientConfiguration config = testClientConfiguration();
90+
91+
SdkAsyncClientHandler sdkAsyncClientHandler = new SdkAsyncClientHandler(config);
92+
CompletableFuture<SdkResponse> execute = sdkAsyncClientHandler.execute(executionParams());
93+
94+
assertThatThrownBy(execute::get)
95+
.hasCauseInstanceOf(SdkClientException.class)
96+
.hasMessageContaining("plaintext HTTP endpoint");
97+
}
98+
99+
@Test
100+
public void execute_interceptorChangesToHttp_usesBearerAuth_fails() {
101+
SdkHttpFullRequest httpRequest = ValidSdkObjects.sdkHttpFullRequest().protocol("https").build();
102+
when(marshaller.marshall(any(SdkRequest.class))).thenReturn(httpRequest);
103+
104+
ExecutionInterceptor interceptor = new ExecutionInterceptor() {
105+
@Override
106+
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
107+
return context.httpRequest()
108+
.toBuilder()
109+
.protocol("http")
110+
.build();
111+
}
112+
};
113+
114+
SdkClientConfiguration config = testClientConfiguration()
115+
.toBuilder()
116+
.option(SdkClientOption.EXECUTION_INTERCEPTORS, Collections.singletonList(interceptor))
117+
.build();
118+
119+
SdkAsyncClientHandler sdkAsyncClientHandler = new SdkAsyncClientHandler(config);
120+
121+
CompletableFuture<SdkResponse> execute = sdkAsyncClientHandler.execute(executionParams());
122+
123+
assertThatThrownBy(execute::get)
124+
.hasCauseInstanceOf(SdkClientException.class)
125+
.hasMessageContaining("plaintext HTTP endpoint");
126+
}
127+
128+
@Test
129+
public void execute_interceptorChangesToHttps_usesBearerAuth_succeeds() throws Exception {
130+
SdkHttpFullRequest httpRequest = ValidSdkObjects.sdkHttpFullRequest().protocol("http").build();
131+
132+
when(marshaller.marshall(any(SdkRequest.class))).thenReturn(httpRequest);
133+
when(signer.sign(any(), any())).thenReturn(httpRequest);
134+
135+
SdkResponse mockSdkResponse = VoidSdkResponse.builder().build();
136+
when(responseHandler.handle(any(), any())).thenReturn(mockSdkResponse);
137+
138+
ExecutionInterceptor interceptor = new ExecutionInterceptor() {
139+
@Override
140+
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
141+
return context.httpRequest()
142+
.toBuilder()
143+
.protocol("https")
144+
.build();
145+
}
146+
};
147+
148+
SdkClientConfiguration config = testClientConfiguration()
149+
.toBuilder()
150+
.option(SdkClientOption.EXECUTION_INTERCEPTORS, Collections.singletonList(interceptor))
151+
.build();
152+
153+
SdkAsyncClientHandler sdkAsyncClientHandler = new SdkAsyncClientHandler(config);
154+
CompletableFuture<SdkResponse> execute = sdkAsyncClientHandler.execute(executionParams());
155+
156+
SdkAsyncHttpResponseHandler capturedHandler = executeRequestCaptor.getValue().responseHandler();
157+
Map<String, List<String>> headers = new HashMap<>();
158+
headers.put("foo", Arrays.asList("bar"));
159+
capturedHandler.onHeaders(SdkHttpFullResponse.builder()
160+
.statusCode(200)
161+
.headers(headers)
162+
.build());
163+
capturedHandler.onStream(new EmptyPublisher<>());
164+
165+
assertThatNoException().isThrownBy(execute::get);
166+
}
167+
168+
@Test
169+
public void execute_requestHasHttpsEndpoint_usesBearerAuth_succeeds() throws Exception {
170+
SdkHttpFullRequest httpRequest = ValidSdkObjects.sdkHttpFullRequest().protocol("https").build();
171+
172+
when(marshaller.marshall(any(SdkRequest.class))).thenReturn(httpRequest);
173+
when(signer.sign(any(), any())).thenReturn(httpRequest);
174+
175+
176+
SdkResponse mockSdkResponse = VoidSdkResponse.builder().build();
177+
when(responseHandler.handle(any(), any())).thenReturn(mockSdkResponse);
178+
179+
SdkAsyncClientHandler sdkAsyncClientHandler = new SdkAsyncClientHandler(testClientConfiguration());
180+
CompletableFuture<SdkResponse> execute = sdkAsyncClientHandler.execute(executionParams());
181+
182+
SdkAsyncHttpResponseHandler capturedHandler = executeRequestCaptor.getValue().responseHandler();
183+
Map<String, List<String>> headers = new HashMap<>();
184+
headers.put("foo", Arrays.asList("bar"));
185+
capturedHandler.onHeaders(SdkHttpFullResponse.builder()
186+
.statusCode(200)
187+
.headers(headers)
188+
.build());
189+
capturedHandler.onStream(new EmptyPublisher<>());
190+
191+
assertThatNoException().isThrownBy(execute::get);
192+
}
193+
194+
@Test
195+
public void execute_requestHasHttpEndpoint_doesNotBearerAuth_succeeds() throws Exception {
196+
SdkHttpFullRequest httpRequest = ValidSdkObjects.sdkHttpFullRequest().protocol("http").build();
197+
198+
when(marshaller.marshall(any(SdkRequest.class))).thenReturn(httpRequest);
199+
when(signer.credentialType()).thenReturn(Signer.CredentialType.AWS);
200+
when(signer.sign(any(), any())).thenReturn(httpRequest);
201+
202+
203+
SdkResponse mockSdkResponse = VoidSdkResponse.builder().build();
204+
when(responseHandler.handle(any(), any())).thenReturn(mockSdkResponse);
205+
206+
SdkAsyncClientHandler sdkAsyncClientHandler = new SdkAsyncClientHandler(testClientConfiguration());
207+
CompletableFuture<SdkResponse> execute = sdkAsyncClientHandler.execute(executionParams());
208+
209+
SdkAsyncHttpResponseHandler capturedHandler = executeRequestCaptor.getValue().responseHandler();
210+
Map<String, List<String>> headers = new HashMap<>();
211+
headers.put("foo", Arrays.asList("bar"));
212+
capturedHandler.onHeaders(SdkHttpFullResponse.builder()
213+
.statusCode(200)
214+
.headers(headers)
215+
.build());
216+
capturedHandler.onStream(new EmptyPublisher<>());
217+
218+
assertThatNoException().isThrownBy(execute::get);
219+
}
220+
221+
private ClientExecutionParams<SdkRequest, SdkResponse> executionParams() {
222+
return new ClientExecutionParams<SdkRequest, SdkResponse>()
223+
.withInput(request)
224+
.withMarshaller(marshaller)
225+
.withResponseHandler(responseHandler);
226+
}
227+
228+
private SdkClientConfiguration testClientConfiguration() {
229+
return HttpTestUtils.testClientConfiguration()
230+
.toBuilder()
231+
.option(SdkClientOption.ASYNC_HTTP_CLIENT, httpClient)
232+
.option(SdkAdvancedClientOption.SIGNER, signer)
233+
.build();
234+
}
235+
}

0 commit comments

Comments
 (0)