HBASE-13115 Fix the usage of remote user in thrift doAs implementation (Srikanth Srungarapu)
This commit is contained in:
parent
e31b9c3d58
commit
6f24e4639f
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
Loading…
Reference in New Issue