From c39e9fc9a3ce7bf6f627c003526fa903a69c2646 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Tue, 15 Oct 2019 13:55:23 -0700 Subject: [PATCH] HADOOP-15169. "hadoop.ssl.enabled.protocols" should be considered in httpserver2. Contributed by Brahma Reddy Battula, Wei-Chiu Chuang. Reviewed-by: Xiaoyu Yao Co-authored-By: Brahma Reddy Battula --- .../org/apache/hadoop/http/HttpServer2.java | 34 ++++++ .../apache/hadoop/http/TestSSLHttpServer.java | 106 ++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java index 04d5da12ed7..74c81331d7f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java @@ -538,11 +538,45 @@ private ServerConnector createHttpsChannelConnector( LOG.info("Excluded Cipher List:" + excludeCiphers); } + setEnabledProtocols(sslContextFactory); conn.addFirstConnectionFactory(new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString())); return conn; } + + private void setEnabledProtocols(SslContextFactory sslContextFactory) { + String enabledProtocols = conf.get(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY, + SSLFactory.SSL_ENABLED_PROTOCOLS_DEFAULT); + if (!enabledProtocols.equals(SSLFactory.SSL_ENABLED_PROTOCOLS_DEFAULT)) { + // Jetty 9.2.4.v20141103 and above excludes certain protocols by + // default. Remove the user enabled protocols from the exclude list, + // and add them into the include list. + String[] jettyExcludedProtocols = + sslContextFactory.getExcludeProtocols(); + String[] enabledProtocolsArray = + StringUtils.getTrimmedStrings(enabledProtocols); + List enabledProtocolsList = + Arrays.asList(enabledProtocolsArray); + + List resetExcludedProtocols = new ArrayList<>(); + for (String jettyExcludedProtocol: jettyExcludedProtocols) { + if (!enabledProtocolsList.contains(jettyExcludedProtocol)) { + resetExcludedProtocols.add(jettyExcludedProtocol); + } else { + LOG.debug("Removed {} from exclude protocol list", + jettyExcludedProtocol); + } + } + + sslContextFactory.setExcludeProtocols( + resetExcludedProtocols.toArray(new String[0])); + LOG.info("Reset exclude protocol list: {}", resetExcludedProtocols); + + sslContextFactory.setIncludeProtocols(enabledProtocolsArray); + LOG.info("Enabled protocols: {}", enabledProtocols); + } + } } private HttpServer2(final Builder b) throws IOException { diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestSSLHttpServer.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestSSLHttpServer.java index 3f6ee7b79f4..5f7a2641909 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestSSLHttpServer.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestSSLHttpServer.java @@ -98,6 +98,8 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest { + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,\t\n " + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"; + private static final String INCLUDED_PROTOCOLS = "SSLv2Hello,TLSv1.1"; + @BeforeClass public static void setup() throws Exception { turnOnSSLDebugLogging(); @@ -128,6 +130,8 @@ public static void setup() throws Exception { private static void setupServer(Configuration conf, Configuration sslConf) throws IOException, URISyntaxException { + conf.set(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY, INCLUDED_PROTOCOLS); + sslConf.set(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY, INCLUDED_PROTOCOLS); server = new HttpServer2.Builder().setName("test") .addEndpoint(new URI("https://localhost")).setConf(conf) .keyPassword( @@ -214,6 +218,22 @@ private HttpsURLConnection getConnectionWithSSLSocketFactory(URL url, return conn; } + private HttpsURLConnection + getConnectionWithPreferredProtocolSSLSocketFactory(URL url, + String protocols) throws IOException, GeneralSecurityException { + HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); + SSLSocketFactory sslSocketFactory = clientSslFactory + .createSSLSocketFactory(); + LOG.info("Creating " + + PreferredProtocolSSLSocketFactory.class.getCanonicalName() + + " with protocols: " + protocols); + PreferredProtocolSSLSocketFactory cipherSSLSocketFactory + = new PreferredProtocolSSLSocketFactory(sslSocketFactory, + StringUtils.getTrimmedStrings(protocols)); + conn.setSSLSocketFactory(cipherSSLSocketFactory); + return conn; + } + @Test public void testEcho() throws Exception { assertEquals("a:b\nc:d\n", @@ -269,6 +289,18 @@ public void testExcludedCiphers() throws Exception { } } + @Test + public void testIncludedProtocols() throws Exception { + URL url = new URL(baseUrl, SERVLET_PATH_ECHO + "?a=b&c=d"); + HttpsURLConnection conn = + getConnectionWithPreferredProtocolSSLSocketFactory(url, + INCLUDED_PROTOCOLS); + assertFalse("included protocol list is empty", + INCLUDED_PROTOCOLS.isEmpty()); + + readFromConnection(conn); + } + /** Test that verified that additionally included cipher * TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA is only available cipher for working * TLS connection from client to server disabled for all other common ciphers. @@ -370,4 +402,78 @@ private void setEnabledCipherSuites(SSLSocket sslSocket) { } } } + + private class PreferredProtocolSSLSocketFactory extends SSLSocketFactory { + private final SSLSocketFactory delegateSocketFactory; + private final String[] enabledProtocols; + + PreferredProtocolSSLSocketFactory(SSLSocketFactory sslSocketFactory, + String[] enabledProtocols) { + delegateSocketFactory = sslSocketFactory; + if (null != enabledProtocols && enabledProtocols.length > 0) { + this.enabledProtocols = enabledProtocols; + } else { + this.enabledProtocols = null; + } + } + + @Override + public String[] getDefaultCipherSuites() { + return delegateSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegateSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String string, int i, boolean bln) + throws IOException { + SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket( + socket, string, i, bln); + setEnabledProtocols(sslSocket); + return sslSocket; + } + + @Override + public Socket createSocket(String string, int i) throws IOException { + SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket( + string, i); + setEnabledProtocols(sslSocket); + return sslSocket; + } + + @Override + public Socket createSocket(String string, int i, InetAddress ia, int i1) + throws IOException { + SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket( + string, i, ia, i1); + setEnabledProtocols(sslSocket); + return sslSocket; + } + + @Override + public Socket createSocket(InetAddress ia, int i) throws IOException { + SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(ia, + i); + setEnabledProtocols(sslSocket); + return sslSocket; + } + + @Override + public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) + throws IOException { + SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(ia, + i, ia1, i1); + setEnabledProtocols(sslSocket); + return sslSocket; + } + + private void setEnabledProtocols(SSLSocket sslSocket) { + if (null != enabledProtocols) { + sslSocket.setEnabledProtocols(enabledProtocols); + } + } + } }