blob: c3ff0c5dba8e4551694b8b9547439fc028b478d3 [file] [log] [blame]
/*
* 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.ssl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.drill.common.exceptions.DrillException;
import org.apache.drill.exec.memory.BufferAllocator;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
public abstract class SSLConfig {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(SSLConfig.class);
public static final String DEFAULT_SSL_PROVIDER = "JDK"; // JDK or OPENSSL
public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2";
public static final int DEFAULT_SSL_HANDSHAKE_TIMEOUT_MS = 10 * 1000; // 10 seconds
// Either the Netty SSL context or the JDK SSL context will be initialized
// The JDK SSL context is use iff the useSystemTrustStore setting is enabled.
protected SslContext nettySslContext;
protected SSLContext jdkSSlContext;
private static final boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("win") >= 0;
private static final boolean isMacOs = System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0;
public static final String HADOOP_SSL_CONF_TPL_KEY = "hadoop.ssl.{0}.conf";
public static final String HADOOP_SSL_KEYSTORE_LOCATION_TPL_KEY = "ssl.{0}.keystore.location";
public static final String HADOOP_SSL_KEYSTORE_PASSWORD_TPL_KEY = "ssl.{0}.keystore.password";
public static final String HADOOP_SSL_KEYSTORE_TYPE_TPL_KEY = "ssl.{0}.keystore.type";
public static final String HADOOP_SSL_KEYSTORE_KEYPASSWORD_TPL_KEY =
"ssl.{0}.keystore.keypassword";
public static final String HADOOP_SSL_TRUSTSTORE_LOCATION_TPL_KEY = "ssl.{0}.truststore.location";
public static final String HADOOP_SSL_TRUSTSTORE_PASSWORD_TPL_KEY = "ssl.{0}.truststore.password";
public static final String HADOOP_SSL_TRUSTSTORE_TYPE_TPL_KEY = "ssl.{0}.truststore.type";
// copy of Hadoop's SSLFactory.Mode. Done so that we do not
// need to include hadoop-common as a dependency in
// jdbc-all-jar.
public enum Mode { CLIENT, SERVER };
public SSLConfig() {
}
public abstract void validateKeyStore() throws DrillException;
// We need to use different SSLContext objects depending on what the user has chosen
// For most uses we will use the Netty SslContext class. This allows us to use either
// the JDK implementation or the OpenSSL implementation. However if the user wants to
// use the system trust store, then the only way to access it is via the JDK's
// SSLContext class. (See the createSSLEngine method below).
public abstract SslContext initNettySslContext() throws DrillException;
public abstract SSLContext initJDKSSLContext() throws DrillException;
public abstract boolean isUserSslEnabled();
public abstract boolean isHttpsEnabled();
public abstract String getKeyStoreType();
public abstract String getKeyStorePath();
public abstract String getKeyStorePassword();
public abstract String getKeyPassword();
public abstract String getTrustStoreType();
public abstract boolean hasTrustStorePath();
public abstract String getTrustStorePath();
public abstract boolean hasTrustStorePassword();
public abstract String getTrustStorePassword();
public abstract String getProtocol();
public abstract SslProvider getProvider();
public abstract int getHandshakeTimeout();
public abstract Mode getMode();
public abstract boolean disableHostVerification();
public abstract boolean disableCertificateVerification();
public abstract boolean useSystemTrustStore();
public abstract boolean isSslValid();
public SslContext getNettySslContext() {
return nettySslContext;
}
public TrustManagerFactory initializeTrustManagerFactory() throws DrillException {
TrustManagerFactory tmf;
KeyStore ts = null;
//Support Windows/MacOs system trust store
try {
String trustStoreType = getTrustStoreType();
if ((isWindows || isMacOs) && useSystemTrustStore()) {
// This is valid for MS-Windows and MacOs
logger.debug("Initializing System truststore.");
ts = KeyStore.getInstance(!trustStoreType.isEmpty() ? trustStoreType : KeyStore.getDefaultType());
ts.load(null, null);
} else if (!getTrustStorePath().isEmpty()) {
// if truststore is not provided then we will use the default. Note that the default depends on
// the TrustManagerFactory that in turn depends on the Security Provider.
// Use null as the truststore which will result in the default truststore being picked up
logger.debug("Initializing truststore {}.", getTrustStorePath());
ts = KeyStore.getInstance(!trustStoreType.isEmpty() ? trustStoreType : KeyStore.getDefaultType());
InputStream tsStream = new FileInputStream(getTrustStorePath());
ts.load(tsStream, getTrustStorePassword().toCharArray());
} else {
logger.debug("Initializing default truststore.");
}
if (disableCertificateVerification()) {
tmf = InsecureTrustManagerFactory.INSTANCE;
} else {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
}
tmf.init(ts);
} catch (Exception e) {
// Catch any SSL initialization Exceptions here and abort.
throw new DrillException(
new StringBuilder()
.append("Exception while initializing the truststore: [")
.append(e.getMessage())
.append("]. ")
.toString(), e);
}
return tmf;
}
public KeyManagerFactory initializeKeyManagerFactory() throws DrillException {
KeyManagerFactory kmf;
String keyStorePath = getKeyStorePath();
String keyStorePassword = getKeyStorePassword();
String keyStoreType = getKeyStoreType();
try {
if (keyStorePath.isEmpty()) {
throw new DrillException("No Keystore provided.");
}
KeyStore ks =
KeyStore.getInstance(!keyStoreType.isEmpty() ? keyStoreType : KeyStore.getDefaultType());
//initialize the key manager factory
// Will throw an exception if the file is not found/accessible.
InputStream ksStream = new FileInputStream(keyStorePath);
// A key password CANNOT be null or an empty string.
if (keyStorePassword.isEmpty()) {
throw new DrillException("The Keystore password cannot be empty.");
}
ks.load(ksStream, keyStorePassword.toCharArray());
// Empty Keystore. (Remarkably, it is possible to do this).
if (ks.size() == 0) {
throw new DrillException("The Keystore has no entries.");
}
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, getKeyPassword().toCharArray());
} catch (Exception e) {
throw new DrillException(
new StringBuilder()
.append("Exception while initializing the keystore: [")
.append(e.getMessage())
.append("]. ")
.toString());
}
return kmf;
}
public void initContext() throws DrillException {
if ((isWindows || isMacOs) && useSystemTrustStore()) {
initJDKSSLContext();
logger.debug("Initialized Windows/MacOs SSL context using JDK.");
} else {
initNettySslContext();
logger.debug("Initialized SSL context.");
}
return;
}
public SSLEngine createSSLEngine(BufferAllocator allocator, String peerHost, int peerPort) {
SSLEngine engine;
if ((isWindows || isMacOs) && useSystemTrustStore()) {
if (peerHost != null) {
engine = jdkSSlContext.createSSLEngine(peerHost, peerPort);
logger.debug("Initializing Windows/MacOs SSLEngine with hostname.");
} else {
engine = jdkSSlContext.createSSLEngine();
logger.debug("Initializing Windows/MacOs SSLEngine with no hostname.");
}
} else {
if (peerHost != null) {
engine = nettySslContext.newEngine(allocator.getAsByteBufAllocator(), peerHost, peerPort);
logger.debug("Initializing SSLEngine with hostname.");
} else {
engine = nettySslContext.newEngine(allocator.getAsByteBufAllocator());
logger.debug("Initializing SSLEngine with no hostname.");
}
}
return engine;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SSL is ")
.append(isUserSslEnabled()?"":" not ")
.append("enabled.\n");
sb.append("HTTPS is ")
.append(isHttpsEnabled()?"":" not ")
.append("enabled.\n");
if(isUserSslEnabled() || isHttpsEnabled()) {
sb.append("SSL Configuration :")
.append("OS:").append(System.getProperty("os.name"))
.append("\n\tUsing system trust store: ").append(useSystemTrustStore())
.append("\n\tprotocol: ").append(getProtocol())
.append("\n\tkeyStoreType: ").append(getKeyStoreType())
.append("\n\tkeyStorePath: ").append(getKeyStorePath())
.append("\n\tkeyStorePassword: ").append(getPrintablePassword(getKeyStorePassword()))
.append("\n\tkeyPassword: ").append(getPrintablePassword(getKeyPassword()))
.append("\n\ttrustStoreType: ").append(getTrustStoreType())
.append("\n\ttrustStorePath: ").append(getTrustStorePath())
.append("\n\ttrustStorePassword: ").append(getPrintablePassword(getTrustStorePassword()))
.append("\n\thandshakeTimeout: ").append(getHandshakeTimeout())
.append("\n\tdisableHostVerification: ").append(disableHostVerification())
.append("\n\tdisableCertificateVerification: ").append(disableCertificateVerification());
}
return sb.toString();
}
private String getPrintablePassword(String password) {
StringBuilder sb = new StringBuilder();
if(password == null || password.length()<2 ){
return password;
}
sb.append(password.charAt(0)).append("****").append(password.charAt(password.length()-1));
return sb.toString();
}
}