Browse Source

HADOOP-15169. "hadoop.ssl.enabled.protocols" should be considered in httpserver2. Contributed by Brahma Reddy Battula, Wei-Chiu Chuang.

Reviewed-by: Xiaoyu Yao <xyao@apache.org>
Co-authored-By: Brahma Reddy Battula <brahma@apache.org>
Wei-Chiu Chuang 5 năm trước cách đây
mục cha
commit
c39e9fc9a3

+ 34 - 0
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java

@@ -538,11 +538,45 @@ public final class HttpServer2 implements FilterContainer {
         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<String> enabledProtocolsList =
+            Arrays.asList(enabledProtocolsArray);
+
+        List<String> 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 {

+ 106 - 0
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 class TestSSLHttpServer extends HttpServerFunctionalTest {
 
   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 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
     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 class TestSSLHttpServer extends HttpServerFunctionalTest {
     }
   }
 
+  @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 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
       }
     }
   }
+
+  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);
+      }
+    }
+  }
 }