From 6f24e4639f23b673bef679a15253d8aa228843c6 Mon Sep 17 00:00:00 2001 From: tedyu Date: Tue, 3 Mar 2015 06:49:30 -0800 Subject: [PATCH] HBASE-13115 Fix the usage of remote user in thrift doAs implementation (Srikanth Srungarapu) --- .../hadoop/hbase/thrift/HttpDoAsClient.java | 27 +++++----- .../hbase/thrift/ThriftHttpServlet.java | 49 ++++++++++++++----- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/thrift/HttpDoAsClient.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/thrift/HttpDoAsClient.java index 9ef1bd28df9..df18fed8539 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/thrift/HttpDoAsClient.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/thrift/HttpDoAsClient.java @@ -18,7 +18,6 @@ */ package org.apache.hadoop.hbase.thrift; -import sun.misc.BASE64Encoder; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; @@ -43,6 +42,7 @@ import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor; import org.apache.hadoop.hbase.thrift.generated.Hbase; import org.apache.hadoop.hbase.thrift.generated.TCell; import org.apache.hadoop.hbase.thrift.generated.TRowResult; +import org.apache.hadoop.hbase.util.Base64; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; @@ -64,21 +64,24 @@ public class HttpDoAsClient { static protected String host; CharsetDecoder decoder = null; private static boolean secure = false; + static protected String doAsUser = null; + static protected String principal = null; public static void main(String[] args) throws Exception { - if (args.length < 2 || args.length > 3) { + if (args.length < 3 || args.length > 4) { System.out.println("Invalid arguments!"); - System.out.println("Usage: DemoClient host port [secure=false]"); - + System.out.println("Usage: HttpDoAsClient host port doAsUserName [security=true]"); System.exit(-1); } - port = Integer.parseInt(args[1]); host = args[0]; - if (args.length > 2) { - secure = Boolean.parseBoolean(args[2]); + port = Integer.parseInt(args[1]); + doAsUser = args[2]; + if (args.length > 3) { + secure = Boolean.parseBoolean(args[3]); + principal = getSubject().getPrincipals().iterator().next().getName(); } final HttpDoAsClient client = new HttpDoAsClient(); @@ -134,7 +137,7 @@ public class HttpDoAsClient { for (ByteBuffer name : refresh(client, httpClient).getTableNames()) { System.out.println(" found: " + utf8(name.array())); if (utf8(name.array()).equals(utf8(t))) { - if (client.isTableEnabled(name)) { + if (refresh(client, httpClient).isTableEnabled(name)) { System.out.println(" disabling table: " + utf8(name.array())); refresh(client, httpClient).disableTable(name); } @@ -180,8 +183,8 @@ public class HttpDoAsClient { } private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) { + httpClient.setCustomHeader("doAs", doAsUser); if(secure) { - httpClient.setCustomHeader("doAs", "hbase"); try { httpClient.setCustomHeader("Authorization", generateTicket()); } catch (GSSException e) { @@ -196,14 +199,14 @@ public class HttpDoAsClient { // Oid for kerberos principal name Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1"); Oid KERB_V5_OID = new Oid("1.2.840.113554.1.2.2"); - final GSSName clientName = manager.createName("hbase/node-1.internal@INTERNAL", + final GSSName clientName = manager.createName(principal, krb5PrincipalOid); final GSSCredential clientCred = manager.createCredential(clientName, 8 * 3600, KERB_V5_OID, GSSCredential.INITIATE_ONLY); - final GSSName serverName = manager.createName("hbase/node-1.internal@INTERNAL", krb5PrincipalOid); + final GSSName serverName = manager.createName(principal, krb5PrincipalOid); final GSSContext context = manager.createContext(serverName, KERB_V5_OID, @@ -216,7 +219,7 @@ public class HttpDoAsClient { final byte[] outToken = context.initSecContext(new byte[0], 0, 0); StringBuffer outputBuffer = new StringBuffer(); outputBuffer.append("Negotiate "); - outputBuffer.append(new BASE64Encoder().encode(outToken).replace("\n", "")); + outputBuffer.append(Base64.encodeBytes(outToken).replace("\n", "")); System.out.print("Ticket is: " + outputBuffer); return outputBuffer.toString(); } diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java index f3bed0a8923..c7ea46f61e1 100644 --- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java +++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java @@ -27,10 +27,10 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.net.util.Base64; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.security.SecurityUtil; +import org.apache.hadoop.hbase.util.Base64; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.ProxyUsers; @@ -57,6 +57,12 @@ public class ThriftHttpServlet extends TServlet { private final boolean securityEnabled; private final boolean doAsEnabled; private transient ThriftServerRunner.HBaseHandler hbaseHandler; + private String outToken; + + // HTTP Header related constants. + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final String AUTHORIZATION = "Authorization"; + public static final String NEGOTIATE = "Negotiate"; public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory, UserGroupInformation realUser, Configuration conf, ThriftServerRunner.HBaseHandler @@ -72,28 +78,38 @@ public class ThriftHttpServlet extends TServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String effectiveUser = realUser.getShortUserName(); + String effectiveUser = request.getRemoteUser(); if (securityEnabled) { try { // As Thrift HTTP transport doesn't support SPNEGO yet (THRIFT-889), // Kerberos authentication is being done at servlet level. effectiveUser = doKerberosAuth(request); + // It is standard for client applications expect this header. + // Please see http://tools.ietf.org/html/rfc4559 for more details. + response.addHeader(WWW_AUTHENTICATE, NEGOTIATE + " " + outToken); } catch (HttpAuthenticationException e) { LOG.error("Kerberos Authentication failed", e); // Send a 401 to the client response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.addHeader(WWW_AUTHENTICATE, NEGOTIATE); response.getWriter().println("Authentication Error: " + e.getMessage()); return; } } String doAsUserFromQuery = request.getHeader("doAs"); + if(effectiveUser == null) { + effectiveUser = realUser.getShortUserName(); + } if (doAsUserFromQuery != null) { if (!doAsEnabled) { throw new ServletException("Support for proxyuser is not configured"); } + // The authenticated remote user is attempting to perform 'doAs' proxy user. + UserGroupInformation remoteUser = UserGroupInformation.createRemoteUser(effectiveUser); // create and attempt to authorize a proxy user (the client is attempting // to do proxy user) - UserGroupInformation ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, realUser); + UserGroupInformation ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, + remoteUser); // validate the proxy user authorization try { ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf); @@ -113,8 +129,11 @@ public class ThriftHttpServlet extends TServlet { */ private String doKerberosAuth(HttpServletRequest request) throws HttpAuthenticationException { + HttpKerberosServerAction action = new HttpKerberosServerAction(request, realUser); try { - return realUser.doAs(new HttpKerberosServerAction(request, realUser)); + String principal = realUser.doAs(action); + outToken = action.outToken; + return principal; } catch (Exception e) { LOG.error("Failed to perform authentication"); throw new HttpAuthenticationException(e); @@ -125,6 +144,7 @@ public class ThriftHttpServlet extends TServlet { private static class HttpKerberosServerAction implements PrivilegedExceptionAction { HttpServletRequest request; UserGroupInformation serviceUGI; + String outToken = null; HttpKerberosServerAction(HttpServletRequest request, UserGroupInformation serviceUGI) { this.request = request; this.serviceUGI = serviceUGI; @@ -153,16 +173,19 @@ public class ThriftHttpServlet extends TServlet { // Create a GSS context gssContext = manager.createContext(serverCreds); // Get service ticket from the authorization header - String serviceTicketBase64 = getAuthHeader(request); - byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes()); - gssContext.acceptSecContext(inToken, 0, inToken.length); - // Authenticate or deny based on its context completion - if (!gssContext.isEstablished()) { + String serviceTicketBase64 = getAuthHeader(request); + byte[] inToken = Base64.decode(serviceTicketBase64); + byte[] res = gssContext.acceptSecContext(inToken, 0, inToken.length); + if(res != null) { + outToken = Base64.encodeBytes(res).replace("\n", ""); + } + // Authenticate or deny based on its context completion + if (!gssContext.isEstablished()) { throw new HttpAuthenticationException("Kerberos authentication failed: " + "unable to establish context with the service ticket " + "provided by the client."); - } - return SecurityUtil.getUserFromPrincipal(gssContext.getSrcName().toString()); + } + return SecurityUtil.getUserFromPrincipal(gssContext.getSrcName().toString()); } catch (GSSException e) { throw new HttpAuthenticationException("Kerberos authentication failed: ", e); } finally { @@ -183,14 +206,14 @@ public class ThriftHttpServlet extends TServlet { */ private String getAuthHeader(HttpServletRequest request) throws HttpAuthenticationException { - String authHeader = request.getHeader("Authorization"); + String authHeader = request.getHeader(AUTHORIZATION); // Each http request must have an Authorization header if (authHeader == null || authHeader.isEmpty()) { throw new HttpAuthenticationException("Authorization header received " + "from the client is empty."); } String authHeaderBase64String; - int beginIndex = ("Negotiate ").length(); + int beginIndex = (NEGOTIATE + " ").length(); authHeaderBase64String = authHeader.substring(beginIndex); // Authorization header must have a payload if (authHeaderBase64String == null || authHeaderBase64String.isEmpty()) {