From 55ce454ce4f1b1eaa9f041f3b0fb69a9fcc56894 Mon Sep 17 00:00:00 2001
From: Sahil Takiar
Date: Tue, 17 Sep 2019 11:30:18 +0100
Subject: [PATCH] HADOOP-16371: Option to disable GCM for SSL connections when
running on Java 8.
Contributed by Sahil Takiar.
This moves the SSLSocketFactoryEx class from hadoop-azure into hadoop-common
as the DelegatingSSLSocketFactory and binds the S3A connector to it so that
it can avoid using those HTTPS algorithms which are underperformant on Java 8.
Change-Id: Ie9e6ac24deac1aa05e136e08899620efa7d22abd
---
hadoop-common-project/hadoop-common/pom.xml | 10 ++
.../ssl/DelegatingSSLSocketFactory.java | 117 ++++++++++++------
.../src/main/resources/core-default.xml | 14 +++
.../ssl/TestDelegatingSSLSocketFactory.java | 57 +++++++++
.../org/apache/hadoop/fs/s3a/Constants.java | 7 ++
.../org/apache/hadoop/fs/s3a/S3AUtils.java | 31 ++++-
.../hadoop/fs/s3a/impl/NetworkBinding.java | 113 +++++++++++++++++
.../markdown/tools/hadoop-aws/performance.md | 51 ++++++++
.../fs/contract/s3a/ITestS3AContractSeek.java | 22 +++-
.../hadoop/fs/azurebfs/AbfsConfiguration.java | 4 +-
.../constants/FileSystemConfigurations.java | 6 +-
.../fs/azurebfs/services/AbfsClient.java | 8 +-
.../azurebfs/services/AbfsHttpOperation.java | 4 +-
...TestAbfsConfigurationFieldsValidation.java | 16 +--
.../fs/azurebfs/services/TestAbfsClient.java | 6 +-
15 files changed, 397 insertions(+), 69 deletions(-)
rename hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/utils/SSLSocketFactoryEx.java => hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/DelegatingSSLSocketFactory.java (63%)
create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestDelegatingSSLSocketFactory.java
create mode 100644 hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/NetworkBinding.java
diff --git a/hadoop-common-project/hadoop-common/pom.xml b/hadoop-common-project/hadoop-common/pom.xml
index b507b8b155..a06915e921 100644
--- a/hadoop-common-project/hadoop-common/pom.xml
+++ b/hadoop-common-project/hadoop-common/pom.xml
@@ -343,6 +343,16 @@
dnsjavacompile
+
+ org.wildfly.openssl
+ wildfly-openssl
+ provided
+
+
+ org.assertj
+ assertj-core
+ test
+
diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/utils/SSLSocketFactoryEx.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/DelegatingSSLSocketFactory.java
similarity index 63%
rename from hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/utils/SSLSocketFactoryEx.java
rename to hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/DelegatingSSLSocketFactory.java
index 01dca4c953..ad97a99c6d 100644
--- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/utils/SSLSocketFactoryEx.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/DelegatingSSLSocketFactory.java
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package org.apache.hadoop.fs.azurebfs.utils;
+package org.apache.hadoop.security.ssl;
import java.io.IOException;
import java.net.InetAddress;
@@ -38,30 +38,60 @@ import org.wildfly.openssl.SSL;
/**
- * Extension to use native OpenSSL library instead of JSSE for better
- * performance.
+ * A {@link SSLSocketFactory} that can delegate to various SSL implementations.
+ * Specifically, either OpenSSL or JSSE can be used. OpenSSL offers better
+ * performance than JSSE and is made available via the
+ * wildlfy-openssl
+ * library.
*
+ *
+ * The factory has several different modes of operation:
+ *
+ *
OpenSSL: Uses the wildly-openssl library to delegate to the
+ * system installed OpenSSL. If the wildfly-openssl integration is not
+ * properly setup, an exception is thrown.
+ *
Default: Attempts to use the OpenSSL mode, if it cannot load the
+ * necessary libraries, it falls back to the Default_JSEE mode.
+ *
Default_JSSE: Delegates to the JSSE implementation of SSL, but
+ * it disables the GCM cipher when running on Java 8.
+ *
Default_JSSE_with_GCM: Delegates to the JSSE implementation of
+ * SSL with no modification to the list of enabled ciphers.
+ *
+ *
*/
-public final class SSLSocketFactoryEx extends SSLSocketFactory {
+public final class DelegatingSSLSocketFactory extends SSLSocketFactory {
/**
* Default indicates Ordered, preferred OpenSSL, if failed to load then fall
- * back to Default_JSSE
+ * back to Default_JSSE.
+ *
+ *
+ * Default_JSSE is not truly the the default JSSE implementation because
+ * the GCM cipher is disabled when running on Java 8. However, the name
+ * was not changed in order to preserve backwards compatibility. Instead,
+ * a new mode called Default_JSSE_with_GCM delegates to the default JSSE
+ * implementation with no changes to the list of enabled ciphers.
+ *
*/
public enum SSLChannelMode {
OpenSSL,
Default,
- Default_JSSE
+ Default_JSSE,
+ Default_JSSE_with_GCM
}
- private static SSLSocketFactoryEx instance = null;
+ private static DelegatingSSLSocketFactory instance = null;
private static final Logger LOG = LoggerFactory.getLogger(
- SSLSocketFactoryEx.class);
+ DelegatingSSLSocketFactory.class);
private String providerName;
private SSLContext ctx;
private String[] ciphers;
private SSLChannelMode channelMode;
+ // This should only be modified within the #initializeDefaultFactory
+ // method which is synchronized
+ private boolean openSSLProviderRegistered;
+
/**
* Initialize a singleton SSL socket factory.
*
@@ -71,7 +101,7 @@ public final class SSLSocketFactoryEx extends SSLSocketFactory {
public static synchronized void initializeDefaultFactory(
SSLChannelMode preferredMode) throws IOException {
if (instance == null) {
- instance = new SSLSocketFactoryEx(preferredMode);
+ instance = new DelegatingSSLSocketFactory(preferredMode);
}
}
@@ -84,15 +114,11 @@ public final class SSLSocketFactoryEx extends SSLSocketFactory {
* @return instance of the SSLSocketFactory, instance must be initialized by
* initializeDefaultFactory.
*/
- public static SSLSocketFactoryEx getDefaultFactory() {
+ public static DelegatingSSLSocketFactory getDefaultFactory() {
return instance;
}
- static {
- OpenSSLProvider.register();
- }
-
- private SSLSocketFactoryEx(SSLChannelMode preferredChannelMode)
+ private DelegatingSSLSocketFactory(SSLChannelMode preferredChannelMode)
throws IOException {
try {
initializeSSLContext(preferredChannelMode);
@@ -118,33 +144,47 @@ public final class SSLSocketFactoryEx extends SSLSocketFactory {
private void initializeSSLContext(SSLChannelMode preferredChannelMode)
throws NoSuchAlgorithmException, KeyManagementException {
switch (preferredChannelMode) {
- case Default:
- try {
- java.util.logging.Logger logger = java.util.logging.Logger.getLogger(SSL.class.getName());
- logger.setLevel(Level.WARNING);
- ctx = SSLContext.getInstance("openssl.TLS");
- ctx.init(null, null, null);
- // Strong reference needs to be kept to logger until initialization of SSLContext finished (see HADOOP-16174):
- logger.setLevel(Level.INFO);
- channelMode = SSLChannelMode.OpenSSL;
- } catch (NoSuchAlgorithmException e) {
- LOG.warn("Failed to load OpenSSL. Falling back to the JSSE default.");
- ctx = SSLContext.getDefault();
- channelMode = SSLChannelMode.Default_JSSE;
- }
- break;
- case OpenSSL:
+ case Default:
+ if (!openSSLProviderRegistered) {
+ OpenSSLProvider.register();
+ openSSLProviderRegistered = true;
+ }
+ try {
+ java.util.logging.Logger logger = java.util.logging.Logger.getLogger(
+ SSL.class.getName());
+ logger.setLevel(Level.WARNING);
ctx = SSLContext.getInstance("openssl.TLS");
ctx.init(null, null, null);
+ // Strong reference needs to be kept to logger until initialization of
+ // SSLContext finished (see HADOOP-16174):
+ logger.setLevel(Level.INFO);
channelMode = SSLChannelMode.OpenSSL;
- break;
- case Default_JSSE:
+ } catch (NoSuchAlgorithmException e) {
+ LOG.debug("Failed to load OpenSSL. Falling back to the JSSE default.");
ctx = SSLContext.getDefault();
channelMode = SSLChannelMode.Default_JSSE;
- break;
- default:
- throw new AssertionError("Unknown channel mode: "
- + preferredChannelMode);
+ }
+ break;
+ case OpenSSL:
+ if (!openSSLProviderRegistered) {
+ OpenSSLProvider.register();
+ openSSLProviderRegistered = true;
+ }
+ ctx = SSLContext.getInstance("openssl.TLS");
+ ctx.init(null, null, null);
+ channelMode = SSLChannelMode.OpenSSL;
+ break;
+ case Default_JSSE:
+ ctx = SSLContext.getDefault();
+ channelMode = SSLChannelMode.Default_JSSE;
+ break;
+ case Default_JSSE_with_GCM:
+ ctx = SSLContext.getDefault();
+ channelMode = SSLChannelMode.Default_JSSE_with_GCM;
+ break;
+ default:
+ throw new NoSuchAlgorithmException("Unknown channel mode: "
+ + preferredChannelMode);
}
}
@@ -234,7 +274,8 @@ public final class SSLSocketFactoryEx extends SSLSocketFactory {
// Remove GCM mode based ciphers from the supported list.
for (int i = 0; i < defaultCiphers.length; i++) {
if (defaultCiphers[i].contains("_GCM_")) {
- LOG.debug("Removed Cipher - " + defaultCiphers[i]);
+ LOG.debug("Removed Cipher - {} from list of enabled SSLSocket ciphers",
+ defaultCiphers[i]);
} else {
preferredSuits.add(defaultCiphers[i]);
}
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index c08cfd27c1..6e5f400c23 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -1973,6 +1973,20 @@
+
+ fs.s3a.ssl.channel.mode
+ default_jsse
+
+ If secure connections to S3 are enabled, configures the SSL
+ implementation used to encrypt connections to S3. Supported values are:
+ "default_jsse" and "default_jsse_with_gcm". "default_jsse" uses the Java
+ Secure Socket Extension package (JSSE). However, when running on Java 8,
+ the GCM cipher is removed from the list of enabled ciphers. This is due
+ to performance issues with GCM in Java 8. "default_jsse_with_gcm" uses
+ the JSSE with the default list of cipher suites.
+
+
+
fs.AbstractFileSystem.wasb.impl
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestDelegatingSSLSocketFactory.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestDelegatingSSLSocketFactory.java
new file mode 100644
index 0000000000..f19f65b18c
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestDelegatingSSLSocketFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.hadoop.security.ssl;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import org.apache.hadoop.util.NativeCodeLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Tests for {@link DelegatingSSLSocketFactory}.
+ */
+public class TestDelegatingSSLSocketFactory {
+
+ @Test
+ public void testOpenSSL() throws IOException {
+ assumeTrue("Unable to load native libraries",
+ NativeCodeLoader.isNativeCodeLoaded());
+ assumeTrue("Build was not compiled with support for OpenSSL",
+ NativeCodeLoader.buildSupportsOpenssl());
+ DelegatingSSLSocketFactory.initializeDefaultFactory(
+ DelegatingSSLSocketFactory.SSLChannelMode.OpenSSL);
+ assertThat(DelegatingSSLSocketFactory.getDefaultFactory()
+ .getProviderName()).contains("openssl");
+ }
+
+ @Test
+ public void testJSEENoGCMJava8() throws IOException {
+ assumeTrue("Not running on Java 8",
+ System.getProperty("java.version").startsWith("1.8"));
+ DelegatingSSLSocketFactory.initializeDefaultFactory(
+ DelegatingSSLSocketFactory.SSLChannelMode.Default_JSSE);
+ assertThat(Arrays.stream(DelegatingSSLSocketFactory.getDefaultFactory()
+ .getSupportedCipherSuites())).noneMatch("GCM"::contains);
+ }
+}
diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
index 5c45ce6e58..791cc41266 100644
--- a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.fs.s3a;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory;
import java.util.concurrent.TimeUnit;
@@ -153,6 +154,12 @@ public final class Constants {
"fs.s3a.connection.ssl.enabled";
public static final boolean DEFAULT_SECURE_CONNECTIONS = true;
+ // use OpenSSL or JSEE for secure connections
+ public static final String SSL_CHANNEL_MODE = "fs.s3a.ssl.channel.mode";
+ public static final DelegatingSSLSocketFactory.SSLChannelMode
+ DEFAULT_SSL_CHANNEL_MODE =
+ DelegatingSSLSocketFactory.SSLChannelMode.Default_JSSE;
+
//use a custom endpoint?
public static final String ENDPOINT = "fs.s3a.endpoint";
diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AUtils.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AUtils.java
index a69815d442..54d1b53405 100644
--- a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AUtils.java
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AUtils.java
@@ -48,6 +48,7 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.fs.s3a.auth.IAMInstanceCredentialsProvider;
+import org.apache.hadoop.fs.s3a.impl.NetworkBinding;
import org.apache.hadoop.fs.s3native.S3xLoginHelper;
import org.apache.hadoop.net.ConnectTimeoutException;
import org.apache.hadoop.security.ProviderUtils;
@@ -1218,14 +1219,15 @@ public final class S3AUtils {
*
* @param conf Hadoop configuration
* @param awsConf AWS SDK configuration
+ *
+ * @throws IOException if there was an error initializing the protocol
+ * settings
*/
public static void initConnectionSettings(Configuration conf,
- ClientConfiguration awsConf) {
+ ClientConfiguration awsConf) throws IOException {
awsConf.setMaxConnections(intOption(conf, MAXIMUM_CONNECTIONS,
DEFAULT_MAXIMUM_CONNECTIONS, 1));
- boolean secureConnections = conf.getBoolean(SECURE_CONNECTIONS,
- DEFAULT_SECURE_CONNECTIONS);
- awsConf.setProtocol(secureConnections ? Protocol.HTTPS : Protocol.HTTP);
+ initProtocolSettings(conf, awsConf);
awsConf.setMaxErrorRetry(intOption(conf, MAX_ERROR_RETRIES,
DEFAULT_MAX_ERROR_RETRIES, 0));
awsConf.setConnectionTimeout(intOption(conf, ESTABLISH_TIMEOUT,
@@ -1244,6 +1246,27 @@ public final class S3AUtils {
}
}
+ /**
+ * Initializes the connection protocol settings when connecting to S3 (e.g.
+ * either HTTP or HTTPS). If secure connections are enabled, this method
+ * will load the configured SSL providers.
+ *
+ * @param conf Hadoop configuration
+ * @param awsConf AWS SDK configuration
+ *
+ * @throws IOException if there is an error initializing the configured
+ * {@link javax.net.ssl.SSLSocketFactory}
+ */
+ private static void initProtocolSettings(Configuration conf,
+ ClientConfiguration awsConf) throws IOException {
+ boolean secureConnections = conf.getBoolean(SECURE_CONNECTIONS,
+ DEFAULT_SECURE_CONNECTIONS);
+ awsConf.setProtocol(secureConnections ? Protocol.HTTPS : Protocol.HTTP);
+ if (secureConnections) {
+ NetworkBinding.bindSSLChannelMode(conf, awsConf);
+ }
+ }
+
/**
* Initializes AWS SDK proxy support in the AWS client configuration
* if the S3A settings enable it.
diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/NetworkBinding.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/NetworkBinding.java
new file mode 100644
index 0000000000..c255127fa9
--- /dev/null
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/NetworkBinding.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.hadoop.fs.s3a.impl;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+import com.amazonaws.ClientConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory;
+
+import static org.apache.hadoop.fs.s3a.Constants.DEFAULT_SSL_CHANNEL_MODE;
+import static org.apache.hadoop.fs.s3a.Constants.SSL_CHANNEL_MODE;
+
+/**
+ * Configures network settings when communicating with AWS services.
+ */
+public class NetworkBinding {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(NetworkBinding.class);
+ private static final String AWS_SOCKET_FACTORY_CLASSNAME = "com.amazonaws" +
+ ".thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory";
+
+ /**
+ * Configures the {@link com.amazonaws.thirdparty.apache.http.conn.ssl
+ * .SSLConnectionSocketFactory} used by the AWS SDK. A custom
+ * SSLConnectionSocketFactory can be set using the method
+ * {@link com.amazonaws.ApacheHttpClientConfig#setSslSocketFactory(
+ * com.amazonaws.thirdparty.apache.http.conn.socket.ConnectionSocketFactory)}.
+ * If {@link com.amazonaws.thirdparty.apache.http.conn.ssl
+ * .SSLConnectionSocketFactory} cannot be found on the classpath, the value
+ * of {@link org.apache.hadoop.fs.s3a.Constants#SSL_CHANNEL_MODE} is ignored.
+ *
+ * @param conf the {@link Configuration} used to get the client specified
+ * value of {@link org.apache.hadoop.fs.s3a.Constants
+ * #SSL_CHANNEL_MODE}
+ * @param awsConf the {@link ClientConfiguration} to set the
+ * SSLConnectionSocketFactory for.
+ * @throws IOException if there is an error while initializing the
+ * {@link SSLSocketFactory}.
+ */
+ public static void bindSSLChannelMode(Configuration conf,
+ ClientConfiguration awsConf) throws IOException {
+ try {
+ // Validate that SSL_CHANNEL_MODE is set to a valid value.
+ String channelModeString = conf.get(
+ SSL_CHANNEL_MODE, DEFAULT_SSL_CHANNEL_MODE.name());
+ DelegatingSSLSocketFactory.SSLChannelMode channelMode = null;
+ for (DelegatingSSLSocketFactory.SSLChannelMode mode :
+ DelegatingSSLSocketFactory.SSLChannelMode.values()) {
+ if (mode.name().equalsIgnoreCase(channelModeString)) {
+ channelMode = mode;
+ }
+ }
+ if (channelMode == null) {
+ throw new IllegalArgumentException(channelModeString +
+ " is not a valid value for " + SSL_CHANNEL_MODE);
+ }
+ if (channelMode == DelegatingSSLSocketFactory.SSLChannelMode.OpenSSL ||
+ channelMode == DelegatingSSLSocketFactory.SSLChannelMode.Default) {
+ throw new UnsupportedOperationException("S3A does not support " +
+ "setting " + SSL_CHANNEL_MODE + " " +
+ DelegatingSSLSocketFactory.SSLChannelMode.OpenSSL + " or " +
+ DelegatingSSLSocketFactory.SSLChannelMode.Default);
+ }
+
+ // Look for AWS_SOCKET_FACTORY_CLASSNAME on the classpath and instantiate
+ // an instance using the DelegatingSSLSocketFactory as the
+ // SSLSocketFactory.
+ Class> sslConnectionSocketFactory = Class.forName(
+ AWS_SOCKET_FACTORY_CLASSNAME);
+ Constructor> factoryConstructor =
+ sslConnectionSocketFactory.getDeclaredConstructor(
+ SSLSocketFactory.class, HostnameVerifier.class);
+ DelegatingSSLSocketFactory.initializeDefaultFactory(channelMode);
+ awsConf.getApacheHttpClientConfig().setSslSocketFactory(
+ (com.amazonaws.thirdparty.apache.http.conn.ssl.
+ SSLConnectionSocketFactory) factoryConstructor
+ .newInstance(DelegatingSSLSocketFactory
+ .getDefaultFactory(),
+ (HostnameVerifier) null));
+ } catch (ClassNotFoundException | NoSuchMethodException |
+ IllegalAccessException | InstantiationException |
+ InvocationTargetException e) {
+ LOG.debug("Unable to create class {}, value of {} will be ignored",
+ AWS_SOCKET_FACTORY_CLASSNAME, SSL_CHANNEL_MODE, e);
+ }
+ }
+}
diff --git a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md
index ca86bd3354..6a9b3f7aba 100644
--- a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md
+++ b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md
@@ -516,3 +516,54 @@ With an object store this is slow, and may cause problems if the caller
expects an immediate response. For example, a thread may block so long
that other liveness checks start to fail.
Consider spawning off an executor thread to do these background cleanup operations.
+
+## Tuning SSL Performance
+
+By default, S3A uses HTTPS to communicate with AWS Services. This means that all
+communication with S3 is encrypted using SSL. The overhead of this encryption
+can significantly slow down applications. The configuration option
+`fs.s3a.ssl.channel.mode` allows applications to trigger certain SSL
+optimizations.
+
+By default, `fs.s3a.ssl.channel.mode` is set to `default_jsse`, which uses
+the Java Secure Socket Extension implementation of SSL (this is the default
+implementation when running Java). However, there is one difference, the GCM
+cipher is removed from the list of enabled cipher suites when running on Java 8.
+The GCM cipher has known performance issues when running on Java 8, see
+HADOOP-15669 and HADOOP-16050 for details. It is important to note that the
+GCM cipher is only disabled on Java 8. GCM performance has been improved
+in Java 9, so if `default_jsse` is specified and applications run on Java
+9, they should see no difference compared to running with the vanilla JSSE.
+
+Other options for `fs.s3a.ssl.channel.mode` include `default_jsse_with_gcm`.
+This option includes GCM in the list of cipher suites on Java 8, so it is
+equivalent to running with the vanilla JSSE. The naming convention is setup
+in order to preserve backwards compatibility with HADOOP-15669.
+
+`fs.s3a.ssl.channel.mode` can be configured as follows:
+
+```xml
+
+ fs.s3a.ssl.channel.mode
+ default_jsse
+
+ If secure connections to S3 are enabled, configures the SSL
+ implementation used to encrypt connections to S3. Supported values are:
+ "default_jsse" and "default_jsse_with_gcm". "default_jsse" uses the Java
+ Secure Socket Extension package (JSSE). However, when running on Java 8,
+ the GCM cipher is removed from the list of enabled ciphers. This is due
+ to performance issues with GCM in Java 8. "default_jsse_with_gcm" uses
+ the JSSE with the default list of cipher suites.
+
+
+```
+
+Supported values for `fs.s3a.ssl.channel.mode`:
+
+| fs.s3a.ssl.channel.mode Value | Description |
+|-------------------------------|-------------|
+| default_jsse | Uses Java JSSE without GCM on Java 8 |
+| default_jsse_with_gcm | Uses Java JSSE |
+
+Other options may be added to `fs.s3a.ssl.channel.mode` in the future as
+further SSL optimizations are made.
\ No newline at end of file
diff --git a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractSeek.java b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractSeek.java
index 787e32a8f7..9332621d11 100644
--- a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractSeek.java
+++ b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractSeek.java
@@ -40,6 +40,7 @@ import org.apache.hadoop.fs.contract.ContractTestUtils;
import org.apache.hadoop.fs.s3a.S3AFileSystem;
import org.apache.hadoop.fs.s3a.S3AInputPolicy;
import org.apache.hadoop.fs.s3a.S3ATestUtils;
+import org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.hadoop.fs.s3a.Constants.INPUT_FADVISE;
@@ -47,8 +48,14 @@ import static org.apache.hadoop.fs.s3a.Constants.INPUT_FADV_NORMAL;
import static org.apache.hadoop.fs.s3a.Constants.INPUT_FADV_RANDOM;
import static org.apache.hadoop.fs.s3a.Constants.INPUT_FADV_SEQUENTIAL;
import static org.apache.hadoop.fs.s3a.Constants.READAHEAD_RANGE;
+import static org.apache.hadoop.fs.s3a.Constants.SSL_CHANNEL_MODE;
import static org.apache.hadoop.fs.s3a.S3ATestConstants.FS_S3A_IMPL_DISABLE_CACHE;
import static org.apache.hadoop.fs.s3a.S3ATestUtils.maybeEnableS3Guard;
+import static org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory.
+ SSLChannelMode.Default_JSSE;
+import static org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory.
+ SSLChannelMode.Default_JSSE_with_GCM;
+
/**
* S3A contract tests covering file seek.
@@ -62,6 +69,7 @@ public class ITestS3AContractSeek extends AbstractContractSeekTest {
protected static final int READAHEAD = 1024;
private final String seekPolicy;
+ private final DelegatingSSLSocketFactory.SSLChannelMode sslChannelMode;
public static final int DATASET_LEN = READAHEAD * 2;
@@ -75,9 +83,9 @@ public class ITestS3AContractSeek extends AbstractContractSeekTest {
@Parameterized.Parameters
public static Collection