Skip to content

Commit

Permalink
HADOOP-16371: Option to disable GCM for SSL connections when running …
Browse files Browse the repository at this point in the history
…on Java 8
  • Loading branch information
Sahil Takiar committed Sep 16, 2019
1 parent 56f042c commit f9a550e
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 69 deletions.
10 changes: 10 additions & 0 deletions hadoop-common-project/hadoop-common/pom.xml
Expand Up @@ -343,6 +343,16 @@
<artifactId>dnsjava</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.wildfly.openssl</groupId>
<artifactId>wildfly-openssl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Expand Up @@ -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;
Expand All @@ -38,30 +38,60 @@


/**
* 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
* <a href="https://github.com/wildfly/wildfly-openssl">wildlfy-openssl</a>
* library.
*
* <p>
* The factory has several different modes of operation:
* <ul>
* <li>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.</li>
* <li>Default: Attempts to use the OpenSSL mode, if it cannot load the
* necessary libraries, it falls back to the Default_JSEE mode.</li>
* <li>Default_JSSE: Delegates to the JSSE implementation of SSL, but
* it disables the GCM cipher when running on Java 8.</li>
* <li>Default_JSSE_with_GCM: Delegates to the JSSE implementation of
* SSL with no modification to the list of enabled ciphers.</li>
* </ul>
* </p>
*/
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.
*
* <p>
* 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.
* </p>
*/
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.
*
Expand All @@ -71,7 +101,7 @@ public enum SSLChannelMode {
public static synchronized void initializeDefaultFactory(
SSLChannelMode preferredMode) throws IOException {
if (instance == null) {
instance = new SSLSocketFactoryEx(preferredMode);
instance = new DelegatingSSLSocketFactory(preferredMode);
}
}

Expand All @@ -84,15 +114,11 @@ public static synchronized void initializeDefaultFactory(
* @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);
Expand All @@ -118,33 +144,47 @@ private SSLSocketFactoryEx(SSLChannelMode preferredChannelMode)
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);
}
}

Expand Down Expand Up @@ -234,7 +274,8 @@ private String[] alterCipherList(String[] defaultCiphers) {
// 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]);
}
Expand Down
Expand Up @@ -1973,6 +1973,20 @@
</description>
</property>

<property>
<name>fs.s3a.ssl.channel.mode</name>
<value>default_jsse</value>
<description>
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.
</description>
</property>

<!-- Azure file system properties -->
<property>
<name>fs.AbstractFileSystem.wasb.impl</name>
Expand Down
@@ -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);
}
}
Expand Up @@ -20,6 +20,7 @@

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory;

import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -153,6 +154,12 @@ private 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";

Expand Down
Expand Up @@ -48,6 +48,7 @@
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;
Expand Down Expand Up @@ -1218,14 +1219,15 @@ public static ClientConfiguration createAwsConf(Configuration conf,
*
* @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,
Expand All @@ -1244,6 +1246,27 @@ public static void initConnectionSettings(Configuration conf,
}
}

/**
* 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.
Expand Down

0 comments on commit f9a550e

Please sign in to comment.