Skip to content

Commit 7f0dddf

Browse files
vsudilovVladimir Sudilovskyculldanx
authored
SigV4: Add host header only when not already provided (#5608)
* SigV4: Add host header only when not already provided * http-client: respect user host header --------- Co-authored-by: Vladimir Sudilovsky <[email protected]> Co-authored-by: Daniel Cullen <[email protected]>
1 parent 97ee691 commit 7f0dddf

File tree

7 files changed

+113
-3
lines changed

7 files changed

+113
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "vsudilov",
5+
"description": "SigV4: Add host header only when not already provided"
6+
}

core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerUtils.java

+7
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ public static void addHostHeader(SdkHttpRequest.Builder requestBuilder) {
171171
// AWS4 requires that we sign the Host header, so we
172172
// have to have it in the request by the time we sign.
173173

174+
// If the SdkHttpRequest has an associated Host header
175+
// already set, prefer to use that.
176+
177+
if (requestBuilder.headers().get(SignerConstant.HOST) != null) {
178+
return;
179+
}
180+
174181
String host = requestBuilder.host();
175182
if (!SdkHttpUtils.isUsingStandardPort(requestBuilder.protocol(), requestBuilder.port())) {
176183
StringBuilder hostHeaderBuilder = new StringBuilder(host);

core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSignerTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.junit.jupiter.api.Test;
3131
import software.amazon.awssdk.http.SdkHttpMethod;
3232
import software.amazon.awssdk.http.SdkHttpRequest;
33+
import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant;
3334
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
3435
import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity;
3536

@@ -58,6 +59,7 @@ public void sign_computesSigningResult() {
5859
assertEquals(expectedCanonicalRequestString, result.getCanonicalRequest().getCanonicalRequestString());
5960
}
6061

62+
6163
@Test
6264
public void sign_withHeader_addsAuthHeaders() {
6365
String expectedAuthorization = "AWS4-HMAC-SHA256 Credential=access/19700101/us-east-1/demo/aws4_request, " +
@@ -82,6 +84,21 @@ public void sign_withHeaderAndSessionCredentials_addsAuthHeadersAndTokenHeader()
8284
assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Security-Token")).hasValue("token");
8385
}
8486

87+
@Test
88+
public void sign_withHeaderAndSessionCredentials_correctSigningUsingProvidedHostHeader() {
89+
String expectedAuthorization = "AWS4-HMAC-SHA256 Credential=access/19700101/us-east-1/demo/aws4_request, " +
90+
"SignedHeaders=host;x-amz-archive-description;x-amz-content-sha256;x-amz-date;"
91+
+ "x-amz-security-token, " +
92+
"Signature=c8228e7bef8a72a450df38e6e935ce61fdb8989670b41d97cfc20d04bb76b10a";
93+
SdkHttpRequest.Builder request = getRequest().putHeader(SignerConstant.HOST, "virtual-host.localhost");
94+
V4RequestSigningResult result = header(getProperties(sessionCreds)).sign(request);
95+
96+
assertThat(result.getSignedRequest().firstMatchingHeader("Host")).hasValue("virtual-host.localhost");
97+
assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Date")).hasValue("19700101T000000Z");
98+
assertThat(result.getSignedRequest().firstMatchingHeader("Authorization")).hasValue(expectedAuthorization);
99+
assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Security-Token")).hasValue("token");
100+
}
101+
85102
@Test
86103
public void sign_withQuery_addsAuthQueryParams() {
87104
V4RequestSigningResult result = query(getProperties(creds)).sign(getRequest());

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/impl/ApacheHttpRequestFactory.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.URI;
2121
import java.util.Arrays;
2222
import java.util.List;
23+
import java.util.Optional;
2324
import org.apache.http.HttpEntity;
2425
import org.apache.http.HttpHeaders;
2526
import org.apache.http.client.config.RequestConfig;
@@ -55,7 +56,6 @@ public HttpRequestBase create(final HttpExecuteRequest request, final ApacheHttp
5556
HttpRequestBase base = createApacheRequest(request, sanitizeUri(request.httpRequest()));
5657
addHeadersToRequest(base, request.httpRequest());
5758
addRequestConfig(base, request.httpRequest(), requestConfig);
58-
5959
return base;
6060
}
6161

@@ -172,7 +172,7 @@ private void addHeadersToRequest(HttpRequestBase httpRequest, SdkHttpRequest req
172172
// it's already present, so we skip it here. We also skip the Host
173173
// header to avoid sending it twice, which will interfere with some
174174
// signing schemes.
175-
if (!IGNORE_HEADERS.contains(name)) {
175+
if (IGNORE_HEADERS.stream().noneMatch(name::equalsIgnoreCase)) {
176176
for (String headerValue : value) {
177177
httpRequest.addHeader(name, headerValue);
178178
}
@@ -181,6 +181,11 @@ private void addHeadersToRequest(HttpRequestBase httpRequest, SdkHttpRequest req
181181
}
182182

183183
private String getHostHeaderValue(SdkHttpRequest request) {
184+
// Respect any user-specified Host header when present
185+
Optional<String> existingHostHeader = request.firstMatchingHeader(HttpHeaders.HOST);
186+
if (existingHostHeader.isPresent()) {
187+
return existingHostHeader.get();
188+
}
184189
// Apache doesn't allow us to include the port in the host header if it's a standard port for that protocol. For that
185190
// reason, we don't include the port when we sign the message. See {@link SdkHttpRequest#port()}.
186191
return !SdkHttpUtils.isUsingStandardPort(request.protocol(), request.port())

http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/internal/impl/ApacheHttpRequestFactoryTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,46 @@ public void createSetsHostHeaderByDefault() {
7171
assertEquals("localhost:12345", hostHeaders[0].getValue());
7272
}
7373

74+
@Test
75+
public void createRespectsUserHostHeader() {
76+
String hostOverride = "virtual.host:123";
77+
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()
78+
.uri(URI.create("http://localhost:12345/"))
79+
.method(SdkHttpMethod.HEAD)
80+
.putHeader("Host", hostOverride)
81+
.build();
82+
HttpExecuteRequest request = HttpExecuteRequest.builder()
83+
.request(sdkRequest)
84+
.build();
85+
86+
HttpRequestBase result = instance.create(request, requestConfig);
87+
88+
Header[] hostHeaders = result.getHeaders(HttpHeaders.HOST);
89+
assertNotNull(hostHeaders);
90+
assertEquals(1, hostHeaders.length);
91+
assertEquals(hostOverride, hostHeaders[0].getValue());
92+
}
93+
94+
@Test
95+
public void createRespectsLowercaseUserHostHeader() {
96+
String hostOverride = "virtual.host:123";
97+
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()
98+
.uri(URI.create("http://localhost:12345/"))
99+
.method(SdkHttpMethod.HEAD)
100+
.putHeader("host", hostOverride)
101+
.build();
102+
HttpExecuteRequest request = HttpExecuteRequest.builder()
103+
.request(sdkRequest)
104+
.build();
105+
106+
HttpRequestBase result = instance.create(request, requestConfig);
107+
108+
Header[] hostHeaders = result.getHeaders(HttpHeaders.HOST);
109+
assertNotNull(hostHeaders);
110+
assertEquals(1, hostHeaders.length);
111+
assertEquals(hostOverride, hostHeaders[0].getValue());
112+
}
113+
74114
@Test
75115
public void putRequest_withTransferEncodingChunked_isChunkedAndDoesNotIncludeHeader() {
76116
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/RequestAdapter.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames;
2525
import java.util.Collections;
2626
import java.util.List;
27+
import java.util.Optional;
2728
import software.amazon.awssdk.annotations.SdkInternalApi;
2829
import software.amazon.awssdk.http.Protocol;
2930
import software.amazon.awssdk.http.SdkHttpMethod;
@@ -87,13 +88,19 @@ private void addHeadersToRequest(DefaultHttpRequest httpRequest, SdkHttpRequest
8788
// Copy over any other headers already in our request
8889
request.forEachHeader((name, value) -> {
8990
// Skip the Host header to avoid sending it twice, which will interfere with some signing schemes.
90-
if (!IGNORE_HEADERS.contains(name)) {
91+
if (IGNORE_HEADERS.stream().noneMatch(name::equalsIgnoreCase)) {
9192
value.forEach(h -> httpRequest.headers().add(name, h));
9293
}
9394
});
9495
}
9596

9697
private String getHostHeaderValue(SdkHttpRequest request) {
98+
// Respect any user-specified Host header when present
99+
Optional<String> existingHostHeader = request.firstMatchingHeader(HOST);
100+
if (existingHostHeader.isPresent()) {
101+
return existingHostHeader.get();
102+
}
103+
97104
return SdkHttpUtils.isUsingStandardPort(request.protocol(), request.port())
98105
? request.host()
99106
: request.host() + ":" + request.port();

http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/RequestAdapterTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,34 @@ public void adapt_hostHeaderSet() {
105105
assertThat(hostHeaders).containsExactly("localhost:12345");
106106
}
107107

108+
@Test
109+
public void adapt_keepsUserHostHeader() {
110+
String hostOverride = "virtual.host:123";
111+
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()
112+
.uri(URI.create("http://localhost:12345/"))
113+
.method(SdkHttpMethod.HEAD)
114+
.putHeader("Host", hostOverride)
115+
.build();
116+
HttpRequest result = h1Adapter.adapt(sdkRequest);
117+
List<String> hostHeaders = result.headers()
118+
.getAll(HttpHeaderNames.HOST.toString());
119+
assertThat(hostHeaders).containsExactly(hostOverride);
120+
}
121+
122+
@Test
123+
public void adapt_keepsLowercaseUserHostHeader() {
124+
String hostOverride = "virtual.host:123";
125+
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()
126+
.uri(URI.create("http://localhost:12345/"))
127+
.method(SdkHttpMethod.HEAD)
128+
.putHeader("host", hostOverride)
129+
.build();
130+
HttpRequest result = h1Adapter.adapt(sdkRequest);
131+
List<String> hostHeaders = result.headers()
132+
.getAll(HttpHeaderNames.HOST.toString());
133+
assertThat(hostHeaders).containsExactly(hostOverride);
134+
}
135+
108136
@Test
109137
public void adapt_standardHttpsPort_omittedInHeader() {
110138
SdkHttpRequest sdkRequest = SdkHttpRequest.builder()

0 commit comments

Comments
 (0)