diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java index d71bde3..f915699 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java @@ -99,6 +99,7 @@ private JdbcConnectionParams connParams; private final boolean isEmbeddedMode; private TTransport transport; + private boolean assumeSubject; // TODO should be replaced by CliServiceClient private TCLIService.Iface client; private boolean isClosed = true; @@ -177,6 +178,9 @@ public HiveConnection(String uri, Properties info) throws SQLException { private void openTransport() throws SQLException { while (true) { try { + assumeSubject = + JdbcConnectionParams.AUTH_KERBEROS_AUTH_TYPE_FROM_SUBJECT.equals(sessConfMap + .get(JdbcConnectionParams.AUTH_KERBEROS_AUTH_TYPE)); transport = isHttpTransportMode() ? createHttpTransport() : createBinaryTransport(); if (!transport.isOpen()) { LOG.info("Will try to open client transport with JDBC Uri: " + jdbcUriString); @@ -265,8 +269,9 @@ private DefaultHttpClient getHttpClient(Boolean useSsl) throws SQLException { * In https mode, the entire information is encrypted * TODO: Optimize this with a mix of kerberos + using cookie. */ - requestInterceptor = new HttpKerberosRequestInterceptor( - sessConfMap.get(JdbcConnectionParams.AUTH_PRINCIPAL), host, getServerHttpUrl(false)); + requestInterceptor = + new HttpKerberosRequestInterceptor(sessConfMap.get(JdbcConnectionParams.AUTH_PRINCIPAL), + host, getServerHttpUrl(useSsl), assumeSubject); } else { /** @@ -351,8 +356,6 @@ private TTransport createBinaryTransport() throws SQLException, TTransportExcept } saslProps.put(Sasl.QOP, saslQOP.toString()); saslProps.put(Sasl.SERVER_AUTH, "true"); - boolean assumeSubject = JdbcConnectionParams.AUTH_KERBEROS_AUTH_TYPE_FROM_SUBJECT.equals(sessConfMap - .get(JdbcConnectionParams.AUTH_KERBEROS_AUTH_TYPE)); transport = KerberosSaslHelper.getKerberosTransport( sessConfMap.get(JdbcConnectionParams.AUTH_PRINCIPAL), host, HiveAuthFactory.getSocketTransport(host, port, loginTimeout), saslProps, diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java index 9634d74..2d21547 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java @@ -39,15 +39,17 @@ String principal; String host; String serverHttpUrl; + boolean assumeSubject; // A fair reentrant lock private static ReentrantLock kerberosLock = new ReentrantLock(true); public HttpKerberosRequestInterceptor(String principal, String host, - String serverHttpUrl) { + String serverHttpUrl, boolean assumeSubject) { this.principal = principal; this.host = host; this.serverHttpUrl = serverHttpUrl; + this.assumeSubject = assumeSubject; } @Override @@ -59,7 +61,7 @@ public void process(HttpRequest httpRequest, HttpContext httpContext) // Locking ensures the tokens are unique in case of concurrent requests kerberosLock.lock(); kerberosAuthHeader = HttpAuthUtils.getKerberosServiceTicket( - principal, host, serverHttpUrl); + principal, host, serverHttpUrl, assumeSubject); // Set the session key token (Base64 encoded) in the headers httpRequest.addHeader(HttpAuthUtils.AUTHORIZATION + ": " + HttpAuthUtils.NEGOTIATE + " ", kerberosAuthHeader); diff --git a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java index 10b6c79..54fa588 100644 --- a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java +++ b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java @@ -18,16 +18,18 @@ package org.apache.hive.service.auth; -import java.io.IOException; +import java.security.AccessControlContext; +import java.security.AccessController; import java.security.PrivilegedExceptionAction; +import javax.security.auth.Subject; + import org.apache.commons.codec.binary.Base64; import org.apache.hadoop.hive.shims.ShimLoader; import org.apache.hadoop.security.UserGroupInformation; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; @@ -36,60 +38,54 @@ * Utility functions for HTTP mode authentication. */ public final class HttpAuthUtils { - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; public static final String AUTHORIZATION = "Authorization"; public static final String BASIC = "Basic"; public static final String NEGOTIATE = "Negotiate"; - - /** - * @return Stringified Base64 encoded kerberosAuthHeader on success - */ - public static String getKerberosServiceTicket(String principal, String host, String serverHttpUrl) - throws IOException, InterruptedException { - UserGroupInformation clientUGI = getClientUGI("kerberos"); - String serverPrincipal = getServerPrincipal(principal, host); - // Uses the Ticket Granting Ticket in the UserGroupInformation - return clientUGI.doAs( - new HttpKerberosClientAction(serverPrincipal, clientUGI.getUserName(), serverHttpUrl)); - } /** - * Get server principal and verify that hostname is present. - */ - private static String getServerPrincipal(String principal, String host) throws IOException { - return ShimLoader.getHadoopThriftAuthBridge().getServerPrincipal(principal, host); - } - - /** - * JAAS login to setup the client UserGroupInformation. - * Sets up the Kerberos Ticket Granting Ticket, - * in the client UserGroupInformation object. - * - * @return Client's UserGroupInformation + * @return Stringified Base64 encoded kerberosAuthHeader on success + * @throws Exception */ - public static UserGroupInformation getClientUGI(String authType) throws IOException { - return ShimLoader.getHadoopThriftAuthBridge().getCurrentUGIWithConf(authType); + public static String getKerberosServiceTicket(String principal, String host, + String serverHttpUrl, boolean assumeSubject) throws Exception { + String serverPrincipal = + ShimLoader.getHadoopThriftAuthBridge().getServerPrincipal(principal, host); + if (assumeSubject) { + // With this option, we're assuming that the external application, + // using the JDBC driver has done a JAAS kerberos login already + AccessControlContext context = AccessController.getContext(); + Subject subject = Subject.getSubject(context); + if (subject == null) { + throw new Exception("The Subject is not set"); + } + return Subject.doAs(subject, new HttpKerberosClientAction(serverPrincipal, serverHttpUrl)); + } else { + // JAAS login from ticket cache to setup the client UserGroupInformation + UserGroupInformation clientUGI = + ShimLoader.getHadoopThriftAuthBridge().getCurrentUGIWithConf("kerberos"); + return clientUGI.doAs(new HttpKerberosClientAction(serverPrincipal, serverHttpUrl)); + } } private HttpAuthUtils() { throw new UnsupportedOperationException("Can't initialize class"); } + /** + * We'll create an instance of this class within a doAs block so that the client's TGT credentials + * can be read from the Subject + */ public static class HttpKerberosClientAction implements PrivilegedExceptionAction { - public static final String HTTP_RESPONSE = "HTTP_RESPONSE"; public static final String SERVER_HTTP_URL = "SERVER_HTTP_URL"; private final String serverPrincipal; - private final String clientUserName; private final String serverHttpUrl; private final Base64 base64codec; private final HttpContext httpContext; - public HttpKerberosClientAction(String serverPrincipal, String clientUserName, - String serverHttpUrl) { + public HttpKerberosClientAction(String serverPrincipal, String serverHttpUrl) { this.serverPrincipal = serverPrincipal; - this.clientUserName = clientUserName; this.serverHttpUrl = serverHttpUrl; base64codec = new Base64(0); httpContext = new BasicHttpContext(); @@ -102,38 +98,17 @@ public String run() throws Exception { Oid mechOid = new Oid("1.2.840.113554.1.2.2"); // Oid for kerberos principal name Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1"); - GSSManager manager = GSSManager.getInstance(); - - // GSS name for client - GSSName clientName = manager.createName(clientUserName, GSSName.NT_USER_NAME); // GSS name for server GSSName serverName = manager.createName(serverPrincipal, krb5PrincipalOid); - - // GSS credentials for client - GSSCredential clientCreds = - manager.createCredential(clientName, GSSCredential.DEFAULT_LIFETIME, mechOid, - GSSCredential.INITIATE_ONLY); - - /* - * Create a GSSContext for mutual authentication with the - * server. - * - serverName is the GSSName that represents the server. - * - krb5Oid is the Oid that represents the mechanism to - * use. The client chooses the mechanism to use. - * - clientCreds are the client credentials - */ + // Create a GSSContext for authentication with the service. + // We're passing client credentials as null since we want them to be read from the Subject. GSSContext gssContext = - manager.createContext(serverName, mechOid, clientCreds, GSSContext.DEFAULT_LIFETIME); - - // Mutual authentication not r + manager.createContext(serverName, mechOid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestMutualAuth(false); - // Establish context byte[] inToken = new byte[0]; - byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length); - gssContext.dispose(); // Base64 encoded and stringified token for server return new String(base64codec.encode(outToken)); 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 c61d3a3..fde39d2 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 @@ -208,11 +208,9 @@ public String run() throws HttpAuthenticationException { // Create a GSS context gssContext = manager.createContext(serverCreds); - // Get service ticket from the authorization header String serviceTicketBase64 = getAuthHeader(request, authType); byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes()); - gssContext.acceptSecContext(inToken, 0, inToken.length); // Authenticate or deny based on its context completion if (!gssContext.isEstablished()) {