blob: 8c98764bfe7343744c9b88ae04942e096efcc97b [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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.tez.http;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.authentication.client.ConnectionConfigurator;
import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory;
import org.apache.hadoop.security.ssl.KeyStoresFactory;
import org.apache.hadoop.security.ssl.SSLFactory.Mode;
import org.apache.hadoop.security.ssl.SSLHostnameVerifier;
import org.apache.hadoop.util.ReflectionUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.GeneralSecurityException;
import java.util.Objects;
import static org.apache.hadoop.security.ssl.SSLFactory.KEYSTORES_FACTORY_CLASS_KEY;
import static org.apache.hadoop.security.ssl.SSLFactory.SSL_CLIENT_CONF_KEY;
import static org.apache.hadoop.security.ssl.SSLFactory.SSL_HOSTNAME_VERIFIER_KEY;
import static org.apache.hadoop.security.ssl.SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY;
import static org.apache.hadoop.security.ssl.SSLFactory.SSL_SERVER_CONF_KEY;
/**
* Factory that creates SSLEngine and SSLSocketFactory instances using
* Hadoop configuration information.
* <p/>
* This SSLFactory uses a {@link org.apache.hadoop.security.ssl.ReloadingX509TrustManager} instance,
* which reloads public keys if the truststore file changes.
* <p/>
* This factory is used to configure HTTPS in Hadoop HTTP based endpoints, both
* client and server.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class SSLFactory implements ConnectionConfigurator {
public static final String SSL_ENABLED_PROTOCOLS = "hadoop.ssl.enabled.protocols";
public static final String DEFAULT_SSL_ENABLED_PROTOCOLS = "TLSv1";
public static final boolean DEFAULT_SSL_REQUIRE_CLIENT_CERT = false;
private Configuration conf;
private Mode mode;
private boolean requireClientCert;
private SSLContext context;
private HostnameVerifier hostnameVerifier;
private KeyStoresFactory keystoresFactory;
private String[] enabledProtocols = null;
/**
* Creates an SSLFactory.
*
* @param mode SSLFactory mode, client or server.
* @param conf Hadoop configuration from where the SSLFactory configuration
* will be read.
* @throws NullPointerException if {@code mode} or {@code conf} is {@code null}
*/
public SSLFactory(Mode mode, Configuration conf) {
this.conf = Objects.requireNonNull(conf);
this.mode = Objects.requireNonNull(mode, "mode cannot be NULL");
requireClientCert = conf.getBoolean(SSL_REQUIRE_CLIENT_CERT_KEY,
DEFAULT_SSL_REQUIRE_CLIENT_CERT);
// Rest of ssl configs are pre-populated in incoming conf payload
conf.setBoolean(SSL_REQUIRE_CLIENT_CERT_KEY, requireClientCert);
Class<? extends KeyStoresFactory> klass
= conf.getClass(KEYSTORES_FACTORY_CLASS_KEY,
FileBasedKeyStoresFactory.class, KeyStoresFactory.class);
keystoresFactory = ReflectionUtils.newInstance(klass, conf);
enabledProtocols = conf.getStrings(SSL_ENABLED_PROTOCOLS, DEFAULT_SSL_ENABLED_PROTOCOLS);
}
private Configuration readSSLConfiguration(Mode mode) {
Configuration sslConf = new Configuration(false);
sslConf.setBoolean(SSL_REQUIRE_CLIENT_CERT_KEY, requireClientCert);
String sslConfResource;
if (mode == Mode.CLIENT) {
sslConfResource = conf.get(SSL_CLIENT_CONF_KEY, "ssl-client.xml");
} else {
sslConfResource = conf.get(SSL_SERVER_CONF_KEY, "ssl-server.xml");
}
sslConf.addResource(sslConfResource);
return sslConf;
}
/**
* Initializes the factory.
*
* @throws GeneralSecurityException thrown if an SSL initialization error
* happened.
* @throws IOException thrown if an IO error happened while reading the SSL
* configuration.
*/
public void init() throws GeneralSecurityException, IOException {
keystoresFactory.init(mode);
context = SSLContext.getInstance("TLS");
context.init(keystoresFactory.getKeyManagers(),
keystoresFactory.getTrustManagers(), null);
context.getDefaultSSLParameters().setProtocols(enabledProtocols);
hostnameVerifier = getHostnameVerifier(conf);
}
private HostnameVerifier getHostnameVerifier(Configuration conf)
throws GeneralSecurityException, IOException {
return getHostnameVerifier(conf.get(SSL_HOSTNAME_VERIFIER_KEY, "DEFAULT").
trim().toUpperCase());
}
public static HostnameVerifier getHostnameVerifier(String verifier)
throws GeneralSecurityException, IOException {
HostnameVerifier hostnameVerifier;
if (verifier.equals("DEFAULT")) {
hostnameVerifier = SSLHostnameVerifier.DEFAULT;
} else if (verifier.equals("DEFAULT_AND_LOCALHOST")) {
hostnameVerifier = SSLHostnameVerifier.DEFAULT_AND_LOCALHOST;
} else if (verifier.equals("STRICT")) {
hostnameVerifier = SSLHostnameVerifier.STRICT;
} else if (verifier.equals("STRICT_IE6")) {
hostnameVerifier = SSLHostnameVerifier.STRICT_IE6;
} else if (verifier.equals("ALLOW_ALL")) {
hostnameVerifier = SSLHostnameVerifier.ALLOW_ALL;
} else {
throw new GeneralSecurityException("Invalid hostname verifier: " +
verifier);
}
return hostnameVerifier;
}
/**
* Releases any resources being used.
*/
public void destroy() {
keystoresFactory.destroy();
}
/**
* Returns the SSLFactory KeyStoresFactory instance.
*
* @return the SSLFactory KeyStoresFactory instance.
*/
public KeyStoresFactory getKeystoresFactory() {
return keystoresFactory;
}
/**
* Returns a configured SSLSocketFactory.
*
* @return the configured SSLSocketFactory.
* @throws GeneralSecurityException thrown if the SSLSocketFactory could not
* be initialized.
* @throws IOException thrown if and IO error occurred while loading
* the server keystore.
*/
public SSLSocketFactory createSSLSocketFactory() throws GeneralSecurityException, IOException {
if (mode != Mode.CLIENT) {
throw new IllegalStateException("Factory is in CLIENT mode");
}
return context.getSocketFactory();
}
/**
* Returns the hostname verifier it should be used in HttpsURLConnections.
*
* @return the hostname verifier.
*/
public HostnameVerifier getHostnameVerifier() {
if (mode != Mode.CLIENT) {
throw new IllegalStateException("Factory is in CLIENT mode");
}
return hostnameVerifier;
}
/**
* If the given {@link HttpURLConnection} is an {@link HttpsURLConnection}
* configures the connection with the {@link SSLSocketFactory} and
* {@link HostnameVerifier} of this SSLFactory, otherwise does nothing.
*
* @param conn the {@link HttpURLConnection} instance to configure.
* @return the configured {@link HttpURLConnection} instance.
* @throws IOException if an IO error occurred.
*/
@Override
public HttpURLConnection configure(HttpURLConnection conn) throws IOException {
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection sslConn = (HttpsURLConnection) conn;
try {
sslConn.setSSLSocketFactory(createSSLSocketFactory());
} catch (GeneralSecurityException ex) {
throw new IOException(ex);
}
sslConn.setHostnameVerifier(getHostnameVerifier());
conn = sslConn;
}
return conn;
}
/**
* Set ssl context for {@link org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder}
*
* @param builder {@link org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder} instance to
* configure.
* @throws IOException if an IO error occurred.
*/
public void configure(DefaultAsyncHttpClientConfig.Builder builder) throws IOException {
if (builder != null) {
JdkSslContext jdkSslContext =
new JdkSslContext(context, mode.equals(Mode.CLIENT), /* ciphers */null,
SupportedCipherSuiteFilter.INSTANCE, /* ApplicationProtocolConfig */ null,
requireClientCert ? ClientAuth.REQUIRE : ClientAuth.OPTIONAL, enabledProtocols,
/* startTls */ true);
builder.setSslContext(jdkSslContext);
}
}
}