diff --git a/jdbc/src/java/org/apache/hive/jdbc/Utils.java b/jdbc/src/java/org/apache/hive/jdbc/Utils.java index 9b08fd9..de7b5e9 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/Utils.java +++ b/jdbc/src/java/org/apache/hive/jdbc/Utils.java @@ -295,10 +295,6 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep // key=value pattern Pattern pattern = Pattern.compile("([^;]*)=([^;]*)[;]?"); - Map sessionVarMap = connParams.getSessionVars(); - Map hiveConfMap = connParams.getHiveConfs(); - Map hiveVarMap = connParams.getHiveVars(); - // dbname and session settings String sessVars = jdbcURI.getPath(); if ((sessVars != null) && !sessVars.isEmpty()) { @@ -315,7 +311,7 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep if (sessVars != null) { Matcher sessMatcher = pattern.matcher(sessVars); while (sessMatcher.find()) { - if (sessionVarMap.put(sessMatcher.group(1), sessMatcher.group(2)) != null) { + if (connParams.getSessionVars().put(sessMatcher.group(1), sessMatcher.group(2)) != null) { throw new JdbcUriParseException("Bad URL format: Multiple values for property " + sessMatcher.group(1)); } @@ -332,7 +328,7 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep if (confStr != null) { Matcher confMatcher = pattern.matcher(confStr); while (confMatcher.find()) { - hiveConfMap.put(confMatcher.group(1), confMatcher.group(2)); + connParams.getHiveConfs().put(confMatcher.group(1), confMatcher.group(2)); } } @@ -341,7 +337,7 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep if (varStr != null) { Matcher varMatcher = pattern.matcher(varStr); while (varMatcher.find()) { - hiveVarMap.put(varMatcher.group(1), varMatcher.group(2)); + connParams.getHiveVars().put(varMatcher.group(1), varMatcher.group(2)); } } @@ -350,19 +346,19 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep String usageUrlBase = "jdbc:hive2://:/dbName;"; // Handle deprecation of AUTH_QOP_DEPRECATED newUsage = usageUrlBase + JdbcConnectionParams.AUTH_QOP + "="; - handleParamDeprecation(sessionVarMap, sessionVarMap, JdbcConnectionParams.AUTH_QOP_DEPRECATED, - JdbcConnectionParams.AUTH_QOP, newUsage); + handleParamDeprecation(connParams.getSessionVars(), connParams.getSessionVars(), + JdbcConnectionParams.AUTH_QOP_DEPRECATED, JdbcConnectionParams.AUTH_QOP, newUsage); // Handle deprecation of TRANSPORT_MODE_DEPRECATED newUsage = usageUrlBase + JdbcConnectionParams.TRANSPORT_MODE + "="; - handleParamDeprecation(hiveConfMap, sessionVarMap, + handleParamDeprecation(connParams.getHiveConfs(), connParams.getSessionVars(), JdbcConnectionParams.TRANSPORT_MODE_DEPRECATED, JdbcConnectionParams.TRANSPORT_MODE, newUsage); // Handle deprecation of HTTP_PATH_DEPRECATED newUsage = usageUrlBase + JdbcConnectionParams.HTTP_PATH + "="; - handleParamDeprecation(hiveConfMap, sessionVarMap, JdbcConnectionParams.HTTP_PATH_DEPRECATED, - JdbcConnectionParams.HTTP_PATH, newUsage); + handleParamDeprecation(connParams.getHiveConfs(), connParams.getSessionVars(), + JdbcConnectionParams.HTTP_PATH_DEPRECATED, JdbcConnectionParams.HTTP_PATH, newUsage); // Extract host, port if (connParams.isEmbeddedMode()) { @@ -374,6 +370,7 @@ public static JdbcConnectionParams parseURL(String uri) throws JdbcUriParseExcep // Else substitute the dummy authority with a resolved one. // In case of dynamic service discovery using ZooKeeper, it picks a server uri from ZooKeeper String resolvedAuthorityString = resolveAuthority(connParams); + LOG.info("Resolved authority: " + resolvedAuthorityString); uri = uri.replace(dummyAuthorityString, resolvedAuthorityString); connParams.setJdbcUriString(uri); // Create a Java URI from the resolved URI for extracting the host/port diff --git a/ql/src/java/org/apache/hadoop/hive/ql/util/ZooKeeperHiveHelper.java b/ql/src/java/org/apache/hadoop/hive/ql/util/ZooKeeperHiveHelper.java index d9faa45..11dd962 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/util/ZooKeeperHiveHelper.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/util/ZooKeeperHiveHelper.java @@ -18,12 +18,18 @@ package org.apache.hadoop.hive.ql.util; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.Watcher; @@ -33,6 +39,7 @@ public class ZooKeeperHiveHelper { public static final Log LOG = LogFactory.getLog(ZooKeeperHiveHelper.class.getName()); public static final String ZOOKEEPER_PATH_SEPARATOR = "/"; + public static final String SASL_LOGIN_CONTEXT_NAME = "HiveZooKeeperClient"; /** * Get the ensemble server addresses from the configuration. The format is: host1:port, * host2:port.. @@ -94,4 +101,55 @@ public void process(org.apache.zookeeper.WatchedEvent event) { } } + /** + * Dynamically sets up the JAAS configuration + * @param principal + * @param keyTabFile + */ + public static void setUpJaasConfiguration(String principal, String keyTabFile) { + JaasConfiguration jaasConf = + new JaasConfiguration(ZooKeeperHiveHelper.SASL_LOGIN_CONTEXT_NAME, principal, keyTabFile); + // Install the Configuration in the runtime. + javax.security.auth.login.Configuration.setConfiguration(jaasConf); + } + + /** + * A JAAS configuration for ZooKeeper clients intended to use for SASL Kerberos. + */ + private static class JaasConfiguration extends javax.security.auth.login.Configuration { + // Current installed Configuration + private javax.security.auth.login.Configuration baseConfig = + javax.security.auth.login.Configuration.getConfiguration(); + private final String loginContextName; + private final String principal; + private final String keyTabFile; + + public JaasConfiguration(String hiveLoginContextName, String principal, String keyTabFile) { + this.loginContextName = hiveLoginContextName; + this.principal = principal; + this.keyTabFile = keyTabFile; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String appName) { + if (loginContextName.equals(appName)) { + Map krbOptions = new HashMap(); + krbOptions.put("doNotPrompt", "true"); + krbOptions.put("storeKey", "true"); + krbOptions.put("useKeyTab", "true"); + krbOptions.put("principal", principal); + krbOptions.put("keyTab", keyTabFile); + krbOptions.put("refreshKrb5Config", "true"); + AppConfigurationEntry hiveZooKeeperClientEntry = + new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(), + LoginModuleControlFlag.REQUIRED, krbOptions); + return new AppConfigurationEntry[] { hiveZooKeeperClientEntry }; + } + // Try the base config + if (baseConfig != null) { + return baseConfig.getAppConfigurationEntry(appName); + } + return null; + } + } } diff --git a/service/src/java/org/apache/hive/service/server/HiveServer2.java b/service/src/java/org/apache/hive/service/server/HiveServer2.java index 8691fcc..18b24e7 100644 --- a/service/src/java/org/apache/hive/service/server/HiveServer2.java +++ b/service/src/java/org/apache/hive/service/server/HiveServer2.java @@ -18,7 +18,9 @@ package org.apache.hive.service.server; +import java.io.IOException; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -36,9 +38,11 @@ import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.ql.exec.tez.TezSessionPoolManager; import org.apache.hadoop.hive.ql.util.ZooKeeperHiveHelper; +import org.apache.hadoop.hive.shims.ShimLoader; import org.apache.hive.common.util.HiveStringUtils; import org.apache.hive.common.util.HiveVersionInfo; import org.apache.hive.service.CompositeService; +import org.apache.hive.service.auth.HiveAuthFactory; import org.apache.hive.service.cli.CLIService; import org.apache.hive.service.cli.thrift.ThriftBinaryCLIService; import org.apache.hive.service.cli.thrift.ThriftCLIService; @@ -48,7 +52,11 @@ import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooDefs.Perms; import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; /** * HiveServer2. @@ -115,14 +123,19 @@ private void addServerInstanceToZooKeeper(HiveConf hiveConf) throws Exception { String rootNamespace = hiveConf.getVar(HiveConf.ConfVars.HIVE_SERVER2_ZOOKEEPER_NAMESPACE); String instanceURI = getServerInstanceURI(hiveConf); byte[] znodeDataUTF8 = instanceURI.getBytes(Charset.forName("UTF-8")); + // Znode ACLs + List nodeAcls = new ArrayList(); + setUpAuthAndAcls(hiveConf, nodeAcls); + // Create a ZooKeeper client zooKeeperClient = new ZooKeeper(zooKeeperEnsemble, zooKeeperSessionTimeout, new ZooKeeperHiveHelper.DummyWatcher()); - - // Create the parent znodes recursively; ignore if the parent already exists + // Create the parent znodes recursively; ignore if the parent already exists. + // If pre-creating the parent on a kerberized cluster, ensure that you give ACLs, + // as explained in {@link #setUpAuthAndAcls(HiveConf, List) setUpAuthAndAcls} try { - ZooKeeperHiveHelper.createPathRecursively(zooKeeperClient, rootNamespace, - Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + ZooKeeperHiveHelper.createPathRecursively(zooKeeperClient, rootNamespace, nodeAcls, + CreateMode.PERSISTENT); LOG.info("Created the root name space: " + rootNamespace + " on ZooKeeper for HiveServer2"); } catch (KeeperException e) { if (e.code() != KeeperException.Code.NODEEXISTS) { @@ -138,7 +151,7 @@ private void addServerInstanceToZooKeeper(HiveConf hiveConf) throws Exception { + ZooKeeperHiveHelper.ZOOKEEPER_PATH_SEPARATOR + "serverUri=" + instanceURI + ";" + "version=" + HiveVersionInfo.getVersion() + ";" + "sequence="; znodePath = - zooKeeperClient.create(znodePath, znodeDataUTF8, Ids.OPEN_ACL_UNSAFE, + zooKeeperClient.create(znodePath, znodeDataUTF8, nodeAcls, CreateMode.EPHEMERAL_SEQUENTIAL); setRegisteredWithZooKeeper(true); // Set a watch on the znode @@ -154,6 +167,48 @@ private void addServerInstanceToZooKeeper(HiveConf hiveConf) throws Exception { } /** + * Set up ACLs for znodes based on whether the cluster is secure or not. + * On a kerberized cluster, ZooKeeper performs Kerberos-SASL authentication. + * We give Read privilege to the world, but Create/Delete/Write/Admin to the authenticated user. + * On a non-kerberized cluster, we give Create/Read/Delete/Write/Admin privileges to the world. + * + * For a kerberized cluster, we also dynamically set up the client's JAAS conf. + * @param hiveConf + * @param nodeAcls + * @return + * @throws Exception + */ + private void setUpAuthAndAcls(HiveConf hiveConf, List nodeAcls) throws Exception { + if (ShimLoader.getHadoopShims().isSecurityEnabled()) { + String principal = + ShimLoader.getHadoopShims().getResolvedPrincipal( + hiveConf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_PRINCIPAL)); + String keyTabFile = hiveConf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_KEYTAB); + if (principal.isEmpty()) { + throw new IOException( + "HiveServer2 Kerberos principal is not correctly configured"); + } + if (keyTabFile.isEmpty()) { + throw new IOException( + "HiveServer2 Kerberos keytab is not correctly configured"); + } + // ZooKeeper property name to pick the correct JAAS conf section + System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, + ZooKeeperHiveHelper.SASL_LOGIN_CONTEXT_NAME); + // Install the JAAS Configuration for the runtime + ZooKeeperHiveHelper.setUpJaasConfiguration(principal, keyTabFile); + // Read all to the world + nodeAcls.addAll(Ids.READ_ACL_UNSAFE); + // Create/Delete/Write/Admin to the authenticated user + nodeAcls.add(new ACL(Perms.ALL, Ids.AUTH_IDS)); + } else { + // ACLs for znodes on a non-kerberized cluster + // Create/Read/Delete/Write/Admin to the world + nodeAcls.addAll(Ids.OPEN_ACL_UNSAFE); + } + } + + /** * The watcher class which sets the de-register flag when the znode corresponding to this server * instance is deleted. Additionally, it shuts down the server if there are no more active client * sessions at the time of receiving a 'NodeDeleted' notification from ZooKeeper. diff --git a/shims/0.20/src/main/java/org/apache/hadoop/hive/shims/Hadoop20Shims.java b/shims/0.20/src/main/java/org/apache/hadoop/hive/shims/Hadoop20Shims.java index 3c7d2af..d18ae44 100644 --- a/shims/0.20/src/main/java/org/apache/hadoop/hive/shims/Hadoop20Shims.java +++ b/shims/0.20/src/main/java/org/apache/hadoop/hive/shims/Hadoop20Shims.java @@ -620,6 +620,12 @@ public UserGroupInformation loginUserFromKeytabAndReturnUGI( } @Override + public String getResolvedPrincipal(String principal) throws IOException { + // Not supported + return null; + } + + @Override public void reLoginUserFromKeytab() throws IOException{ throwKerberosUnsupportedError(); } diff --git a/shims/common-secure/src/main/java/org/apache/hadoop/hive/shims/HadoopShimsSecure.java b/shims/common-secure/src/main/java/org/apache/hadoop/hive/shims/HadoopShimsSecure.java index 6e1ad03..606f973 100644 --- a/shims/common-secure/src/main/java/org/apache/hadoop/hive/shims/HadoopShimsSecure.java +++ b/shims/common-secure/src/main/java/org/apache/hadoop/hive/shims/HadoopShimsSecure.java @@ -573,6 +573,17 @@ public UserGroupInformation loginUserFromKeytabAndReturnUGI( return UserGroupInformation.loginUserFromKeytabAndReturnUGI(hostPrincipal, keytabFile); } + /** + * Convert Kerberos principal name pattern to valid Kerberos principal names. + * @param principal (principal name pattern) + * @return + * @throws IOException + */ + @Override + public String getResolvedPrincipal(String principal) throws IOException { + return SecurityUtil.getServerPrincipal(principal, "0.0.0.0"); + } + @Override public String getTokenFileLocEnvName() { return UserGroupInformation.HADOOP_TOKEN_FILE_LOCATION; diff --git a/shims/common/src/main/java/org/apache/hadoop/hive/shims/HadoopShims.java b/shims/common/src/main/java/org/apache/hadoop/hive/shims/HadoopShims.java index 99a2bcf..9850405 100644 --- a/shims/common/src/main/java/org/apache/hadoop/hive/shims/HadoopShims.java +++ b/shims/common/src/main/java/org/apache/hadoop/hive/shims/HadoopShims.java @@ -61,6 +61,7 @@ import org.apache.hadoop.mapreduce.TaskAttemptContext; import org.apache.hadoop.mapreduce.TaskAttemptID; import org.apache.hadoop.mapreduce.TaskID; +import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.Progressable; @@ -322,6 +323,14 @@ public UserGroupInformation loginUserFromKeytabAndReturnUGI(String principal, String keytabFile) throws IOException; /** + * Convert Kerberos principal name pattern to valid Kerberos principal names. + * @param principal (principal name pattern) + * @return + * @throws IOException + */ + public String getResolvedPrincipal(String principal) throws IOException; + + /** * Perform kerberos re-login using the given principal and keytab, to renew * the credentials * @throws IOException