You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by ve...@apache.org on 2015/09/30 18:50:24 UTC

drill git commit: DRILL-3725: Add HTTPS support for Drill web interface

Repository: drill
Updated Branches:
  refs/heads/master cf4f74598 -> e7e018a3d


DRILL-3725: Add HTTPS support for Drill web interface


Project: http://git-wip-us.apache.org/repos/asf/drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/e7e018a3
Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/e7e018a3
Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/e7e018a3

Branch: refs/heads/master
Commit: e7e018a3d4634614591d8e2069d816f687c62634
Parents: cf4f745
Author: vkorukanti <ve...@gmail.com>
Authored: Mon Aug 31 13:13:05 2015 -0700
Committer: vkorukanti <ve...@gmail.com>
Committed: Wed Sep 30 07:56:37 2015 -0700

----------------------------------------------------------------------
 .../src/resources/drill-override-example.conf   |  10 +
 exec/java-exec/pom.xml                          |   4 +
 .../org/apache/drill/exec/ExecConstants.java    |   5 +
 .../org/apache/drill/exec/server/Drillbit.java  |  66 +----
 .../drill/exec/server/rest/WebServer.java       | 244 +++++++++++++++++++
 .../src/main/resources/drill-module.conf        |   1 +
 pom.xml                                         |   6 +
 7 files changed, 276 insertions(+), 60 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/distribution/src/resources/drill-override-example.conf
----------------------------------------------------------------------
diff --git a/distribution/src/resources/drill-override-example.conf b/distribution/src/resources/drill-override-example.conf
index 805d6e9..6dbab3d 100644
--- a/distribution/src/resources/drill-override-example.conf
+++ b/distribution/src/resources/drill-override-example.conf
@@ -82,6 +82,7 @@ drill.exec: {
   },
   http: {
     enabled: true,
+    ssl_enabled: false,
     port: 8047
   },
   functions: ["org.apache.drill.expr.fn.impl"],
@@ -159,3 +160,12 @@ drill.exec: {
   },
   debug.error_on_leak: true
 }
+
+# Below SSL parameters need to be set for custom transport layer settings.
+javax.net.ssl {
+  keyStore: "/keystore.file",
+  keyStorePassword: "ks_passwd",
+  trustStore: "/truststore.file",
+  trustStorePassword: "ts_passwd"
+}
+

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/exec/java-exec/pom.xml
----------------------------------------------------------------------
diff --git a/exec/java-exec/pom.xml b/exec/java-exec/pom.xml
index cb48567..fb7a62d 100644
--- a/exec/java-exec/pom.xml
+++ b/exec/java-exec/pom.xml
@@ -144,6 +144,10 @@
       <artifactId>jpam</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk15on</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.freemarker</groupId>
       <artifactId>freemarker</artifactId>
       <version>2.3.19</version>

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
index d54a777..c9554af 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
@@ -80,6 +80,11 @@ public interface ExecConstants {
   public static final String TOP_LEVEL_MAX_ALLOC = "drill.exec.memory.top.max";
   public static final String HTTP_ENABLE = "drill.exec.http.enabled";
   public static final String HTTP_PORT = "drill.exec.http.port";
+  public static final String HTTP_ENABLE_SSL = "drill.exec.http.ssl_enabled";
+  public static final String HTTP_KEYSTORE_PATH = "javax.net.ssl.keyStore";
+  public static final String HTTP_KEYSTORE_PASSWORD = "javax.net.ssl.keyStorePassword";
+  public static final String HTTP_TRUSTSTORE_PATH = "javax.net.ssl.trustStore";
+  public static final String HTTP_TRUSTSTORE_PASSWORD = "javax.net.ssl.trustStorePassword";
   public static final String SYS_STORE_PROVIDER_CLASS = "drill.exec.sys.store.provider.class";
   public static final String SYS_STORE_PROVIDER_LOCAL_PATH = "drill.exec.sys.store.provider.local.path";
   public static final String SYS_STORE_PROVIDER_LOCAL_ENABLE_WRITE = "drill.exec.sys.store.provider.local.write";

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java
index a3f17e9..892e9d9 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java
@@ -32,7 +32,7 @@ import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint;
 import org.apache.drill.exec.server.options.OptionManager;
 import org.apache.drill.exec.server.options.OptionValue;
 import org.apache.drill.exec.server.options.OptionValue.OptionType;
-import org.apache.drill.exec.server.rest.DrillRestServer;
+import org.apache.drill.exec.server.rest.WebServer;
 import org.apache.drill.exec.service.ServiceEngine;
 import org.apache.drill.exec.store.sys.CachingStoreProvider;
 import org.apache.drill.exec.store.sys.PStoreProvider;
@@ -40,16 +40,7 @@ import org.apache.drill.exec.store.sys.PStoreRegistry;
 import org.apache.drill.exec.store.sys.local.LocalPStoreProvider;
 import org.apache.drill.exec.work.WorkManager;
 import org.apache.zookeeper.Environment;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ErrorHandler;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.resource.Resource;
-import org.glassfish.jersey.servlet.ServletContainer;
-
-import com.codahale.metrics.servlets.MetricsServlet;
-import com.codahale.metrics.servlets.ThreadDumpServlet;
+
 import com.google.common.base.Stopwatch;
 
 /**
@@ -172,24 +163,19 @@ public class Drillbit implements AutoCloseable {
   private final PStoreProvider storeProvider;
   private final WorkManager manager;
   private final BootStrapContext context;
-  private final Server embeddedJetty;
+  private final WebServer webServer;
   private RegistrationHandle registrationHandle;
 
   public Drillbit(final DrillConfig config, final RemoteServiceSet serviceSet) throws Exception {
     final Stopwatch w = new Stopwatch().start();
     logger.debug("Construction started.");
     final boolean allowPortHunting = serviceSet != null;
-    final boolean enableHttp = config.getBoolean(ExecConstants.HTTP_ENABLE);
     context = new BootStrapContext(config);
     manager = new WorkManager(context);
     engine = new ServiceEngine(manager.getControlMessageHandler(), manager.getUserWorker(), context,
         manager.getWorkBus(), manager.getDataHandler(), allowPortHunting);
 
-    if (enableHttp) {
-      embeddedJetty = new Server(config.getInt(ExecConstants.HTTP_PORT));
-    } else {
-      embeddedJetty = null;
-    }
+    webServer = new WebServer(config, context.getMetrics(), manager);
 
     if (serviceSet != null) {
       coord = serviceSet.getCoordinator();
@@ -201,39 +187,6 @@ public class Drillbit implements AutoCloseable {
     logger.info("Construction completed ({} ms).", w.elapsed(TimeUnit.MILLISECONDS));
   }
 
-  private void startJetty() throws Exception {
-    if (embeddedJetty == null) {
-      return;
-    }
-
-    final ErrorHandler errorHandler = new ErrorHandler();
-    errorHandler.setShowStacks(true);
-    errorHandler.setShowMessageInTitle(true);
-
-    final ServletContextHandler servletContextHandler =
-        new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
-    servletContextHandler.setErrorHandler(errorHandler);
-    servletContextHandler.setContextPath("/");
-    embeddedJetty.setHandler(servletContextHandler);
-
-    final ServletHolder servletHolder = new ServletHolder(new ServletContainer(new DrillRestServer(manager)));
-//    servletHolder.setInitParameter(ServerProperties.PROVIDER_PACKAGES, "org.apache.drill.exec.server");
-    servletHolder.setInitOrder(1);
-    servletContextHandler.addServlet(servletHolder, "/*");
-
-    servletContextHandler.addServlet(
-        new ServletHolder(new MetricsServlet(context.getMetrics())), "/status/metrics");
-    servletContextHandler.addServlet(new ServletHolder(new ThreadDumpServlet()), "/status/threads");
-
-    final ServletHolder staticHolder = new ServletHolder("static", DefaultServlet.class);
-    staticHolder.setInitParameter("resourceBase", Resource.newClassPathResource("/rest/static").toString());
-    staticHolder.setInitParameter("dirAllowed","false");
-    staticHolder.setInitParameter("pathInfoOnly","true");
-    servletContextHandler.addServlet(staticHolder,"/static/*");
-
-    embeddedJetty.start();
-  }
-
   public void run() throws Exception {
     final Stopwatch w = new Stopwatch().start();
     logger.debug("Startup begun.");
@@ -246,7 +199,7 @@ public class Drillbit implements AutoCloseable {
     drillbitContext.getOptionManager().init();
     javaPropertiesToSystemOptions();
     registrationHandle = coord.register(md);
-    startJetty();
+    webServer.start();
 
     Runtime.getRuntime().addShutdownHook(new ShutdownThread(this, new StackTrace()));
     logger.info("Startup completed ({} ms).", w.elapsed(TimeUnit.MILLISECONDS));
@@ -278,15 +231,8 @@ public class Drillbit implements AutoCloseable {
       Thread.currentThread().interrupt();
     }
 
-    if (embeddedJetty != null) {
-      try {
-        embeddedJetty.stop();
-      } catch (final Exception e) {
-        logger.warn("Failure while shutting down embedded jetty server.");
-      }
-    }
-
     // TODO these should use a DeferredException
+    AutoCloseables.close(webServer, logger);
     AutoCloseables.close(engine, logger);
     AutoCloseables.close(storeProvider, logger);
     AutoCloseables.close(coord, logger);

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
new file mode 100644
index 0000000..802d5cd
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
@@ -0,0 +1,244 @@
+/**
+ * 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.drill.exec.server.rest;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.servlets.MetricsServlet;
+import com.codahale.metrics.servlets.ThreadDumpServlet;
+import com.google.common.base.Strings;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.work.WorkManager;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.joda.time.DateTime;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+/**
+ * Wrapper class around jetty based webserver.
+ */
+public class WebServer implements AutoCloseable {
+  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(WebServer.class);
+
+  private final DrillConfig config;
+  private final MetricRegistry metrics;
+  private final WorkManager workManager;
+  private final Server embeddedJetty;
+
+  /**
+   * Create Jetty based web server.
+   * @param config DrillConfig instance.
+   * @param metrics Metrics registry.
+   * @param workManager WorkManager instance.
+   */
+  public WebServer(final DrillConfig config, final MetricRegistry metrics, final WorkManager workManager) {
+    this.config = config;
+    this.metrics = metrics;
+    this.workManager = workManager;
+
+    if (config.getBoolean(ExecConstants.HTTP_ENABLE)) {
+      embeddedJetty = new Server();
+    } else {
+      embeddedJetty = null;
+    }
+  }
+
+  /**
+   * Start the web server including setup.
+   * @throws Exception
+   */
+  public void start() throws Exception {
+    if (embeddedJetty == null) {
+      return;
+    }
+
+    final ServerConnector serverConnector;
+    if (config.getBoolean(ExecConstants.HTTP_ENABLE_SSL)) {
+      serverConnector = createHttpsConnector();
+    } else {
+      serverConnector = createHttpConnector();
+    }
+    embeddedJetty.addConnector(serverConnector);
+
+    // Add resources
+    final ErrorHandler errorHandler = new ErrorHandler();
+    errorHandler.setShowStacks(true);
+    errorHandler.setShowMessageInTitle(true);
+
+    final ServletContextHandler servletContextHandler =
+        new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+    servletContextHandler.setErrorHandler(errorHandler);
+    servletContextHandler.setContextPath("/");
+    embeddedJetty.setHandler(servletContextHandler);
+
+    final ServletHolder servletHolder = new ServletHolder(new ServletContainer(new DrillRestServer(workManager)));
+    servletHolder.setInitOrder(1);
+    servletContextHandler.addServlet(servletHolder, "/*");
+
+    servletContextHandler.addServlet(
+        new ServletHolder(new MetricsServlet(metrics)), "/status/metrics");
+    servletContextHandler.addServlet(new ServletHolder(new ThreadDumpServlet()), "/status/threads");
+
+    final ServletHolder staticHolder = new ServletHolder("static", DefaultServlet.class);
+    staticHolder.setInitParameter("resourceBase", Resource.newClassPathResource("/rest/static").toString());
+    staticHolder.setInitParameter("dirAllowed","false");
+    staticHolder.setInitParameter("pathInfoOnly","true");
+    servletContextHandler.addServlet(staticHolder,"/static/*");
+
+    embeddedJetty.start();
+  }
+
+  /**
+   * Create an HTTPS connector for given jetty server instance. If the admin has specified keystore/truststore settings
+   * they will be used else a self-signed certificate is generated and used.
+   *
+   * @return Initialized {@link ServerConnector} for HTTPS connectios.
+   * @throws Exception
+   */
+  private ServerConnector createHttpsConnector() throws Exception {
+    logger.info("Setting up HTTPS connector for web server");
+
+    final SslContextFactory sslContextFactory = new SslContextFactory();
+
+    if (config.hasPath(ExecConstants.HTTP_KEYSTORE_PATH) &&
+        !Strings.isNullOrEmpty(config.getString(ExecConstants.HTTP_KEYSTORE_PATH))) {
+      logger.info("Using configured SSL settings for web server");
+      sslContextFactory.setKeyStorePath(config.getString(ExecConstants.HTTP_KEYSTORE_PATH));
+      sslContextFactory.setKeyStorePassword(config.getString(ExecConstants.HTTP_KEYSTORE_PASSWORD));
+
+      // TrustStore and TrustStore password are optional
+      if (config.hasPath(ExecConstants.HTTP_TRUSTSTORE_PATH)) {
+        sslContextFactory.setTrustStorePath(config.getString(ExecConstants.HTTP_TRUSTSTORE_PATH));
+        if (config.hasPath(ExecConstants.HTTP_TRUSTSTORE_PASSWORD)) {
+          sslContextFactory.setTrustStorePassword(config.getString(ExecConstants.HTTP_TRUSTSTORE_PASSWORD));
+        }
+      }
+    } else {
+      logger.info("Using generated self-signed SSL settings for web server");
+      final SecureRandom random = new SecureRandom();
+
+      // Generate a private-public key pair
+      final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+      keyPairGenerator.initialize(1024, random);
+      final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+      final DateTime now = DateTime.now();
+
+      // Create builder for certificate attributes
+      final X500NameBuilder nameBuilder =
+          new X500NameBuilder(BCStyle.INSTANCE)
+              .addRDN(BCStyle.OU, "Apache Drill (auth-generated)")
+              .addRDN(BCStyle.O, "Apache Software Foundation (auto-generated)")
+              .addRDN(BCStyle.CN, workManager.getContext().getEndpoint().getAddress());
+
+      final Date notBefore = now.minusMinutes(1).toDate();
+      final Date notAfter = now.plusYears(5).toDate();
+      final BigInteger serialNumber = new BigInteger(128, random);
+
+      // Create a certificate valid for 5years from now.
+      final X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
+          nameBuilder.build(), // attributes
+          serialNumber,
+          notBefore,
+          notAfter,
+          nameBuilder.build(),
+          keyPair.getPublic());
+
+      // Sign the certificate using the private key
+      final ContentSigner contentSigner =
+          new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate());
+      final X509Certificate certificate =
+          new JcaX509CertificateConverter().getCertificate(certificateBuilder.build(contentSigner));
+
+      // Check the validity
+      certificate.checkValidity(now.toDate());
+
+      // Make sure the certificate is self-signed.
+      certificate.verify(certificate.getPublicKey());
+
+      // Generate a random password for keystore protection
+      final String keyStorePasswd = RandomStringUtils.random(20);
+      final KeyStore keyStore = KeyStore.getInstance("JKS");
+      keyStore.load(null, null);
+      keyStore.setKeyEntry("DrillAutoGeneratedCert", keyPair.getPrivate(),
+          keyStorePasswd.toCharArray(), new java.security.cert.Certificate[]{certificate});
+
+      sslContextFactory.setKeyStore(keyStore);
+      sslContextFactory.setKeyStorePassword(keyStorePasswd);
+    }
+
+    final HttpConfiguration httpsConfig = new HttpConfiguration();
+    httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+    // SSL Connector
+    final ServerConnector sslConnector = new ServerConnector(embeddedJetty,
+        new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+        new HttpConnectionFactory(httpsConfig));
+    sslConnector.setPort(config.getInt(ExecConstants.HTTP_PORT));
+
+    return sslConnector;
+  }
+
+  /**
+   * Create HTTP connector.
+   * @return Initialized {@link ServerConnector} instance for HTTP connections.
+   * @throws Exception
+   */
+  private ServerConnector createHttpConnector() throws Exception {
+    logger.info("Setting up HTTP connector for web server");
+    final HttpConfiguration httpConfig = new HttpConfiguration();
+    final ServerConnector httpConnector = new ServerConnector(embeddedJetty, new HttpConnectionFactory(httpConfig));
+    httpConnector.setPort(config.getInt(ExecConstants.HTTP_PORT));
+
+    return httpConnector;
+  }
+
+  @Override
+  public void close() throws Exception {
+    if (embeddedJetty != null) {
+      embeddedJetty.stop();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/exec/java-exec/src/main/resources/drill-module.conf
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/drill-module.conf b/exec/java-exec/src/main/resources/drill-module.conf
index dbe449a..cf0d86c 100644
--- a/exec/java-exec/src/main/resources/drill-module.conf
+++ b/exec/java-exec/src/main/resources/drill-module.conf
@@ -87,6 +87,7 @@ drill.exec: {
   },
   http: {
     enabled: true,
+    ssl_enabled: false,
     port: 8047
   },
   functions: ["org.apache.drill.expr.fn.impl"],

http://git-wip-us.apache.org/repos/asf/drill/blob/e7e018a3/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index bc5af74..f9ed586 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1276,6 +1276,12 @@
             </exclusions>
           </dependency>
 
+          <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+            <version>1.52</version>
+          </dependency>
+
           <!-- Test Dependencies -->
           <dependency>
             <groupId>org.apache.hadoop</groupId>