Skip to content

Enable client TLS auth for Netty #1390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,11 @@
</Or>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
</Match>

<!-- See SpotBugs bug: https://github.com/spotbugs/spotbugs/issues/600, https://github.com/spotbugs/spotbugs/issues/756 -->
<Match>
<Class name="software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider"/>
<Method name="createKeyStore"/>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>
</FindBugsFilter>
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
public static final SdkHttpConfigurationOption<Boolean> REAP_IDLE_CONNECTIONS =
new SdkHttpConfigurationOption<>("ReapIdleConnections", Boolean.class);

/**
* The {@link TlsKeyManagersProvider} that will be used by the HTTP client when authenticating with a
* TLS host.
*/
public static final SdkHttpConfigurationOption<TlsKeyManagersProvider> TLS_KEY_MANAGERS_PROVIDER =
new SdkHttpConfigurationOption<>("TlsKeyManagersProvider", TlsKeyManagersProvider.class);

private static final Duration DEFAULT_SOCKET_READ_TIMEOUT = Duration.ofSeconds(30);
private static final Duration DEFAULT_SOCKET_WRITE_TIMEOUT = Duration.ofSeconds(30);
private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(2);
Expand All @@ -110,6 +117,8 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {

private static final Protocol DEFAULT_PROTOCOL = Protocol.HTTP1_1;

private static final TlsKeyManagersProvider DEFAULT_TLS_KEY_MANAGERS_PROVIDER = SystemPropertyTlsKeyManagersProvider.create();

public static final AttributeMap GLOBAL_HTTP_DEFAULTS = AttributeMap
.builder()
.put(READ_TIMEOUT, DEFAULT_SOCKET_READ_TIMEOUT)
Expand All @@ -123,6 +132,7 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
.put(PROTOCOL, DEFAULT_PROTOCOL)
.put(TRUST_ALL_CERTIFICATES, DEFAULT_TRUST_ALL_CERTIFICATES)
.put(REAP_IDLE_CONNECTIONS, DEFAULT_REAP_IDLE_CONNECTIONS)
.put(TLS_KEY_MANAGERS_PROVIDER, DEFAULT_TLS_KEY_MANAGERS_PROVIDER)
.build();

private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import javax.net.ssl.KeyManager;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;

/**
* Provider for the {@link KeyManager key managers} to be used by the SDK when
Expand All @@ -31,4 +32,11 @@ public interface TlsKeyManagersProvider {
* @return The {@link KeyManager}s, or {@code null}.
*/
KeyManager[] keyManagers();

/**
* @return A provider that returns a {@code null} array of {@link KeyManager}s.
*/
static TlsKeyManagersProvider noneProvider() {
return NoneTlsKeyManagersProvider.getInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.internal.http;

import javax.net.ssl.KeyManager;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.http.TlsKeyManagersProvider;

/**
* Simple implementation of {@link TlsKeyManagersProvider} that return a null array.
* <p>
* Use this provider if you don't want the client to present any certificates to the remote TLS host.
*/
@SdkInternalApi
public final class NoneTlsKeyManagersProvider implements TlsKeyManagersProvider {
private static final NoneTlsKeyManagersProvider INSTANCE = new NoneTlsKeyManagersProvider();

private NoneTlsKeyManagersProvider() {
}

@Override
public KeyManager[] keyManagers() {
return null;
}

public static NoneTlsKeyManagersProvider getInstance() {
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.http;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;

public class TlsKeyManagersProviderTest {

@Test
public void noneProvider_returnsProviderThatReturnsNull() {
assertThat(TlsKeyManagersProvider.noneProvider().keyManagers()).isNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.http.software.amazon.awssdk.internal.http;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;

public class NoneTlsKeyManagersProviderTest {
@Test
public void getInstance_returnsNonNull() {
assertThat(NoneTlsKeyManagersProvider.getInstance()).isNotNull();
}

@Test
public void keyManagers_returnsNull() {
assertThat(NoneTlsKeyManagersProvider.getInstance().keyManagers()).isNull();
}

@Test
public void getInstance_returnsSingletonInstance() {
NoneTlsKeyManagersProvider provider1 = NoneTlsKeyManagersProvider.getInstance();
NoneTlsKeyManagersProvider provider2 = NoneTlsKeyManagersProvider.getInstance();
assertThat(provider1 == provider2).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_CONNECTIONS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.READ_TIMEOUT;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER;
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;

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

private DefaultBuilder() {
}
Expand Down Expand Up @@ -521,7 +525,7 @@ public Builder credentialsProvider(CredentialsProvider credentialsProvider) {

@Override
public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
this.tlsKeyManagersProvider = tlsKeyManagersProvider;
standardOptions.put(TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
return this;
}

Expand Down Expand Up @@ -565,7 +569,7 @@ public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder config
private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration,
AttributeMap standardOptions) {
// TODO v2 custom socket factory
return new SdkTlsSocketFactory(getSslContext(configuration.tlsKeyManagersProvider, standardOptions),
return new SdkTlsSocketFactory(getSslContext(standardOptions),
getHostNameVerifier(standardOptions));
}

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

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

KeyManager[] keyManagers = null;
if (keyManagersProvider != null) {
keyManagers = keyManagersProvider.keyManagers();
}
TlsKeyManagersProvider provider = standardOptions.get(TLS_KEY_MANAGERS_PROVIDER);
KeyManager[] keyManagers = provider.keyManagers();

try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.instanceOf;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;
Expand All @@ -30,11 +32,14 @@
import java.net.URI;
import javax.net.ssl.SSLException;
import org.apache.http.NoHttpResponseException;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import software.amazon.awssdk.http.FileStoreTlsKeyManagersProvider;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
Expand All @@ -43,6 +48,7 @@
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.TlsKeyManagersProvider;
import software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider;

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

@Rule
public ExpectedException thrown = ExpectedException.none();

@BeforeClass
public static void setUp() throws IOException {
ClientTlsAuthTestBase.setUp();
Expand Down Expand Up @@ -108,14 +117,9 @@ public void canMakeHttpsRequestWhenKeyProviderConfigured() throws IOException {

@Test
public void requestFailsWhenKeyProviderNotConfigured() throws IOException {
client = ApacheHttpClient.builder().build();
try {
makeRequestWithHttpClient(client);
fail("HTTP request should have failed");
} catch (NoHttpResponseException | SSLException | SocketException expected) {
// The client doesn't seem to consistently throw a single error,
// and can also vary depending on actual JVM used.
}
thrown.expect(anyOf(instanceOf(NoHttpResponseException.class), instanceOf(SSLException.class)));
client = ApacheHttpClient.builder().tlsKeyManagersProvider(NoneTlsKeyManagersProvider.getInstance()).build();
makeRequestWithHttpClient(client);
}

@Test
Expand Down Expand Up @@ -151,6 +155,22 @@ public void defaultTlsKeyManagersProviderIsSystemPropertyProvider() throws IOExc
}
}

@Test
public void defaultTlsKeyManagersProviderIsSystemPropertyProvider_explicitlySetToNull() throws IOException {
System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString());
System.setProperty(SSL_KEY_STORE_TYPE.property(), CLIENT_STORE_TYPE);
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), STORE_PASSWORD);

client = ApacheHttpClient.builder().tlsKeyManagersProvider(null).build();
try {
makeRequestWithHttpClient(client);
} finally {
System.clearProperty(SSL_KEY_STORE.property());
System.clearProperty(SSL_KEY_STORE_TYPE.property());
System.clearProperty(SSL_KEY_STORE_PASSWORD.property());
}
}

private HttpExecuteResponse makeRequestWithHttpClient(SdkHttpClient httpClient) throws IOException {
SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
.method(SdkHttpMethod.GET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_PENDING_CONNECTION_ACQUIRES;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.READ_TIMEOUT;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.WRITE_TIMEOUT;
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS;
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS;
Expand All @@ -48,6 +49,8 @@
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider;
import software.amazon.awssdk.http.TlsKeyManagersProvider;
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.http.nio.netty.internal.AwaitCloseChannelPoolMap;
Expand Down Expand Up @@ -354,14 +357,25 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
* @see ProxyConfiguration#nonProxyHosts()
*/
Builder proxyConfiguration(ProxyConfiguration proxyConfiguration);

/**
* Set the {@link TlsKeyManagersProvider} for this client. The {@code KeyManager}s will be used by the client to
* authenticate itself with the remote server if necessary when establishing the TLS connection.
* <p>
* If no provider is configured, the client will default to {@link SystemPropertyTlsKeyManagersProvider}. To
* disable any automatic resolution via the system properties, use {@link TlsKeyManagersProvider#noneProvider()}.
*
* @param keyManagersProvider The {@code TlsKeyManagersProvider}.
* @return The builder for method chaining.
*/
Builder tlsKeyManagersProvider(TlsKeyManagersProvider keyManagersProvider);
}

/**
* Factory that allows more advanced configuration of the Netty NIO HTTP implementation. Use {@link #builder()} to
* configure and construct an immutable instance of the factory.
*/
private static final class DefaultBuilder implements Builder {

private final AttributeMap.Builder standardOptions = AttributeMap.builder();

private SdkChannelOptions sdkChannelOptions = new SdkChannelOptions();
Expand Down Expand Up @@ -537,6 +551,12 @@ public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
proxyConfiguration(proxyConfiguration);
}

@Override
public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
this.standardOptions.put(TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
return this;
}

@Override
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
return new NettyNioAsyncHttpClient(this, standardOptions.build()
Expand Down
Loading