Skip to content

Commit 926b67b

Browse files
committed
Enable client TLS auth for Netty
This commit enables customers to configure a TlsKeyManagersProvider on the Netty client which will be used to authenticate the client during client TLS authentication.
1 parent 313c15e commit 926b67b

File tree

20 files changed

+689
-17
lines changed

20 files changed

+689
-17
lines changed

build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,11 @@
143143
</Or>
144144
<Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
145145
</Match>
146+
147+
<!-- See SpotBugs bug: https://github.com/spotbugs/spotbugs/issues/600, https://github.com/spotbugs/spotbugs/issues/756 -->
148+
<Match>
149+
<Class name="software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider"/>
150+
<Method name="createKeyStore"/>
151+
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
152+
</Match>
146153
</FindBugsFilter>

http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
9797
public static final SdkHttpConfigurationOption<Boolean> REAP_IDLE_CONNECTIONS =
9898
new SdkHttpConfigurationOption<>("ReapIdleConnections", Boolean.class);
9999

100+
/**
101+
* The {@link TlsKeyManagersProvider} that will be used by the HTTP client when authenticating with a
102+
* TLS host.
103+
*/
104+
public static final SdkHttpConfigurationOption<TlsKeyManagersProvider> TLS_KEY_MANAGERS_PROVIDER =
105+
new SdkHttpConfigurationOption<>("TlsKeyManagersProvider", TlsKeyManagersProvider.class);
106+
100107
private static final Duration DEFAULT_SOCKET_READ_TIMEOUT = Duration.ofSeconds(30);
101108
private static final Duration DEFAULT_SOCKET_WRITE_TIMEOUT = Duration.ofSeconds(30);
102109
private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(2);
@@ -110,6 +117,8 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
110117

111118
private static final Protocol DEFAULT_PROTOCOL = Protocol.HTTP1_1;
112119

120+
private static final TlsKeyManagersProvider DEFAULT_TLS_KEY_MANAGERS_PROVIDER = SystemPropertyTlsKeyManagersProvider.create();
121+
113122
public static final AttributeMap GLOBAL_HTTP_DEFAULTS = AttributeMap
114123
.builder()
115124
.put(READ_TIMEOUT, DEFAULT_SOCKET_READ_TIMEOUT)
@@ -123,6 +132,7 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
123132
.put(PROTOCOL, DEFAULT_PROTOCOL)
124133
.put(TRUST_ALL_CERTIFICATES, DEFAULT_TRUST_ALL_CERTIFICATES)
125134
.put(REAP_IDLE_CONNECTIONS, DEFAULT_REAP_IDLE_CONNECTIONS)
135+
.put(TLS_KEY_MANAGERS_PROVIDER, DEFAULT_TLS_KEY_MANAGERS_PROVIDER)
126136
.build();
127137

128138
private final String name;

http-client-spi/src/main/java/software/amazon/awssdk/http/TlsKeyManagersProvider.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import javax.net.ssl.KeyManager;
1919
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;
2021

2122
/**
2223
* Provider for the {@link KeyManager key managers} to be used by the SDK when
@@ -31,4 +32,11 @@ public interface TlsKeyManagersProvider {
3132
* @return The {@link KeyManager}s, or {@code null}.
3233
*/
3334
KeyManager[] keyManagers();
35+
36+
/**
37+
* @return A provider that returns a {@code null} array of {@link KeyManager}s.
38+
*/
39+
static TlsKeyManagersProvider noneProvider() {
40+
return NoneTlsKeyManagersProvider.getInstance();
41+
}
3442
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2010-2019 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.internal.http;
17+
18+
import javax.net.ssl.KeyManager;
19+
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.http.TlsKeyManagersProvider;
21+
22+
/**
23+
* Simple implementation of {@link TlsKeyManagersProvider} that return a null array.
24+
* <p>
25+
* Use this provider if you don't want the client to present any certificates to the remote TLS host.
26+
*/
27+
@SdkInternalApi
28+
public final class NoneTlsKeyManagersProvider implements TlsKeyManagersProvider {
29+
private static final NoneTlsKeyManagersProvider INSTANCE = new NoneTlsKeyManagersProvider();
30+
31+
private NoneTlsKeyManagersProvider() {
32+
}
33+
34+
@Override
35+
public KeyManager[] keyManagers() {
36+
return null;
37+
}
38+
39+
public static NoneTlsKeyManagersProvider getInstance() {
40+
return INSTANCE;
41+
}
42+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2010-2019 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.http;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import org.junit.Test;
20+
21+
public class TlsKeyManagersProviderTest {
22+
23+
@Test
24+
public void noneProvider_returnsProviderThatReturnsNull() {
25+
assertThat(TlsKeyManagersProvider.noneProvider().keyManagers()).isNull();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2010-2019 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.http.software.amazon.awssdk.internal.http;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import org.junit.Test;
20+
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;
21+
22+
public class NoneTlsKeyManagersProviderTest {
23+
@Test
24+
public void getInstance_returnsNonNull() {
25+
assertThat(NoneTlsKeyManagersProvider.getInstance()).isNotNull();
26+
}
27+
28+
@Test
29+
public void keyManagers_returnsNull() {
30+
assertThat(NoneTlsKeyManagersProvider.getInstance().keyManagers()).isNull();
31+
}
32+
33+
@Test
34+
public void getInstance_returnsSingletonInstance() {
35+
NoneTlsKeyManagersProvider provider1 = NoneTlsKeyManagersProvider.getInstance();
36+
NoneTlsKeyManagersProvider provider2 = NoneTlsKeyManagersProvider.getInstance();
37+
assertThat(provider1 == provider2).isTrue();
38+
}
39+
}

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/ApacheHttpClient.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_CONNECTIONS;
2727
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.READ_TIMEOUT;
2828
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS;
29+
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER;
2930
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;
3031

3132
import java.io.IOException;
@@ -381,6 +382,10 @@ public interface Builder extends SdkHttpClient.Builder<ApacheHttpClient.Builder>
381382
/**
382383
* Configure the {@link TlsKeyManagersProvider} that will provide the {@link javax.net.ssl.KeyManager}s to use
383384
* when constructing the SSL context.
385+
* <p>
386+
* The default used by the client will be {@link SystemPropertyTlsKeyManagersProvider}. Configure an instance of
387+
* {@link software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider} or another implementation of
388+
* {@link TlsKeyManagersProvider} to override it.
384389
*/
385390
Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider);
386391
}
@@ -392,7 +397,6 @@ private static final class DefaultBuilder implements Builder {
392397
private Boolean expectContinueEnabled;
393398
private HttpRoutePlanner httpRoutePlanner;
394399
private CredentialsProvider credentialsProvider;
395-
private TlsKeyManagersProvider tlsKeyManagersProvider = SystemPropertyTlsKeyManagersProvider.create();
396400

397401
private DefaultBuilder() {
398402
}
@@ -521,7 +525,7 @@ public Builder credentialsProvider(CredentialsProvider credentialsProvider) {
521525

522526
@Override
523527
public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
524-
this.tlsKeyManagersProvider = tlsKeyManagersProvider;
528+
standardOptions.put(TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
525529
return this;
526530
}
527531

@@ -565,7 +569,7 @@ public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder config
565569
private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration,
566570
AttributeMap standardOptions) {
567571
// TODO v2 custom socket factory
568-
return new SdkTlsSocketFactory(getSslContext(configuration.tlsKeyManagersProvider, standardOptions),
572+
return new SdkTlsSocketFactory(getSslContext(standardOptions),
569573
getHostNameVerifier(standardOptions));
570574
}
571575

@@ -575,18 +579,16 @@ private HostnameVerifier getHostNameVerifier(AttributeMap standardOptions) {
575579
: SSLConnectionSocketFactory.getDefaultHostnameVerifier();
576580
}
577581

578-
private SSLContext getSslContext(TlsKeyManagersProvider keyManagersProvider, AttributeMap standardOptions) {
582+
private SSLContext getSslContext(AttributeMap standardOptions) {
579583
TrustManager[] trustManagers = null;
580584
if (standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) {
581585
log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be "
582586
+ "used for testing.");
583587
trustManagers = trustAllTrustManager();
584588
}
585589

586-
KeyManager[] keyManagers = null;
587-
if (keyManagersProvider != null) {
588-
keyManagers = keyManagersProvider.keyManagers();
589-
}
590+
TlsKeyManagersProvider provider = standardOptions.get(TLS_KEY_MANAGERS_PROVIDER);
591+
KeyManager[] keyManagers = provider.keyManagers();
590592

591593
try {
592594
SSLContext sslcontext = SSLContext.getInstance("TLS");

http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheClientTlsAuthTest.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
2222
import static org.assertj.core.api.Assertions.assertThat;
2323
import static org.assertj.core.api.Assertions.fail;
24+
import static org.hamcrest.Matchers.anyOf;
25+
import static org.hamcrest.Matchers.instanceOf;
2426
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
2527
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
2628
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;
@@ -30,11 +32,14 @@
3032
import java.net.URI;
3133
import javax.net.ssl.SSLException;
3234
import org.apache.http.NoHttpResponseException;
35+
import org.hamcrest.Matchers;
3336
import org.junit.After;
3437
import org.junit.AfterClass;
3538
import org.junit.Before;
3639
import org.junit.BeforeClass;
40+
import org.junit.Rule;
3741
import org.junit.Test;
42+
import org.junit.rules.ExpectedException;
3843
import software.amazon.awssdk.http.FileStoreTlsKeyManagersProvider;
3944
import software.amazon.awssdk.http.HttpExecuteRequest;
4045
import software.amazon.awssdk.http.HttpExecuteResponse;
@@ -43,6 +48,7 @@
4348
import software.amazon.awssdk.http.SdkHttpMethod;
4449
import software.amazon.awssdk.http.SdkHttpRequest;
4550
import software.amazon.awssdk.http.TlsKeyManagersProvider;
51+
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;
4652

4753
/**
4854
* Tests to ensure that {@link ApacheHttpClient} can properly support TLS
@@ -53,6 +59,9 @@ public class ApacheClientTlsAuthTest extends ClientTlsAuthTestBase {
5359
private static TlsKeyManagersProvider keyManagersProvider;
5460
private SdkHttpClient client;
5561

62+
@Rule
63+
public ExpectedException thrown = ExpectedException.none();
64+
5665
@BeforeClass
5766
public static void setUp() throws IOException {
5867
ClientTlsAuthTestBase.setUp();
@@ -108,14 +117,9 @@ public void canMakeHttpsRequestWhenKeyProviderConfigured() throws IOException {
108117

109118
@Test
110119
public void requestFailsWhenKeyProviderNotConfigured() throws IOException {
111-
client = ApacheHttpClient.builder().build();
112-
try {
113-
makeRequestWithHttpClient(client);
114-
fail("HTTP request should have failed");
115-
} catch (NoHttpResponseException | SSLException | SocketException expected) {
116-
// The client doesn't seem to consistently throw a single error,
117-
// and can also vary depending on actual JVM used.
118-
}
120+
thrown.expect(anyOf(instanceOf(NoHttpResponseException.class), instanceOf(SSLException.class)));
121+
client = ApacheHttpClient.builder().tlsKeyManagersProvider(NoneTlsKeyManagersProvider.getInstance()).build();
122+
makeRequestWithHttpClient(client);
119123
}
120124

121125
@Test
@@ -151,6 +155,22 @@ public void defaultTlsKeyManagersProviderIsSystemPropertyProvider() throws IOExc
151155
}
152156
}
153157

158+
@Test
159+
public void defaultTlsKeyManagersProviderIsSystemPropertyProvider_explicitlySetToNull() throws IOException {
160+
System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString());
161+
System.setProperty(SSL_KEY_STORE_TYPE.property(), CLIENT_STORE_TYPE);
162+
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), STORE_PASSWORD);
163+
164+
client = ApacheHttpClient.builder().tlsKeyManagersProvider(null).build();
165+
try {
166+
makeRequestWithHttpClient(client);
167+
} finally {
168+
System.clearProperty(SSL_KEY_STORE.property());
169+
System.clearProperty(SSL_KEY_STORE_TYPE.property());
170+
System.clearProperty(SSL_KEY_STORE_PASSWORD.property());
171+
}
172+
}
173+
154174
private HttpExecuteResponse makeRequestWithHttpClient(SdkHttpClient httpClient) throws IOException {
155175
SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
156176
.method(SdkHttpMethod.GET)

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_PENDING_CONNECTION_ACQUIRES;
2424
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.READ_TIMEOUT;
2525
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS;
26+
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER;
2627
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.WRITE_TIMEOUT;
2728
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS;
2829
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS;
@@ -48,6 +49,8 @@
4849
import software.amazon.awssdk.http.Protocol;
4950
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
5051
import software.amazon.awssdk.http.SdkHttpRequest;
52+
import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider;
53+
import software.amazon.awssdk.http.TlsKeyManagersProvider;
5154
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
5255
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
5356
import software.amazon.awssdk.http.nio.netty.internal.AwaitCloseChannelPoolMap;
@@ -354,14 +357,25 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
354357
* @see ProxyConfiguration#nonProxyHosts()
355358
*/
356359
Builder proxyConfiguration(ProxyConfiguration proxyConfiguration);
360+
361+
/**
362+
* Set the {@link TlsKeyManagersProvider} for this client. The {@code KeyManager}s will be used by the client to
363+
* authenticate itself with the remote server if necessary when establishing the TLS connection.
364+
* <p>
365+
* If no provider is configured, the client will default to {@link SystemPropertyTlsKeyManagersProvider}. To
366+
* disable any automatic resolution via the system properties, use {@link TlsKeyManagersProvider#noneProvider()}.
367+
*
368+
* @param keyManagersProvider The {@code TlsKeyManagersProvider}.
369+
* @return The builder for method chaining.
370+
*/
371+
Builder tlsKeyManagersProvider(TlsKeyManagersProvider keyManagersProvider);
357372
}
358373

359374
/**
360375
* Factory that allows more advanced configuration of the Netty NIO HTTP implementation. Use {@link #builder()} to
361376
* configure and construct an immutable instance of the factory.
362377
*/
363378
private static final class DefaultBuilder implements Builder {
364-
365379
private final AttributeMap.Builder standardOptions = AttributeMap.builder();
366380

367381
private SdkChannelOptions sdkChannelOptions = new SdkChannelOptions();
@@ -537,6 +551,12 @@ public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
537551
proxyConfiguration(proxyConfiguration);
538552
}
539553

554+
@Override
555+
public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
556+
this.standardOptions.put(TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
557+
return this;
558+
}
559+
540560
@Override
541561
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
542562
return new NettyNioAsyncHttpClient(this, standardOptions.build()

0 commit comments

Comments
 (0)