This is an automated email from the ASF dual-hosted git repository. volodymyr pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/drill.git
commit f44f335473b587ceab9aa5043ac5bfbe172c7426 Author: Igor Guzenko <[email protected]> AuthorDate: Mon Mar 2 17:09:54 2020 +0200 DRILL-7625: Add options for SslContextFactory closes #2012 --- .../src/main/resources/drill-override-example.conf | 66 +++++++ .../java/org/apache/drill/exec/ExecConstants.java | 32 +++ .../apache/drill/exec/server/rest/WebServer.java | 121 ++---------- .../rest/ssl/SslContextFactoryConfigurator.java | 214 +++++++++++++++++++++ .../org/apache/drill/exec/ssl/SSLConfigServer.java | 16 +- .../ssl/SslContextFactoryConfiguratorTest.java | 71 +++++++ 6 files changed, 404 insertions(+), 116 deletions(-) diff --git a/distribution/src/main/resources/drill-override-example.conf b/distribution/src/main/resources/drill-override-example.conf index ad942bb..2dec959 100644 --- a/distribution/src/main/resources/drill-override-example.conf +++ b/distribution/src/main/resources/drill-override-example.conf @@ -113,6 +113,72 @@ drill.exec: { # Location to keytab file for above spnego principal spnego.keytab: "<keytab_file_location>"; }, + jetty: { + server: { + # Optional params to set on Jetty's org.eclipse.jetty.util.ssl.SslContextFactory when drill.exec.http.ssl_enabled + sslContextFactory: { + # allows to specify cert to use when multiple non-SNI certificates are available. + certAlias: "certAlias", + # path to file that contains Certificate Revocation List + crlPath: "/etc/file.crl", + # enable Certificate Revocation List Distribution Points Support + enableCRLDP: false, + # enable On-Line Certificate Status Protocol support + enableOCSP: false, + # when set to "HTTPS" hostname verification will be enabled + endpointIdentificationAlgorithm: "HTTPS", + # accepts exact cipher suite names and/or regular expressions. + excludeCipherSuites: ["SSL_DHE_DSS_WITH_DES_CBC_SHA"], + # list of TLS/SSL protocols to exclude + excludeProtocols: ["TLSv1.1"], + # accepts exact cipher suite names and/or regular expressions. + includeCipherSuites: ["SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"], + # list of TLS/SSL protocols to include + includeProtocols: ["TLSv1.2", "TLSv1.3"], + # the algorithm name (default "SunX509") used by the javax.net.ssl.KeyManagerFactory + keyManagerFactoryAlgorithm: "SunX509", + # classname of custom java.security.Provider implementation + keyStoreProvider: null, + # type of key store (default "JKS") + keyStoreType: "JKS", + # max number of intermediate certificates in sertificate chain + maxCertPathLength: -1, + # set true if ssl needs client authentication + needClientAuth: false, + # location of the OCSP Responder + ocspResponderURL: "", + # javax.net.ssl.SSLContext provider + provider: null, + # whether TLS renegotiation is allowed + renegotiationAllowed: false, + # number of renegotions allowed for this connection (-1 for unlimited, default 5) . + renegotiationLimit: 5, + # algorithm name for java.security.SecurityRandom instances. + # https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecureRandom + secureRandomAlgorithm: "NativePRNG", + # set the flag to enable SSL Session caching + sessionCachingEnabled: false, + # set if you want to bound session cache size + sslSessionCacheSize: -1, + # session timeout in seconds. + sslSessionTimeout: -1, + # the algorithm name (default "SunX509") used by the javax.net.ssl.TrustManagerFactory + trustManagerFactoryAlgorithm: "SunX509", + # provider of the trust store + trustStoreProvider: null, + # type of the trust store (default "JKS") + trustStoreType: "JKS", + # sets whether the local cipher suites preference should be honored. + useCipherSuiteOrder: false, + # true if SSL certificates have to be validated + validateCerts: false, + # true if SSL certificates of the peer have to be validated + validatePeerCerts: false, + # true if SSL wants client authentication. + wantClientAuth: false + } + } + } }, # Below SSL parameters need to be set for custom transport layer settings. ssl: { 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 d0a4718..2e3a9cf 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 @@ -212,6 +212,38 @@ public final class ExecConstants { public static final String HTTP_JETTY_SERVER_ACCEPTORS = "drill.exec.http.jetty.server.acceptors"; public static final String HTTP_JETTY_SERVER_SELECTORS = "drill.exec.http.jetty.server.selectors"; public static final String HTTP_JETTY_SERVER_HANDLERS = "drill.exec.http.jetty.server.handlers"; + + public static final String HTTP_JETTY_SSL_CONTEXT_FACTORY_OPTIONS_PREFIX = "drill.exec.http.jetty.server.sslContextFactory"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_CERT_ALIAS = "drill.exec.http.jetty.server.sslContextFactory.certAlias"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_CRL_PATH = "drill.exec.http.jetty.server.sslContextFactory.crlPath"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENABLE_CRLDP = "drill.exec.http.jetty.server.sslContextFactory.enableCRLDP"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENABLE_OCSP = "drill.exec.http.jetty.server.sslContextFactory.enableOCSP"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENDPOINT_IDENTIFICATION_ALGORITHM = "drill.exec.http.jetty.server.sslContextFactory.endpointIdentificationAlgorithm"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_EXCLUDE_CIPHER_SUITES = "drill.exec.http.jetty.server.sslContextFactory.excludeCipherSuites"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_EXCLUDE_PROTOCOLS = "drill.exec.http.jetty.server.sslContextFactory.excludeProtocols"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_INCLUDE_CIPHER_SUITES = "drill.exec.http.jetty.server.sslContextFactory.includeCipherSuites"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_INCLUDE_PROTOCOLS = "drill.exec.http.jetty.server.sslContextFactory.includeProtocols"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEY_MANAGER_FACTORY_ALGORITHM = "drill.exec.http.jetty.server.sslContextFactory.keyManagerFactoryAlgorithm"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEYSTORE_PROVIDER = "drill.exec.http.jetty.server.sslContextFactory.keyStoreProvider"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEYSTORE_TYPE = "drill.exec.http.jetty.server.sslContextFactory.keyStoreType"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_MAX_CERT_PATH_LENGTH = "drill.exec.http.jetty.server.sslContextFactory.maxCertPathLength"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_NEED_CLIENT_AUTH = "drill.exec.http.jetty.server.sslContextFactory.needClientAuth"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_OCSP_RESPONDER_URL = "drill.exec.http.jetty.server.sslContextFactory.ocspResponderURL"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_PROVIDER = "drill.exec.http.jetty.server.sslContextFactory.provider"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_RENEGOTIATION_ALLOWED = "drill.exec.http.jetty.server.sslContextFactory.renegotiationAllowed"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_RENEGOTIATION_LIMIT = "drill.exec.http.jetty.server.sslContextFactory.renegotiationLimit"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SECURE_RANDOM_ALGORITHM = "drill.exec.http.jetty.server.sslContextFactory.secureRandomAlgorithm"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SESSION_CACHING_ENABLED = "drill.exec.http.jetty.server.sslContextFactory.sessionCachingEnabled"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SSL_SESSION_CACHE_SIZE = "drill.exec.http.jetty.server.sslContextFactory.sslSessionCacheSize"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SSL_SESSION_TIMEOUT = "drill.exec.http.jetty.server.sslContextFactory.sslSessionTimeout"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTMANAGERFACTORY_ALGORITHM = "drill.exec.http.jetty.server.sslContextFactory.trustManagerFactoryAlgorithm"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTSTORE_PROVIDER = "drill.exec.http.jetty.server.sslContextFactory.trustStoreProvider"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTSTORE_TYPE = "drill.exec.http.jetty.server.sslContextFactory.trustStoreType"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_USE_CIPHER_SUITE_ORDER = "drill.exec.http.jetty.server.sslContextFactory.useCipherSuiteOrder"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_VALIDATE_CERTS = "drill.exec.http.jetty.server.sslContextFactory.validateCerts"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_VALIDATE_PEER_CERTS = "drill.exec.http.jetty.server.sslContextFactory.validatePeerCerts"; + public static final String HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_WANT_CLIENT_AUTH = "drill.exec.http.jetty.server.sslContextFactory.wantClientAuth"; + public static final String HTTP_ENABLE_SSL = "drill.exec.http.ssl_enabled"; public static final String HTTP_CLIENT_TIMEOUT = "drill.exec.http.client.timeout"; public static final String HTTP_CORS_ENABLED = "drill.exec.http.cors.enabled"; 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 index b648de5..0e937d0 100644 --- 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 @@ -22,13 +22,12 @@ import com.codahale.metrics.servlets.MetricsServlet; import com.codahale.metrics.servlets.ThreadDumpServlet; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.drill.common.config.DrillConfig; import org.apache.drill.common.exceptions.DrillException; import org.apache.drill.exec.ExecConstants; -import org.apache.drill.exec.ssl.SSLConfig; +import org.apache.drill.exec.server.rest.ssl.SslContextFactoryConfigurator; import org.apache.drill.exec.exception.DrillbitStartupException; import org.apache.drill.exec.expr.fn.registry.FunctionHolder; import org.apache.drill.exec.expr.fn.registry.LocalFunctionRegistry; @@ -40,15 +39,7 @@ import org.apache.drill.exec.server.options.OptionValidator.OptionDescription; import org.apache.drill.exec.server.options.OptionValue; import org.apache.drill.exec.server.rest.auth.DrillErrorHandler; import org.apache.drill.exec.server.rest.auth.DrillHttpSecurityHandlerProvider; -import org.apache.drill.exec.ssl.SSLConfigBuilder; 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.security.SecurityHandler; import org.eclipse.jetty.security.authentication.SessionAuthentication; @@ -71,7 +62,6 @@ import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.glassfish.jersey.servlet.ServletContainer; -import org.joda.time.DateTime; import javax.servlet.DispatcherType; import javax.servlet.http.HttpSession; @@ -82,18 +72,11 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; -import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.TreeSet; @@ -161,26 +144,24 @@ public class WebServer implements AutoCloseable { return; } - final boolean authEnabled = config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED); - int port = config.getInt(ExecConstants.HTTP_PORT); - final boolean portHunt = config.getBoolean(ExecConstants.HTTP_PORT_HUNT); - final int acceptors = config.getInt(ExecConstants.HTTP_JETTY_SERVER_ACCEPTORS); - final int selectors = config.getInt(ExecConstants.HTTP_JETTY_SERVER_SELECTORS); - final int handlers = config.getInt(ExecConstants.HTTP_JETTY_SERVER_HANDLERS); final QueuedThreadPool threadPool = new QueuedThreadPool(2, 2); embeddedJetty = new Server(threadPool); + + final boolean authEnabled = config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED); ServletContextHandler webServerContext = createServletContextHandler(authEnabled); - //Allow for Other Drillbits to make REST calls - FilterHolder filterHolder = new FilterHolder(CrossOriginFilter.class); - filterHolder.setInitParameter("allowedOrigins", "*"); - //Allowing CORS for metrics only - webServerContext.addFilter(filterHolder, STATUS_METRICS_PATH, null); embeddedJetty.setHandler(webServerContext); + final int acceptors = config.getInt(ExecConstants.HTTP_JETTY_SERVER_ACCEPTORS); + final int selectors = config.getInt(ExecConstants.HTTP_JETTY_SERVER_SELECTORS); + int port = config.getInt(ExecConstants.HTTP_PORT); ServerConnector connector = createConnector(port, acceptors, selectors); + + final int handlers = config.getInt(ExecConstants.HTTP_JETTY_SERVER_HANDLERS); threadPool.setMaxThreads(handlers + connector.getAcceptors() + connector.getSelectorManager().getSelectorCount()); embeddedJetty.addConnector(connector); + + final boolean portHunt = config.getBoolean(ExecConstants.HTTP_PORT_HUNT); for (int retry = 0; retry < PORT_HUNT_TRIES; retry++) { connector.setPort(port); try { @@ -264,6 +245,12 @@ public class WebServer implements AutoCloseable { } } + //Allow for Other Drillbits to make REST calls + FilterHolder filterHolder = new FilterHolder(CrossOriginFilter.class); + filterHolder.setInitParameter("allowedOrigins", "*"); + //Allowing CORS for metrics only + servletContextHandler.addFilter(filterHolder, STATUS_METRICS_PATH, null); + return servletContextHandler; } @@ -346,79 +333,9 @@ public class WebServer implements AutoCloseable { */ private ServerConnector createHttpsConnector(int port, int acceptors, int selectors) throws Exception { logger.info("Setting up HTTPS connector for web server"); - - final SslContextFactory sslContextFactory = new SslContextFactory(); - SSLConfig ssl = new SSLConfigBuilder() - .config(config) - .mode(SSLConfig.Mode.SERVER) - .initializeSSLContext(false) - .validateKeyStore(true) - .build(); - if (ssl.isSslValid()) { - logger.info("Using configured SSL settings for web server"); - - sslContextFactory.setKeyStorePath(ssl.getKeyStorePath()); - sslContextFactory.setKeyStorePassword(ssl.getKeyStorePassword()); - sslContextFactory.setKeyManagerPassword(ssl.getKeyPassword()); - if(ssl.hasTrustStorePath()){ - sslContextFactory.setTrustStorePath(ssl.getTrustStorePath()); - if(ssl.hasTrustStorePassword()){ - sslContextFactory.setTrustStorePassword(ssl.getTrustStorePassword()); - } - } - } 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); - } - + SslContextFactory sslContextFactory = new SslContextFactoryConfigurator(config, + workManager.getContext().getEndpoint().getAddress()) + .configureNewSslContextFactory(); final HttpConfiguration httpsConfig = baseHttpConfig(); httpsConfig.addCustomizer(new SecureRequestCustomizer()); diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfigurator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfigurator.java new file mode 100644 index 0000000..a5aa541 --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfigurator.java @@ -0,0 +1,214 @@ +/* + * 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.ssl; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.ssl.SSLConfig; +import org.apache.drill.exec.ssl.SSLConfigBuilder; +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.util.ssl.SslContextFactory; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Configures {@link SslContextFactory} when https is enabled for Web UI + */ +public class SslContextFactoryConfigurator { + private static final Logger logger = LoggerFactory.getLogger(SslContextFactoryConfigurator.class); + + private final DrillConfig config; + private final String drillbitEndpointAddress; + + public SslContextFactoryConfigurator(DrillConfig config, String drillbitEndpointAddress) { + this.config = config; + this.drillbitEndpointAddress = drillbitEndpointAddress; + } + + /** + * Tries to apply ssl options configured by user. If provided configuration isn't valid, + * new self-signed certificate will be generated and used in sslContextFactory. + * + * @return new configured sslContextFactory + * @throws Exception when generation of self-signed certificate failed + */ + public SslContextFactory configureNewSslContextFactory() throws Exception { + SSLConfig sslConf = new SSLConfigBuilder() + .config(config) + .mode(SSLConfig.Mode.SERVER) + .initializeSSLContext(false) + .validateKeyStore(true) + .build(); + final SslContextFactory sslContextFactory = new SslContextFactory(); + if (sslConf.isSslValid()) { + useOptionsConfiguredByUser(sslContextFactory, sslConf); + } else { + useAutoGeneratedSelfSignedCertificate(sslContextFactory); + } + return sslContextFactory; + } + + private void useOptionsConfiguredByUser(SslContextFactory sslFactory, SSLConfig sslConf) { + logger.info("Using configured SSL settings for web server"); + sslFactory.setKeyStorePath(sslConf.getKeyStorePath()); + sslFactory.setKeyStorePassword(sslConf.getKeyStorePassword()); + sslFactory.setKeyManagerPassword(sslConf.getKeyPassword()); + if (sslConf.hasTrustStorePath()) { + sslFactory.setTrustStorePath(sslConf.getTrustStorePath()); + if (sslConf.hasTrustStorePassword()) { + sslFactory.setTrustStorePassword(sslConf.getTrustStorePassword()); + } + } + sslFactory.setProtocol(sslConf.getProtocol()); + sslFactory.setIncludeProtocols(sslConf.getProtocol()); + if (config.hasPath(ExecConstants.HTTP_JETTY_SSL_CONTEXT_FACTORY_OPTIONS_PREFIX)) { + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_CERT_ALIAS, sslFactory::setCertAlias); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_CRL_PATH, sslFactory::setCrlPath); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENABLE_CRLDP, sslFactory::setEnableCRLDP); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENABLE_OCSP, sslFactory::setEnableOCSP); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_ENDPOINT_IDENTIFICATION_ALGORITHM, sslFactory::setEndpointIdentificationAlgorithm); + setStringArrayIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_EXCLUDE_CIPHER_SUITES, sslFactory::setExcludeCipherSuites); + setStringArrayIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_EXCLUDE_PROTOCOLS, sslFactory::setExcludeProtocols); + setStringArrayIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_INCLUDE_CIPHER_SUITES, sslFactory::setIncludeCipherSuites); + setStringArrayIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_INCLUDE_PROTOCOLS, sslFactory::setIncludeProtocols); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEY_MANAGER_FACTORY_ALGORITHM, sslFactory::setKeyManagerFactoryAlgorithm); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEYSTORE_PROVIDER, sslFactory::setKeyStoreProvider); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_KEYSTORE_TYPE, sslFactory::setKeyStoreType); + setIntIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_MAX_CERT_PATH_LENGTH, sslFactory::setMaxCertPathLength); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_NEED_CLIENT_AUTH, sslFactory::setNeedClientAuth); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_OCSP_RESPONDER_URL, sslFactory::setOcspResponderURL); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_PROVIDER, sslFactory::setProvider); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_RENEGOTIATION_ALLOWED, sslFactory::setRenegotiationAllowed); + setIntIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_RENEGOTIATION_LIMIT, sslFactory::setRenegotiationLimit); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SECURE_RANDOM_ALGORITHM, sslFactory::setSecureRandomAlgorithm); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SESSION_CACHING_ENABLED, sslFactory::setSessionCachingEnabled); + setIntIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SSL_SESSION_CACHE_SIZE, sslFactory::setSslSessionCacheSize); + setIntIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SSL_SESSION_TIMEOUT, sslFactory::setSslSessionTimeout); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTMANAGERFACTORY_ALGORITHM, sslFactory::setTrustManagerFactoryAlgorithm); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTSTORE_PROVIDER, sslFactory::setTrustStoreProvider); + setStringIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_TRUSTSTORE_TYPE, sslFactory::setTrustStoreType); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_USE_CIPHER_SUITE_ORDER, sslFactory::setUseCipherSuitesOrder); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_VALIDATE_CERTS, sslFactory::setValidateCerts); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_VALIDATE_PEER_CERTS, sslFactory::setValidatePeerCerts); + setBooleanIfPresent(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_WANT_CLIENT_AUTH, sslFactory::setWantClientAuth); + } + } + + private void setStringArrayIfPresent(String optKey, Consumer<String[]> optSet) { + setIfPresent(optKey, + key -> { + List<String> list = config.getStringList(key); + return list == null ? null : list.toArray(new String[0]); + }, + optSet); + } + + private void setBooleanIfPresent(String optKey, Consumer<Boolean> optSet) { + setIfPresent(optKey, config::getBoolean, optSet); + } + + private void setStringIfPresent(String optKey, Consumer<String> optSet) { + setIfPresent(optKey, config::getString, optSet); + } + + private void setIntIfPresent(String optKey, Consumer<Integer> optSet) { + setIfPresent(optKey, config::getInt, optSet); + } + + private <T> void setIfPresent(String optKey, Function<String, T> optGet, Consumer<T> optSet) { + if (config.hasPath(optKey)) { + T optVal = optGet.apply(optKey); + if (optVal != null) { + optSet.accept(optVal); + } + } + } + + + private void useAutoGeneratedSelfSignedCertificate(SslContextFactory sslContextFactory) throws Exception { + 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(); + + + // 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, drillbitEndpointAddress); + + final DateTime now = DateTime.now(); + 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); + } +} diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/ssl/SSLConfigServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/ssl/SSLConfigServer.java index e0b8f54..53d2616 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/ssl/SSLConfigServer.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ssl/SSLConfigServer.java @@ -90,7 +90,7 @@ public class SSLConfigServer extends SSLConfig { String keyPass = getConfigParam(ExecConstants.SSL_KEY_PASSWORD, resolveHadoopPropertyName(HADOOP_SSL_KEYSTORE_KEYPASSWORD_TPL_KEY, mode)); keyPassword = keyPass.isEmpty() ? keyStorePassword : keyPass; - protocol = getConfigParamWithDefault(ExecConstants.SSL_PROTOCOL, DEFAULT_SSL_PROTOCOL); + protocol = config.getString(ExecConstants.SSL_PROTOCOL); // If provider is OPENSSL then to debug or run this code in an IDE, you will need to enable // the dependency on netty-tcnative with the correct classifier for the platform you use. // This can be done by enabling the openssl profile. @@ -99,7 +99,7 @@ public class SSLConfigServer extends SSLConfig { // or from your local maven repository: // ~/.m2/repository/kr/motd/maven/os-maven-plugin/1.6.1/os-maven-plugin-1.6.1.jar // Note that installing this plugin may require you to start with a new workspace - provider = getConfigParamWithDefault(ExecConstants.SSL_PROVIDER, DEFAULT_SSL_PROVIDER); + provider = config.getString(ExecConstants.SSL_PROVIDER); } public void validateKeyStore() throws DrillException { @@ -222,18 +222,6 @@ public class SSLConfigServer extends SSLConfig { return value; } - private String getConfigParamWithDefault(String name, String defaultValue) { - String value = ""; - if (config.hasPath(name)) { - value = config.getString(name); - } - if (value.isEmpty()) { - value = defaultValue; - } - value = value.trim(); - return value; - } - private String resolveHadoopPropertyName(String nameTemplate, Mode mode) { return MessageFormat.format(nameTemplate, mode.toString().toLowerCase()); } diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfiguratorTest.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfiguratorTest.java new file mode 100644 index 0000000..6a184fb --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/ssl/SslContextFactoryConfiguratorTest.java @@ -0,0 +1,71 @@ +/* + * 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.ssl; + +import java.util.Arrays; + +import org.apache.drill.categories.OptionsTest; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.test.ClusterFixture; +import org.apache.drill.test.ClusterFixtureBuilder; +import org.apache.drill.test.ClusterTest; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@Category(OptionsTest.class) +public class SslContextFactoryConfiguratorTest extends ClusterTest { + + private static SslContextFactoryConfigurator sslContextFactoryConfigurator; + + @BeforeClass + public static void setUpClass() throws Exception { + ClusterFixtureBuilder fixtureBuilder = ClusterFixture.builder(dirTestWatcher) + // imitate proper ssl config for embedded web + .configProperty(ExecConstants.SSL_PROTOCOL, "TLSv1.2") + .configProperty(ExecConstants.HTTP_ENABLE_SSL, true) + .configProperty(ExecConstants.HTTP_TRUSTSTORE_PATH, "/tmp/ssl/cacerts.jks") + .configProperty(ExecConstants.HTTP_TRUSTSTORE_PASSWORD, "passphrase") + .configProperty(ExecConstants.HTTP_KEYSTORE_PATH, "/tmp/ssl/keystore.jks") + .configProperty(ExecConstants.HTTP_KEYSTORE_PASSWORD, "passphrase") + .configProperty(ExecConstants.SSL_KEY_PASSWORD, "passphrase") + .configProperty(ExecConstants.SSL_USE_HADOOP_CONF, false) + + // few specific opts for Jetty sslContextFactory + .configProperty(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_SSL_SESSION_TIMEOUT, 30) + .configProperty(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_WANT_CLIENT_AUTH, true); + fixtureBuilder.configBuilder().put(ExecConstants.HTTP_JETTY_SERVER_SSL_CONTEXT_FACTORY_EXCLUDE_PROTOCOLS, + Arrays.asList("TLSv1.0", "TLSv1.1")); + startCluster(fixtureBuilder); + sslContextFactoryConfigurator = new SslContextFactoryConfigurator(cluster.config(), cluster.drillbit().getContext().getEndpoint().getAddress()); + } + + @Test + public void configureNewSslContextFactory() throws Exception { + SslContextFactory sslContextFactory = sslContextFactoryConfigurator.configureNewSslContextFactory(); + + assertEquals(30, sslContextFactory.getSslSessionTimeout()); + assertTrue(sslContextFactory.getWantClientAuth()); + assertArrayEquals(new String[]{"TLSv1.0", "TLSv1.1"}, sslContextFactory.getExcludeProtocols()); + } +}
