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 affcbb4..91d45d1 100644 --- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -890,6 +890,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..f55d7b8 100644 --- a/conf/hive-default.xml.template +++ b/conf/hive-default.xml.template @@ -2220,6 +2220,24 @@ + hive.server2.authentication.spnego.principal + + + SPNego service principal, + typical value would look like HTTP/_HOST@EXAMPLE.COM + + + + + hive.server2.authentication.spnego.keytab + + + keytab file for SPNego principal, + typical value would look like /etc/security/keytabs/spnego.service.keytab + + + + 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 6e6a47d..d946dfa 100644 --- a/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java +++ b/service/src/java/org/apache/hive/service/auth/HiveAuthFactory.java @@ -182,7 +182,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..6d81091 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,14 @@ 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 + 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); } @@ -97,6 +106,10 @@ public synchronized void init(HiveConf hiveConf) { public UserGroupInformation getServiceUGI() { return this.serviceUGI; } + + public UserGroupInformation getHttpUGI() { + return this.httpUGI; + } @Override public synchronized void 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 cb01cfd..9bc48ce 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..cab7e7e 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 @@ -80,17 +82,18 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } 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,23 +130,37 @@ 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 { + // Try authenticating with the http/_HOST principal + if (httpUGI != null) { + try { + return httpUGI.doAs(new HttpKerberosServerAction(request, httpUGI)); + } catch (Exception e) { + LOG.warn("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, UserGroupInformation serviceUGI) { this.request = request; @@ -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,13 +187,19 @@ 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); // Get service ticket from the authorization header String serviceTicketBase64 = getAuthHeader(request, authType); + if (serviceTicketBase64.endsWith("[\r][\n]")) { + serviceTicketBase64 = serviceTicketBase64.substring(0, + serviceTicketBase64.length() -4); + } byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes()); gssContext.acceptSecContext(inToken, 0, inToken.length); @@ -275,6 +300,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 9aa555a..37720d5 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 @@ -595,6 +595,13 @@ public UserGroupInformation createRemoteUser(String userName, List group public void loginUserFromKeytab(String principal, String keytabFile) throws IOException { throwKerberosUnsupportedError(); } + + @Override + public UserGroupInformation loginUserFromKeytabAndReturnUGI( + String principal, String keytabFile) throws IOException { + throwKerberosUnsupportedError(); + return null; + } @Override public void reLoginUserFromKeytab() throws IOException{ 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..665edfb 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 @@ -556,6 +556,13 @@ public void loginUserFromKeytab(String principal, String keytabFile) throws IOEx String hostPrincipal = SecurityUtil.getServerPrincipal(principal, "0.0.0.0"); UserGroupInformation.loginUserFromKeytab(hostPrincipal, keytabFile); } + + @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() { 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 ed951f1..b7594f4 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 @@ -308,6 +308,14 @@ public String addServiceToToken(String tokenStr, String tokenService) * @throws IOException */ 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