diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 551639f..99b8815 100644 --- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -887,6 +887,8 @@ HIVE_SERVER2_ALLOW_USER_SUBSTITUTION("hive.server2.allow.user.substitution", true), HIVE_SERVER2_KERBEROS_KEYTAB("hive.server2.authentication.kerberos.keytab", ""), HIVE_SERVER2_KERBEROS_PRINCIPAL("hive.server2.authentication.kerberos.principal", ""), + HIVE_SERVER2_SPNEGO_KEYTAB("hive.server2.authentication.spnego.keytab", ""), + HIVE_SERVER2_SPNEGO_PRINCIPAL("hive.server2.authentication.spnego.principal", ""), HIVE_SERVER2_PLAIN_LDAP_URL("hive.server2.authentication.ldap.url", null), HIVE_SERVER2_PLAIN_LDAP_BASEDN("hive.server2.authentication.ldap.baseDN", null), HIVE_SERVER2_PLAIN_LDAP_DOMAIN("hive.server2.authentication.ldap.Domain", null), diff --git a/conf/hive-default.xml.template b/conf/hive-default.xml.template index 3c3df43..af0ff8d 100644 --- a/conf/hive-default.xml.template +++ b/conf/hive-default.xml.template @@ -2218,7 +2218,36 @@ Kerberos keytab file for server principal - + + + hive.server2.authentication.spnego.principal + + + SPNego service principal, optional, + typical value would look like HTTP/_HOST@EXAMPLE.COM + SPNego service principal would be used by hiveserver2 when kerberos security is enabled + and HTTP transport mode is used. + This needs to be set only if SPNEGO is to be used in authentication. + + + + + hive.server2.authentication.spnego.keytab + + + keytab file for SPNego principal, optional, + typical value would look like /etc/security/keytabs/spnego.service.keytab, + This keytab would be used by hiveserver2 when kerberos security is enabled + and HTTP transport mode is used. + This needs to be set only if SPNEGO is to be used in authentication. + SPNego authentication would be honored only if valid + hive.server2.authentication.spnego.principal + and + hive.server2.authentication.spnego.keytab + are specified + + + hive.server2.authentication.ldap.url diff --git a/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java b/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java index 86d2009..d8f4822 100644 --- a/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java +++ b/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java @@ -186,7 +186,22 @@ public static void loginFromKeytab(HiveConf hiveConf) throws IOException { if (!principal.isEmpty() && !keyTabFile.isEmpty()) { ShimLoader.getHadoopShims().loginUserFromKeytab(principal, keyTabFile); } else { - throw new IOException ("HiveServer2 kerberos principal or keytab is not correctly configured"); + throw new IOException ("HiveServer2 kerberos principal or keytab " + + "is not correctly configured"); + } + } + + // Perform spnego login using the hadoop shim API if the configuration is available + public static UserGroupInformation loginFromSpnegoKeytabAndReturnUGI( + HiveConf hiveConf) throws IOException { + String principal = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_PRINCIPAL); + String keyTabFile = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_KEYTAB); + if (!principal.isEmpty() && !keyTabFile.isEmpty()) { + return ShimLoader.getHadoopShims().loginUserFromKeytabAndReturnUGI( + principal, keyTabFile); + } else { + throw new IOException ("HiveServer2 SPNego principal or keytab " + + "is not correctly configured"); } } diff --git a/service/src/java/org/apache/hive/service/cli/CLIService.java b/service/src/java/org/apache/hive/service/cli/CLIService.java index e31a74e..d01bce9 100644 --- a/service/src/java/org/apache/hive/service/cli/CLIService.java +++ b/service/src/java/org/apache/hive/service/cli/CLIService.java @@ -67,6 +67,7 @@ private SessionManager sessionManager; private IMetaStoreClient metastoreClient; private UserGroupInformation serviceUGI; + private UserGroupInformation httpUGI; public CLIService() { super("CLIService"); @@ -90,6 +91,21 @@ public synchronized void init(HiveConf hiveConf) { } catch (LoginException e) { throw new ServiceException("Unable to login to kerberos with given principal/keytab", e); } + + // Also try creating a UGI object for the SPNego principal + String principal = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_PRINCIPAL); + String keyTabFile = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_KEYTAB); + if (principal.isEmpty() || keyTabFile.isEmpty()) { + LOG.info("SPNego httpUGI not created, spNegoPrincipal: " + principal + + ", ketabFile: " + keyTabFile); + } else { + try { + this.httpUGI = HiveAuthFactory.loginFromSpnegoKeytabAndReturnUGI(hiveConf); + LOG.info("SPNego httpUGI successfully created."); + } catch (IOException e) { + LOG.warn("SPNego httpUGI creation failed: ", e); + } + } } super.init(hiveConf); } @@ -98,6 +114,10 @@ public UserGroupInformation getServiceUGI() { return this.serviceUGI; } + public UserGroupInformation getHttpUGI() { + return this.httpUGI; + } + @Override public synchronized void start() { super.start(); diff --git a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java index f4cbe91..98d75b5 100644 --- a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java +++ b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java @@ -76,7 +76,10 @@ public void run() { String schemeName = useSsl ? "https" : "http"; String authType = hiveConf.getVar(ConfVars.HIVE_SERVER2_AUTHENTICATION); // Set during the init phase of HiveServer2 if auth mode is kerberos + // UGI for the hive/_HOST (kerberos) principal UserGroupInformation serviceUGI = cliService.getServiceUGI(); + // UGI for the http/_HOST (SPNego) principal + UserGroupInformation httpUGI = cliService.getHttpUGI(); if (useSsl) { String keyStorePath = hiveConf.getVar(ConfVars.HIVE_SERVER2_SSL_KEYSTORE_PATH).trim(); @@ -101,8 +104,9 @@ public void run() { TProcessor processor = processorFactory.getProcessor(null); TProtocolFactory protocolFactory = new TBinaryProtocol.Factory(); + TServlet thriftHttpServlet = new ThriftHttpServlet(processor, protocolFactory, - authType, serviceUGI); + authType, serviceUGI, httpUGI); final ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS); diff --git a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java index 255a165..c579db5 100644 --- a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java +++ b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java @@ -58,12 +58,14 @@ public static final Log LOG = LogFactory.getLog(ThriftHttpServlet.class.getName()); private final String authType; private final UserGroupInformation serviceUGI; + private final UserGroupInformation httpUGI; public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory, - String authType, UserGroupInformation serviceUGI) { + String authType, UserGroupInformation serviceUGI, UserGroupInformation httpUGI) { super(processor, protocolFactory); this.authType = authType; this.serviceUGI = serviceUGI; + this.httpUGI = httpUGI; } @Override @@ -73,24 +75,25 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) try { // For a kerberos setup if(isKerberosAuthMode(authType)) { - clientUserName = doKerberosAuth(request, serviceUGI); + clientUserName = doKerberosAuth(request); } else { clientUserName = doPasswdAuth(request, authType); } LOG.info("Client username: " + clientUserName); - + // Set the thread local username to be used for doAs if true SessionManager.setUserName(clientUserName); super.doPost(request, response); } catch (HttpAuthenticationException e) { - // Send a 403 to the client LOG.error("Error: ", e); - response.setContentType("application/x-thrift"); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - // Send the response back to the client + // Send a 401 to the client + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + if(isKerberosAuthMode(authType)) { + response.addHeader(HttpAuthUtils.WWW_AUTHENTICATE, HttpAuthUtils.NEGOTIATE); + } response.getWriter().println("Authentication Error: " + e.getMessage()); } finally { @@ -127,24 +130,38 @@ private String doPasswdAuth(HttpServletRequest request, String authType) * Do the GSS-API kerberos authentication. * We already have a logged in subject in the form of serviceUGI, * which GSS-API will extract information from. + * In case of a SPNego request we use the httpUGI, + * for the authenticating service tickets. * @param request * @return * @throws HttpAuthenticationException */ - private String doKerberosAuth(HttpServletRequest request, - UserGroupInformation serviceUGI) throws HttpAuthenticationException { + private String doKerberosAuth(HttpServletRequest request) + throws HttpAuthenticationException { + // Try authenticating with the http/_HOST principal + if (httpUGI != null) { + try { + return httpUGI.doAs(new HttpKerberosServerAction(request, httpUGI)); + } catch (Exception e) { + LOG.info("Failed to authenticate with http/_HOST kerberos principal, " + + "trying with hive/_HOST kerberos principal"); + } + } + // Now try with hive/_HOST principal try { return serviceUGI.doAs(new HttpKerberosServerAction(request, serviceUGI)); } catch (Exception e) { + LOG.error("Failed to authenticate with hive/_HOST kerberos principal"); throw new HttpAuthenticationException(e); } + } class HttpKerberosServerAction implements PrivilegedExceptionAction { HttpServletRequest request; UserGroupInformation serviceUGI; - - HttpKerberosServerAction(HttpServletRequest request, + + HttpKerberosServerAction(HttpServletRequest request, UserGroupInformation serviceUGI) { this.request = request; this.serviceUGI = serviceUGI; @@ -152,14 +169,16 @@ private String doKerberosAuth(HttpServletRequest request, @Override public String run() throws HttpAuthenticationException { - // Get own Kerberos credentials for accepting connection + // Get own Kerberos credentials for accepting connection GSSManager manager = GSSManager.getInstance(); GSSContext gssContext = null; String serverPrincipal = getPrincipalWithoutRealm( serviceUGI.getUserName()); try { // This Oid for Kerberos GSS-API mechanism. - Oid mechOid = new Oid("1.2.840.113554.1.2.2"); + Oid kerberosMechOid = new Oid("1.2.840.113554.1.2.2"); + // Oid for SPNego GSS-API mechanism. + Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2"); // Oid for kerberos principal name Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1"); @@ -168,7 +187,9 @@ public String run() throws HttpAuthenticationException { // GSS credentials for server GSSCredential serverCreds = manager.createCredential(serverName, - GSSCredential.DEFAULT_LIFETIME, mechOid, GSSCredential.ACCEPT_ONLY); + GSSCredential.DEFAULT_LIFETIME, + new Oid[]{kerberosMechOid, spnegoMechOid}, + GSSCredential.ACCEPT_ONLY); // Create a GSS context gssContext = manager.createContext(serverCreds); @@ -275,6 +296,7 @@ private String getAuthHeader(HttpServletRequest request, String authType) private boolean isKerberosAuthMode(String authType) { return authType.equalsIgnoreCase(HiveAuthFactory.AuthTypes.KERBEROS.toString()); } + } 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 80247ec..7aae689 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 @@ -599,6 +599,13 @@ public void loginUserFromKeytab(String principal, String keytabFile) throws IOEx } @Override + public UserGroupInformation loginUserFromKeytabAndReturnUGI( + String principal, String keytabFile) throws IOException { + throwKerberosUnsupportedError(); + 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 d4cddda..b7016a6 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 @@ -558,6 +558,13 @@ public void loginUserFromKeytab(String principal, String keytabFile) throws IOEx } @Override + public UserGroupInformation loginUserFromKeytabAndReturnUGI( + String principal, String keytabFile) throws IOException { + String hostPrincipal = SecurityUtil.getServerPrincipal(principal, "0.0.0.0"); + return UserGroupInformation.loginUserFromKeytabAndReturnUGI(hostPrincipal, keytabFile); + } + + @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 90c5602..c5fa1b7 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 @@ -311,6 +311,14 @@ public String addServiceToToken(String tokenStr, String tokenService) public void loginUserFromKeytab(String principal, String keytabFile) throws IOException; /** + * Perform kerberos login using the given principal and keytab, + * and return the UGI object + * @throws IOException + */ + public UserGroupInformation loginUserFromKeytabAndReturnUGI(String principal, + String keytabFile) throws IOException; + + /** * Perform kerberos re-login using the given principal and keytab, to renew * the credentials * @throws IOException