Skip to content

Commit 93d67b6

Browse files
committed
Use forwarding proxy mode for plaintext endpoints in CRT S3 client
The CRT derives the proxy connection type from whether TLS options were supplied for the main connection, not from the scheme actually in use. Because S3NativeClientConfiguration always builds a TlsContext and S3CrtAsyncHttpClient passes it to the native client unconditionally, a plaintext http:// endpoint was still reached through a CONNECT tunnel. Proxies that only support forwarding mode reject those requests. Pin the proxy connection type to Forwarding when endpointOverride uses the http scheme, mirroring the scheme check that the CRT's own HttpClientConnectionManager already performs before handing a TLS context to the native layer. Only the combination of a configured proxy and a plaintext endpoint override is affected; https endpoints, endpoints without an override, and proxy-less configurations continue to resolve through Legacy as before. Not passing the TlsContext was evaluated first and does not work: aws-c-s3 still tunnels, so the connection type has to be set explicitly. Fixes #7320
1 parent a06a1a6 commit 93d67b6

3 files changed

Lines changed: 74 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS CRT-based S3 Client",
4+
"contributor": "MingWangSong",
5+
"description": "Fixed an issue where the CRT-based S3 client always established proxy connections through a `CONNECT` tunnel, even when `endpointOverride` used the plaintext `http://` scheme. Proxies that only support forwarding mode rejected these requests. The proxy connection type is now pinned to forwarding for plaintext endpoints, matching the behavior of `ApacheHttpClient`. See [#7320](https://github.com/aws/aws-sdk-java-v2/issues/7320)."
6+
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfiguration.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class S3NativeClientConfiguration implements SdkAutoCloseable {
4747
static final long DEFAULT_PART_SIZE_IN_BYTES = 8L * 1024 * 1024;
4848
private static final Logger log = Logger.loggerFor(S3NativeClientConfiguration.class);
4949
private static final long DEFAULT_TARGET_THROUGHPUT_IN_GBPS = 10;
50+
private static final String HTTP_SCHEME = "http";
5051

5152
private final String signingRegion;
5253
private final StandardRetryOptions standardRetryOptions;
@@ -106,6 +107,7 @@ public S3NativeClientConfiguration(Builder builder) {
106107

107108
if (builder.httpConfiguration != null) {
108109
this.proxyOptions = resolveProxy(builder.httpConfiguration.proxyConfiguration(), tlsContext).orElse(null);
110+
applyPlaintextEndpointProxyConnectionType(this.proxyOptions, this.endpointOverride);
109111
this.connectionTimeout = builder.httpConfiguration.connectionTimeout();
110112
this.httpMonitoringOptions =
111113
resolveHttpMonitoringOptions(builder.httpConfiguration.healthConfiguration()).orElse(null);
@@ -135,6 +137,24 @@ private long resolveThresholdInBytes(Builder builder) {
135137
return this.partSizeInBytes == null ? DEFAULT_PART_SIZE_IN_BYTES : this.partSizeInBytes;
136138
}
137139

140+
/**
141+
* Pins the proxy connection type to forwarding when the endpoint is plaintext.
142+
*
143+
* <p>The CRT derives the proxy connection type from whether TLS options were supplied for the main connection,
144+
* not from the scheme actually in use. Because this client is always constructed with a {@link TlsContext}, a
145+
* plaintext {@code http://} endpoint would otherwise be reached through a {@code CONNECT} tunnel, which
146+
* forwarding-only proxies reject. This mirrors the scheme check that the CRT's own
147+
* {@code HttpClientConnectionManager} performs before handing a TLS context to the native layer.
148+
*/
149+
private static void applyPlaintextEndpointProxyConnectionType(HttpProxyOptions proxyOptions, URI endpointOverride) {
150+
if (proxyOptions == null || endpointOverride == null) {
151+
return;
152+
}
153+
if (HTTP_SCHEME.equalsIgnoreCase(endpointOverride.getScheme())) {
154+
proxyOptions.setConnectionType(HttpProxyOptions.HttpProxyConnectionType.Forwarding);
155+
}
156+
}
157+
138158
private static Boolean resolveUseEnvironmentVariableValues(Builder builder) {
139159
if (builder != null && builder.httpConfiguration != null && builder.httpConfiguration.proxyConfiguration() != null) {
140160
return builder.httpConfiguration.proxyConfiguration().isUseEnvironmentVariableValues();

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfigurationTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919

20+
import java.net.URI;
2021
import java.util.stream.Stream;
2122
import org.junit.jupiter.api.Test;
2223
import org.junit.jupiter.params.ParameterizedTest;
2324
import org.junit.jupiter.params.provider.Arguments;
2425
import org.junit.jupiter.params.provider.MethodSource;
2526
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
2627
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
28+
import software.amazon.awssdk.crt.http.HttpProxyOptions;
29+
import software.amazon.awssdk.services.s3.crt.S3CrtProxyConfiguration;
2730
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
2831
import software.amazon.awssdk.testutils.LogCaptor;
2932

@@ -61,7 +64,49 @@ private static Stream<Arguments> noWarningConfigurations() {
6164
);
6265
}
6366

67+
@ParameterizedTest
68+
@MethodSource("proxyConnectionTypeCases")
69+
void build_proxyConnectionType_followsEndpointScheme(URI endpointOverride,
70+
HttpProxyOptions.HttpProxyConnectionType expectedType) {
71+
S3CrtHttpConfiguration httpConfig =
72+
S3CrtHttpConfiguration.builder()
73+
.proxyConfiguration(S3CrtProxyConfiguration.builder()
74+
.scheme("http")
75+
.host("localhost")
76+
.port(8888)
77+
.build())
78+
.build();
79+
80+
try (S3NativeClientConfiguration config = buildConfig(httpConfig, endpointOverride)) {
81+
assertThat(config.proxyOptions().getConnectionType()).isEqualTo(expectedType);
82+
}
83+
}
84+
85+
private static Stream<Arguments> proxyConnectionTypeCases() {
86+
return Stream.of(
87+
Arguments.of(URI.create("http://localhost:9000"),
88+
HttpProxyOptions.HttpProxyConnectionType.Forwarding),
89+
Arguments.of(URI.create("HTTP://localhost:9000"),
90+
HttpProxyOptions.HttpProxyConnectionType.Forwarding),
91+
Arguments.of(URI.create("https://s3.us-east-1.amazonaws.com"),
92+
HttpProxyOptions.HttpProxyConnectionType.Legacy),
93+
Arguments.of(null, HttpProxyOptions.HttpProxyConnectionType.Legacy)
94+
);
95+
}
96+
97+
@Test
98+
void build_whenPlaintextEndpointAndNoProxy_shouldNotSetProxyOptions() {
99+
try (S3NativeClientConfiguration config = buildConfig(S3CrtHttpConfiguration.builder().build(),
100+
URI.create("http://localhost:9000"))) {
101+
assertThat(config.proxyOptions()).isNull();
102+
}
103+
}
104+
64105
private S3NativeClientConfiguration buildConfig(S3CrtHttpConfiguration httpConfig) {
106+
return buildConfig(httpConfig, null);
107+
}
108+
109+
private S3NativeClientConfiguration buildConfig(S3CrtHttpConfiguration httpConfig, URI endpointOverride) {
65110
S3NativeClientConfiguration.Builder builder =
66111
S3NativeClientConfiguration.builder()
67112
.signingRegion("us-east-1")
@@ -71,6 +116,9 @@ private S3NativeClientConfiguration buildConfig(S3CrtHttpConfiguration httpConfi
71116
if (httpConfig != null) {
72117
builder.httpConfiguration(httpConfig);
73118
}
119+
if (endpointOverride != null) {
120+
builder.endpointOverride(endpointOverride);
121+
}
74122
return builder.build();
75123
}
76124
}

0 commit comments

Comments
 (0)