Skip to content

Commit

Permalink
HBASE-26616 Refactor code related to ZooKeeper authentication (#3973)
Browse files Browse the repository at this point in the history
This refactor reduces the size and scope of the `ZKUtil` class. The core of this refactor is
moving the `login*` methods from `ZKUtil` into their own class, `ZKAuthentication`. The class
`JaasConfiguration` is also moved along with them.

Signed-off-by: Andrew Purtell <apurtell@apache.org>
Signed-off-by: Duo Zhang <zhangduo@apache.org>
  • Loading branch information
ndimiduk committed Jan 11, 2022
1 parent 6a060ad commit 9ef7f59
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 302 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import org.apache.hadoop.hbase.util.JVMClusterUtil;
import org.apache.hadoop.hbase.util.ServerCommandLine;
import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
import org.apache.hadoop.hbase.zookeeper.ZKAuthentication;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.KeeperException;
Expand Down Expand Up @@ -214,7 +214,7 @@ private int startMaster() {
}

// login the zookeeper server principal (if using security)
ZKUtil.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
ZKAuthentication.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, null);
int localZKClusterSessionTimeout =
conf.getInt(HConstants.ZK_SESSION_TIMEOUT + ".localHBaseCluster", 10*1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
import org.apache.hadoop.hbase.wal.WALFactory;
import org.apache.hadoop.hbase.zookeeper.ClusterStatusTracker;
import org.apache.hadoop.hbase.zookeeper.MasterAddressTracker;
import org.apache.hadoop.hbase.zookeeper.ZKAuthentication;
import org.apache.hadoop.hbase.zookeeper.ZKClusterId;
import org.apache.hadoop.hbase.zookeeper.ZKNodeTracker;
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
Expand Down Expand Up @@ -637,7 +638,7 @@ public HRegionServer(final Configuration conf) throws IOException {
rpcRetryingCallerFactory = RpcRetryingCallerFactory.instantiate(this.conf);

// login the zookeeper client principal (if using security)
ZKUtil.loginClient(this.conf, HConstants.ZK_CLIENT_KEYTAB_FILE,
ZKAuthentication.loginClient(this.conf, HConstants.ZK_CLIENT_KEYTAB_FILE,
HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, hostName);
// login the server principal (if using secure Hadoop)
login(userProvider, hostName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ public void testOutsideHBaseNodeACL() throws Exception {
@Test
public void testIsZooKeeperSecure() throws Exception {
boolean testJaasConfig =
ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
ZKAuthentication.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
assertEquals(testJaasConfig, secureZKAvailable);
// Define Jaas configuration without ZooKeeper Jaas config
File saslConfFile = File.createTempFile("tmp", "fakeJaas.conf");
Expand All @@ -286,7 +286,8 @@ public void testIsZooKeeperSecure() throws Exception {
System.setProperty("java.security.auth.login.config",
saslConfFile.getAbsolutePath());

testJaasConfig = ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
testJaasConfig = ZKAuthentication.isSecureZooKeeper(
new Configuration(TEST_UTIL.getConfiguration()));
assertFalse(testJaasConfig);
saslConfFile.delete();
}
Expand All @@ -300,21 +301,21 @@ public void testIsZooKeeperSecureWithProgrammaticConfig() throws Exception {
javax.security.auth.login.Configuration.setConfiguration(new DummySecurityConfiguration());

Configuration config = new Configuration(HBaseConfiguration.create());
boolean testJaasConfig = ZKUtil.isSecureZooKeeper(config);
boolean testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
assertFalse(testJaasConfig);

// Now set authentication scheme to Kerberos still it should return false
// because no configuration set
config.set("hbase.security.authentication", "kerberos");
testJaasConfig = ZKUtil.isSecureZooKeeper(config);
testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
assertFalse(testJaasConfig);

// Now set programmatic options related to security
config.set(HConstants.ZK_CLIENT_KEYTAB_FILE, "/dummy/file");
config.set(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, "dummy");
config.set(HConstants.ZK_SERVER_KEYTAB_FILE, "/dummy/file");
config.set(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, "dummy");
testJaasConfig = ZKUtil.isSecureZooKeeper(config);
testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
assertTrue(testJaasConfig);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static void main(String[] args) {
zkConfig.parseProperties(zkProperties);

// login the zookeeper server principal (if using security)
ZKUtil.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
ZKAuthentication.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
HConstants.ZK_SERVER_KERBEROS_PRINCIPAL,
zkConfig.getClientPortAddress().getHostName());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package org.apache.hadoop.hbase.zookeeper;

import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hbase.HBaseConfiguration;
Expand Down Expand Up @@ -61,7 +60,7 @@ private static void resetAcls(final ZKWatcher zkw, final String znode,
zk.setACL(znode, ZooDefs.Ids.OPEN_ACL_UNSAFE, -1);
} else {
LOG.info(" - set ACLs for {}", znode);
zk.setACL(znode, ZKUtil.createACL(zkw, znode, true), -1);
zk.setACL(znode, zkw.createACL(znode, true), -1);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*
* 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);
}
}
}
Loading

0 comments on commit 9ef7f59

Please sign in to comment.