HBASE-13115 Fix the usage of remote user in thrift doAs implementation (Srikanth Srungarapu)

This commit is contained in:
tedyu 2015-03-03 06:49:30 -08:00
parent e31b9c3d58
commit 6f24e4639f
2 changed files with 51 additions and 25 deletions

View File

@ -18,7 +18,6 @@
*/ */
package org.apache.hadoop.hbase.thrift; package org.apache.hadoop.hbase.thrift;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; 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.Hbase;
import org.apache.hadoop.hbase.thrift.generated.TCell; import org.apache.hadoop.hbase.thrift.generated.TCell;
import org.apache.hadoop.hbase.thrift.generated.TRowResult; 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.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.THttpClient;
@ -64,21 +64,24 @@ public class HttpDoAsClient {
static protected String host; static protected String host;
CharsetDecoder decoder = null; CharsetDecoder decoder = null;
private static boolean secure = false; private static boolean secure = false;
static protected String doAsUser = null;
static protected String principal = null;
public static void main(String[] args) throws Exception { 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("Invalid arguments!");
System.out.println("Usage: DemoClient host port [secure=false]"); System.out.println("Usage: HttpDoAsClient host port doAsUserName [security=true]");
System.exit(-1); System.exit(-1);
} }
port = Integer.parseInt(args[1]);
host = args[0]; host = args[0];
if (args.length > 2) { port = Integer.parseInt(args[1]);
secure = Boolean.parseBoolean(args[2]); doAsUser = args[2];
if (args.length > 3) {
secure = Boolean.parseBoolean(args[3]);
principal = getSubject().getPrincipals().iterator().next().getName();
} }
final HttpDoAsClient client = new HttpDoAsClient(); final HttpDoAsClient client = new HttpDoAsClient();
@ -134,7 +137,7 @@ public class HttpDoAsClient {
for (ByteBuffer name : refresh(client, httpClient).getTableNames()) { for (ByteBuffer name : refresh(client, httpClient).getTableNames()) {
System.out.println(" found: " + utf8(name.array())); System.out.println(" found: " + utf8(name.array()));
if (utf8(name.array()).equals(utf8(t))) { 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())); System.out.println(" disabling table: " + utf8(name.array()));
refresh(client, httpClient).disableTable(name); refresh(client, httpClient).disableTable(name);
} }
@ -180,8 +183,8 @@ public class HttpDoAsClient {
} }
private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) { private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) {
httpClient.setCustomHeader("doAs", doAsUser);
if(secure) { if(secure) {
httpClient.setCustomHeader("doAs", "hbase");
try { try {
httpClient.setCustomHeader("Authorization", generateTicket()); httpClient.setCustomHeader("Authorization", generateTicket());
} catch (GSSException e) { } catch (GSSException e) {
@ -196,14 +199,14 @@ public class HttpDoAsClient {
// Oid for kerberos principal name // Oid for kerberos principal name
Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1"); 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"); 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); krb5PrincipalOid);
final GSSCredential clientCred = manager.createCredential(clientName, final GSSCredential clientCred = manager.createCredential(clientName,
8 * 3600, 8 * 3600,
KERB_V5_OID, KERB_V5_OID,
GSSCredential.INITIATE_ONLY); 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, final GSSContext context = manager.createContext(serverName,
KERB_V5_OID, KERB_V5_OID,
@ -216,7 +219,7 @@ public class HttpDoAsClient {
final byte[] outToken = context.initSecContext(new byte[0], 0, 0); final byte[] outToken = context.initSecContext(new byte[0], 0, 0);
StringBuffer outputBuffer = new StringBuffer(); StringBuffer outputBuffer = new StringBuffer();
outputBuffer.append("Negotiate "); outputBuffer.append("Negotiate ");
outputBuffer.append(new BASE64Encoder().encode(outToken).replace("\n", "")); outputBuffer.append(Base64.encodeBytes(outToken).replace("\n", ""));
System.out.print("Ticket is: " + outputBuffer); System.out.print("Ticket is: " + outputBuffer);
return outputBuffer.toString(); return outputBuffer.toString();
} }

View File

@ -27,10 +27,10 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.util.Base64;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.security.SecurityUtil; 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.UserGroupInformation;
import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.hadoop.security.authorize.ProxyUsers;
@ -57,6 +57,12 @@ public class ThriftHttpServlet extends TServlet {
private final boolean securityEnabled; private final boolean securityEnabled;
private final boolean doAsEnabled; private final boolean doAsEnabled;
private transient ThriftServerRunner.HBaseHandler hbaseHandler; 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, public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory,
UserGroupInformation realUser, Configuration conf, ThriftServerRunner.HBaseHandler UserGroupInformation realUser, Configuration conf, ThriftServerRunner.HBaseHandler
@ -72,28 +78,38 @@ public class ThriftHttpServlet extends TServlet {
@Override @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
String effectiveUser = realUser.getShortUserName(); String effectiveUser = request.getRemoteUser();
if (securityEnabled) { if (securityEnabled) {
try { try {
// As Thrift HTTP transport doesn't support SPNEGO yet (THRIFT-889), // As Thrift HTTP transport doesn't support SPNEGO yet (THRIFT-889),
// Kerberos authentication is being done at servlet level. // Kerberos authentication is being done at servlet level.
effectiveUser = doKerberosAuth(request); 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) { } catch (HttpAuthenticationException e) {
LOG.error("Kerberos Authentication failed", e); LOG.error("Kerberos Authentication failed", e);
// Send a 401 to the client // Send a 401 to the client
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader(WWW_AUTHENTICATE, NEGOTIATE);
response.getWriter().println("Authentication Error: " + e.getMessage()); response.getWriter().println("Authentication Error: " + e.getMessage());
return; return;
} }
} }
String doAsUserFromQuery = request.getHeader("doAs"); String doAsUserFromQuery = request.getHeader("doAs");
if(effectiveUser == null) {
effectiveUser = realUser.getShortUserName();
}
if (doAsUserFromQuery != null) { if (doAsUserFromQuery != null) {
if (!doAsEnabled) { if (!doAsEnabled) {
throw new ServletException("Support for proxyuser is not configured"); 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 // create and attempt to authorize a proxy user (the client is attempting
// to do proxy user) // to do proxy user)
UserGroupInformation ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, realUser); UserGroupInformation ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery,
remoteUser);
// validate the proxy user authorization // validate the proxy user authorization
try { try {
ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf); ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
@ -113,8 +129,11 @@ public class ThriftHttpServlet extends TServlet {
*/ */
private String doKerberosAuth(HttpServletRequest request) private String doKerberosAuth(HttpServletRequest request)
throws HttpAuthenticationException { throws HttpAuthenticationException {
HttpKerberosServerAction action = new HttpKerberosServerAction(request, realUser);
try { try {
return realUser.doAs(new HttpKerberosServerAction(request, realUser)); String principal = realUser.doAs(action);
outToken = action.outToken;
return principal;
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to perform authentication"); LOG.error("Failed to perform authentication");
throw new HttpAuthenticationException(e); throw new HttpAuthenticationException(e);
@ -125,6 +144,7 @@ public class ThriftHttpServlet extends TServlet {
private static class HttpKerberosServerAction implements PrivilegedExceptionAction<String> { private static class HttpKerberosServerAction implements PrivilegedExceptionAction<String> {
HttpServletRequest request; HttpServletRequest request;
UserGroupInformation serviceUGI; UserGroupInformation serviceUGI;
String outToken = null;
HttpKerberosServerAction(HttpServletRequest request, UserGroupInformation serviceUGI) { HttpKerberosServerAction(HttpServletRequest request, UserGroupInformation serviceUGI) {
this.request = request; this.request = request;
this.serviceUGI = serviceUGI; this.serviceUGI = serviceUGI;
@ -154,8 +174,11 @@ public class ThriftHttpServlet extends TServlet {
gssContext = manager.createContext(serverCreds); gssContext = manager.createContext(serverCreds);
// Get service ticket from the authorization header // Get service ticket from the authorization header
String serviceTicketBase64 = getAuthHeader(request); String serviceTicketBase64 = getAuthHeader(request);
byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes()); byte[] inToken = Base64.decode(serviceTicketBase64);
gssContext.acceptSecContext(inToken, 0, inToken.length); 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 // Authenticate or deny based on its context completion
if (!gssContext.isEstablished()) { if (!gssContext.isEstablished()) {
throw new HttpAuthenticationException("Kerberos authentication failed: " + throw new HttpAuthenticationException("Kerberos authentication failed: " +
@ -183,14 +206,14 @@ public class ThriftHttpServlet extends TServlet {
*/ */
private String getAuthHeader(HttpServletRequest request) private String getAuthHeader(HttpServletRequest request)
throws HttpAuthenticationException { throws HttpAuthenticationException {
String authHeader = request.getHeader("Authorization"); String authHeader = request.getHeader(AUTHORIZATION);
// Each http request must have an Authorization header // Each http request must have an Authorization header
if (authHeader == null || authHeader.isEmpty()) { if (authHeader == null || authHeader.isEmpty()) {
throw new HttpAuthenticationException("Authorization header received " + throw new HttpAuthenticationException("Authorization header received " +
"from the client is empty."); "from the client is empty.");
} }
String authHeaderBase64String; String authHeaderBase64String;
int beginIndex = ("Negotiate ").length(); int beginIndex = (NEGOTIATE + " ").length();
authHeaderBase64String = authHeader.substring(beginIndex); authHeaderBase64String = authHeader.substring(beginIndex);
// Authorization header must have a payload // Authorization header must have a payload
if (authHeaderBase64String == null || authHeaderBase64String.isEmpty()) { if (authHeaderBase64String == null || authHeaderBase64String.isEmpty()) {