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