blob: 353a5449844a248addedb0706ebe7a80e582d421 [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.hadoop.hbase.zookeeper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.login.AppConfigurationEntry;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.authentication.util.KerberosUtil;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.client.ZooKeeperSaslClient;
import org.apache.zookeeper.server.ZooKeeperSaslServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides ZooKeeper authentication services for both client and server processes.
*/
@InterfaceAudience.Private
public final class ZKAuthentication {
private static final Logger LOG = LoggerFactory.getLogger(ZKAuthentication.class);
private ZKAuthentication() {
}
/**
* Log in the current zookeeper server process using the given configuration keys for the
* credential file and login principal.
* <p>
* <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without
* security features), this will safely be ignored.
* </p>
* @param conf The configuration data to use
* @param keytabFileKey Property key used to configure the path to the credential file
* @param userNameKey Property key used to configure the login principal
* @param hostname Current hostname to use in any credentials
* @throws IOException underlying exception from SecurityUtil.login() call
*/
public static void loginServer(Configuration conf, String keytabFileKey, String userNameKey,
String hostname) throws IOException {
login(conf, keytabFileKey, userNameKey, hostname, ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY,
JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME);
}
/**
* Log in the current zookeeper client using the given configuration keys for the credential file
* and login principal.
* <p>
* <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without
* security features), this will safely be ignored.
* </p>
* @param conf The configuration data to use
* @param keytabFileKey Property key used to configure the path to the credential file
* @param userNameKey Property key used to configure the login principal
* @param hostname Current hostname to use in any credentials
* @throws IOException underlying exception from SecurityUtil.login() call
*/
public static void loginClient(Configuration conf, String keytabFileKey, String userNameKey,
String hostname) throws IOException {
login(conf, keytabFileKey, userNameKey, hostname, ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY,
JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME);
}
/**
* Log in the current process using the given configuration keys for the credential file and login
* principal.
* <p>
* <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without
* security features), this will safely be ignored.
* </p>
* @param conf The configuration data to use
* @param keytabFileKey Property key used to configure the path to the credential file
* @param userNameKey Property key used to configure the login principal
* @param hostname Current hostname to use in any credentials
* @param loginContextProperty property name to expose the entry name
* @param loginContextName jaas entry name
* @throws IOException underlying exception from SecurityUtil.login() call
*/
private static void login(Configuration conf, String keytabFileKey, String userNameKey,
String hostname, String loginContextProperty, String loginContextName) throws IOException {
if (!isSecureZooKeeper(conf)) {
return;
}
// User has specified a jaas.conf, keep this one as the good one.
// HBASE_OPTS="-Djava.security.auth.login.config=jaas.conf"
if (System.getProperty("java.security.auth.login.config") != null) {
return;
}
// No keytab specified, no auth
String keytabFilename = conf.get(keytabFileKey);
if (keytabFilename == null) {
LOG.warn("no keytab specified for: {}", keytabFileKey);
return;
}
String principalConfig = conf.get(userNameKey, System.getProperty("user.name"));
String principalName = SecurityUtil.getServerPrincipal(principalConfig, hostname);
// Initialize the "jaas.conf" for keyTab/principal,
// If keyTab is not specified use the Ticket Cache.
// and set the zookeeper login context name.
JaasConfiguration jaasConf =
new JaasConfiguration(loginContextName, principalName, keytabFilename);
javax.security.auth.login.Configuration.setConfiguration(jaasConf);
System.setProperty(loginContextProperty, loginContextName);
}
/**
* Returns {@code true} when secure authentication is enabled (whether
* {@code hbase.security.authentication} is set to "{@code kerberos}").
*/
public static boolean isSecureZooKeeper(Configuration conf) {
// Detection for embedded HBase client with jaas configuration
// defined for third party programs.
try {
javax.security.auth.login.Configuration testConfig =
javax.security.auth.login.Configuration.getConfiguration();
if (
testConfig.getAppConfigurationEntry("Client") == null
&& testConfig
.getAppConfigurationEntry(JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME) == null
&& testConfig
.getAppConfigurationEntry(JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME) == null
&& conf.get(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL) == null
&& conf.get(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL) == null
) {
return false;
}
} catch (Exception e) {
// No Jaas configuration defined.
return false;
}
// Master & RSs uses hbase.zookeeper.client.*
return "kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication"));
}
/**
* A JAAS configuration that defines the login modules that we want to use for ZooKeeper login.
*/
private static class JaasConfiguration extends javax.security.auth.login.Configuration {
private static final Logger LOG = LoggerFactory.getLogger(JaasConfiguration.class);
public static final String SERVER_KEYTAB_KERBEROS_CONFIG_NAME =
"zookeeper-server-keytab-kerberos";
public static final String CLIENT_KEYTAB_KERBEROS_CONFIG_NAME =
"zookeeper-client-keytab-kerberos";
private static final Map<String, String> BASIC_JAAS_OPTIONS = new HashMap<>();
static {
String jaasEnvVar = System.getenv("HBASE_JAAS_DEBUG");
if ("true".equalsIgnoreCase(jaasEnvVar)) {
BASIC_JAAS_OPTIONS.put("debug", "true");
}
}
private static final Map<String, String> KEYTAB_KERBEROS_OPTIONS = new HashMap<>();
static {
KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
KEYTAB_KERBEROS_OPTIONS.put("refreshKrb5Config", "true");
KEYTAB_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS);
}
private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN =
new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, KEYTAB_KERBEROS_OPTIONS);
private static final AppConfigurationEntry[] KEYTAB_KERBEROS_CONF =
new AppConfigurationEntry[] { KEYTAB_KERBEROS_LOGIN };
private javax.security.auth.login.Configuration baseConfig;
private final String loginContextName;
private final boolean useTicketCache;
private final String keytabFile;
private final String principal;
public JaasConfiguration(String loginContextName, String principal, String keytabFile) {
this(loginContextName, principal, keytabFile, keytabFile == null || keytabFile.length() == 0);
}
private JaasConfiguration(String loginContextName, String principal, String keytabFile,
boolean useTicketCache) {
try {
this.baseConfig = javax.security.auth.login.Configuration.getConfiguration();
} catch (SecurityException e) {
this.baseConfig = null;
}
this.loginContextName = loginContextName;
this.useTicketCache = useTicketCache;
this.keytabFile = keytabFile;
this.principal = principal;
LOG.info("JaasConfiguration loginContextName={} principal={} useTicketCache={} keytabFile={}",
loginContextName, principal, useTicketCache, keytabFile);
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
if (loginContextName.equals(appName)) {
if (!useTicketCache) {
KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile);
KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
}
KEYTAB_KERBEROS_OPTIONS.put("principal", principal);
KEYTAB_KERBEROS_OPTIONS.put("useTicketCache", useTicketCache ? "true" : "false");
return KEYTAB_KERBEROS_CONF;
}
if (baseConfig != null) {
return baseConfig.getAppConfigurationEntry(appName);
}
return (null);
}
}
}