This is an automated email from the ASF dual-hosted git repository. elserj pushed a commit to branch branch-2 in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2 by this push: new 5ce3e3e HBASE-24268 REST and Thrift server do not handle the "doAs" parameter case insensitively 5ce3e3e is described below commit 5ce3e3e12c19182150a1693dd410be9c602b6fea Author: Richard Antal <richard.an...@cloudera.com> 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 <els...@apache.org> Signed-off-by: Sean Busbey <bus...@apache.org> --- .../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");