This is an automated email from the ASF dual-hosted git repository.
elserj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/master by this push:
new fc33137 HBASE-24268 REST and Thrift server do not handle the "doAs"
parameter case insensitively
fc33137 is described below
commit fc3313771ddbd83d68a47a649d4265d9a2941de9
Author: Richard Antal <[email protected]>
AuthorDate: Wed Nov 18 11:19:42 2020 +0100
HBASE-24268 REST and Thrift server do not handle the "doAs" parameter case
insensitively
Closes #1843
Signed-off-by: Josh Elser <[email protected]>
Signed-off-by: Sean Busbey <[email protected]>
---
.../hbase/http/ProxyUserAuthenticationFilter.java | 19 ++++++
.../hadoop/hbase/rest/RESTServletContainer.java | 5 +-
.../hadoop/hbase/rest/TestSecureRESTServer.java | 72 +++++++++++++++++-----
.../hadoop/hbase/thrift/ThriftHttpServlet.java | 3 +-
4 files changed, 80 insertions(+), 19 deletions(-)
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
index 5fb17c9..182a4e1 100644
---
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
+++
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
@@ -150,6 +150,25 @@ public class ProxyUserAuthenticationFilter extends
AuthenticationFilter {
return false;
}
+ /**
+ * The purpose of this function is to get the doAs parameter of a http
request
+ * case insensitively
+ * @param request
+ * @return doAs parameter if exists or null otherwise
+ */
+ public static String getDoasFromHeader(final HttpServletRequest request) {
+ String doas = null;
+ final Enumeration<String> headers = request.getHeaderNames();
+ while (headers.hasMoreElements()){
+ String header = headers.nextElement();
+ if (header.toLowerCase().equals("doas")){
+ doas = request.getHeader(header);
+ break;
+ }
+ }
+ return doas;
+ }
+
public static HttpServletRequest toLowerCase(
final HttpServletRequest request) {
@SuppressWarnings("unchecked")
diff --git
a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServletContainer.java
b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServletContainer.java
index 1cae45c..28cf4cb 100644
---
a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServletContainer.java
+++
b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServletContainer.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AuthorizationException;
@@ -30,6 +31,7 @@ import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
import
org.apache.hbase.thirdparty.org.glassfish.jersey.servlet.ServletContainer;
+import static
org.apache.hadoop.hbase.http.ProxyUserAuthenticationFilter.toLowerCase;
/**
* REST servlet container. It is used to get the remote request user
@@ -51,7 +53,8 @@ public class RESTServletContainer extends ServletContainer {
@Override
public void service(final HttpServletRequest request,
final HttpServletResponse response) throws ServletException, IOException
{
- final String doAsUserFromQuery = request.getParameter("doAs");
+ final HttpServletRequest lowerCaseRequest = toLowerCase(request);
+ final String doAsUserFromQuery = lowerCaseRequest.getParameter("doas");
RESTServlet servlet = RESTServlet.getInstance();
if (doAsUserFromQuery != null) {
Configuration conf = servlet.getConfiguration();
diff --git
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecureRESTServer.java
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecureRESTServer.java
index 920cf45..47ef053 100644
---
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecureRESTServer.java
+++
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecureRESTServer.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hbase.rest;
+import static
org.apache.hadoop.hbase.rest.RESTServlet.HBASE_REST_SUPPORT_PROXYUSER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -24,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import java.io.File;
+import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Principal;
@@ -115,6 +117,7 @@ public class TestSecureRESTServer {
private static final String HOSTNAME = "localhost";
private static final String CLIENT_PRINCIPAL = "client";
+ private static final String WHEEL_PRINCIPAL = "wheel";
// The principal for accepting SPNEGO authn'ed requests (*must* be HTTP/fqdn)
private static final String SPNEGO_SERVICE_PRINCIPAL = "HTTP/" + HOSTNAME;
// The principal we use to connect to HBase
@@ -126,6 +129,7 @@ public class TestSecureRESTServer {
private static RESTServer server;
private static File restServerKeytab;
private static File clientKeytab;
+ private static File wheelKeytab;
private static File serviceKeytab;
@BeforeClass
@@ -148,6 +152,8 @@ public class TestSecureRESTServer {
restServerKeytab = new File(keytabDir, "spnego.keytab");
// Keytab for the client
clientKeytab = new File(keytabDir, CLIENT_PRINCIPAL + ".keytab");
+ // Keytab for wheel
+ wheelKeytab = new File(keytabDir, WHEEL_PRINCIPAL + ".keytab");
/*
* Update UGI
@@ -159,6 +165,7 @@ public class TestSecureRESTServer {
*/
KDC = TEST_UTIL.setupMiniKdc(serviceKeytab);
KDC.createPrincipal(clientKeytab, CLIENT_PRINCIPAL);
+ KDC.createPrincipal(wheelKeytab, WHEEL_PRINCIPAL);
KDC.createPrincipal(serviceKeytab, SERVICE_PRINCIPAL);
// REST server's keytab contains keys for both principals REST uses
KDC.createPrincipal(restServerKeytab, SPNEGO_SERVICE_PRINCIPAL,
REST_SERVER_PRINCIPAL);
@@ -184,6 +191,8 @@ public class TestSecureRESTServer {
conf.set("hbase.superuser", "hbase");
conf.set("hadoop.proxyuser.rest.hosts", "*");
conf.set("hadoop.proxyuser.rest.users", "*");
+ conf.set("hadoop.proxyuser.wheel.hosts", "*");
+ conf.set("hadoop.proxyuser.wheel.users", "*");
UserGroupInformation.setConfiguration(conf);
updateKerberosConfiguration(conf, REST_SERVER_PRINCIPAL,
SPNEGO_SERVICE_PRINCIPAL,
@@ -230,6 +239,7 @@ public class TestSecureRESTServer {
return null;
}
});
+ instertData();
}
@AfterClass
@@ -299,21 +309,21 @@ public class TestSecureRESTServer {
// Keytab for both principals above
conf.set(RESTServer.REST_KEYTAB_FILE, serverKeytab.getAbsolutePath());
conf.set("hbase.rest.authentication.kerberos.keytab",
serverKeytab.getAbsolutePath());
+ conf.set(HBASE_REST_SUPPORT_PROXYUSER, "true");
}
- @Test
- public void testPositiveAuthorization() throws Exception {
+ private static void instertData() throws IOException, InterruptedException {
// Create a table, write a row to it, grant read perms to the client
UserGroupInformation superuser =
UserGroupInformation.loginUserFromKeytabAndReturnUGI(
- SERVICE_PRINCIPAL, serviceKeytab.getAbsolutePath());
+ SERVICE_PRINCIPAL, serviceKeytab.getAbsolutePath());
final TableName table = TableName.valueOf("publicTable");
superuser.doAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
try (Connection conn =
ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) {
TableDescriptor desc = TableDescriptorBuilder.newBuilder(table)
- .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1"))
- .build();
+ .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1"))
+ .build();
conn.getAdmin().createTable(desc);
try (Table t = conn.getTable(table)) {
Put p = new Put(Bytes.toBytes("a"));
@@ -331,6 +341,12 @@ public class TestSecureRESTServer {
return null;
}
});
+ }
+
+ public void testProxy(String extraArgs, String PRINCIPAL, File keytab, int
responseCode) throws Exception{
+ UserGroupInformation superuser =
UserGroupInformation.loginUserFromKeytabAndReturnUGI(
+ SERVICE_PRINCIPAL, serviceKeytab.getAbsolutePath());
+ final TableName table = TableName.valueOf("publicTable");
// Read that row as the client
Pair<CloseableHttpClient,HttpClientContext> pair = getClient();
@@ -338,33 +354,55 @@ public class TestSecureRESTServer {
HttpClientContext context = pair.getSecond();
HttpGet get = new HttpGet(new URL("http://localhost:"+
REST_TEST.getServletPort()).toURI()
- + "/" + table + "/a");
+ + "/" + table + "/a" + extraArgs);
get.addHeader("Accept", "application/json");
UserGroupInformation user =
UserGroupInformation.loginUserFromKeytabAndReturnUGI(
- CLIENT_PRINCIPAL, clientKeytab.getAbsolutePath());
+ PRINCIPAL, keytab.getAbsolutePath());
String jsonResponse = user.doAs(new PrivilegedExceptionAction<String>() {
@Override
public String run() throws Exception {
try (CloseableHttpResponse response = client.execute(get, context)) {
final int statusCode = response.getStatusLine().getStatusCode();
- assertEquals(response.getStatusLine().toString(),
HttpURLConnection.HTTP_OK, statusCode);
+ assertEquals(response.getStatusLine().toString(), responseCode,
statusCode);
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity);
}
}
});
- ObjectMapper mapper = new JacksonJaxbJsonProvider()
- .locateMapper(CellSetModel.class, MediaType.APPLICATION_JSON_TYPE);
- CellSetModel model = mapper.readValue(jsonResponse, CellSetModel.class);
- assertEquals(1, model.getRows().size());
- RowModel row = model.getRows().get(0);
- assertEquals("a", Bytes.toString(row.getKey()));
- assertEquals(1, row.getCells().size());
- CellModel cell = row.getCells().get(0);
- assertEquals("1", Bytes.toString(cell.getValue()));
+ if(responseCode == HttpURLConnection.HTTP_OK) {
+ ObjectMapper mapper = new
JacksonJaxbJsonProvider().locateMapper(CellSetModel.class,
MediaType.APPLICATION_JSON_TYPE);
+ CellSetModel model = mapper.readValue(jsonResponse, CellSetModel.class);
+ assertEquals(1, model.getRows().size());
+ RowModel row = model.getRows().get(0);
+ assertEquals("a", Bytes.toString(row.getKey()));
+ assertEquals(1, row.getCells().size());
+ CellModel cell = row.getCells().get(0);
+ assertEquals("1", Bytes.toString(cell.getValue()));
+ }
}
@Test
+ public void testPositiveAuthorization() throws Exception {
+ testProxy("", CLIENT_PRINCIPAL, clientKeytab, HttpURLConnection.HTTP_OK);
+ }
+
+ @Test
+ public void testDoAs() throws Exception {
+ testProxy("?doAs="+CLIENT_PRINCIPAL, WHEEL_PRINCIPAL, wheelKeytab,
HttpURLConnection.HTTP_OK);
+ }
+
+ @Test
+ public void testDoas() throws Exception {
+ testProxy("?doas="+CLIENT_PRINCIPAL, WHEEL_PRINCIPAL, wheelKeytab,
HttpURLConnection.HTTP_OK);
+ }
+
+ @Test
+ public void testWithoutDoAs() throws Exception {
+ testProxy("", WHEEL_PRINCIPAL, wheelKeytab,
HttpURLConnection.HTTP_FORBIDDEN);
+ }
+
+
+ @Test
public void testNegativeAuthorization() throws Exception {
Pair<CloseableHttpClient,HttpClientContext> pair = getClient();
CloseableHttpClient client = pair.getFirst();
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 f1353c8..c37401d 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
@@ -43,6 +43,7 @@ import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static
org.apache.hadoop.hbase.http.ProxyUserAuthenticationFilter.getDoasFromHeader;
/**
* Thrift Http Servlet is used for performing Kerberos authentication if
security is enabled and
@@ -112,7 +113,7 @@ public class ThriftHttpServlet extends TServlet {
effectiveUser = serviceUGI.getShortUserName();
}
- String doAsUserFromQuery = request.getHeader("doAs");
+ String doAsUserFromQuery = getDoasFromHeader(request);
if (doAsUserFromQuery != null) {
if (!doAsEnabled) {
throw new ServletException("Support for proxyuser is not configured");