001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.http; 019 020import static org.hamcrest.Matchers.greaterThan; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertNull; 024import static org.junit.Assert.assertTrue; 025 026import java.io.BufferedReader; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.InputStreamReader; 030import java.io.PrintWriter; 031import java.net.HttpURLConnection; 032import java.net.URI; 033import java.net.URL; 034import java.nio.CharBuffer; 035import java.nio.charset.StandardCharsets; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.Enumeration; 039import java.util.HashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.SortedSet; 043import java.util.TreeSet; 044import java.util.concurrent.CountDownLatch; 045import java.util.concurrent.Executor; 046import java.util.concurrent.Executors; 047import javax.servlet.Filter; 048import javax.servlet.FilterChain; 049import javax.servlet.FilterConfig; 050import javax.servlet.ServletContext; 051import javax.servlet.ServletException; 052import javax.servlet.ServletRequest; 053import javax.servlet.ServletResponse; 054import javax.servlet.http.HttpServlet; 055import javax.servlet.http.HttpServletRequest; 056import javax.servlet.http.HttpServletRequestWrapper; 057import javax.servlet.http.HttpServletResponse; 058import org.apache.hadoop.conf.Configuration; 059import org.apache.hadoop.fs.CommonConfigurationKeys; 060import org.apache.hadoop.hbase.HBaseClassTestRule; 061import org.apache.hadoop.hbase.http.HttpServer.QuotingInputFilter.RequestQuoter; 062import org.apache.hadoop.hbase.http.resource.JerseyResource; 063import org.apache.hadoop.hbase.testclassification.MiscTests; 064import org.apache.hadoop.hbase.testclassification.SmallTests; 065import org.apache.hadoop.net.NetUtils; 066import org.apache.hadoop.security.Groups; 067import org.apache.hadoop.security.ShellBasedUnixGroupsMapping; 068import org.apache.hadoop.security.UserGroupInformation; 069import org.apache.hadoop.security.authorize.AccessControlList; 070import org.apache.http.HttpEntity; 071import org.apache.http.HttpHeaders; 072import org.apache.http.client.methods.CloseableHttpResponse; 073import org.apache.http.client.methods.HttpGet; 074import org.apache.http.impl.client.CloseableHttpClient; 075import org.apache.http.impl.client.HttpClients; 076import org.hamcrest.MatcherAssert; 077import org.junit.AfterClass; 078import org.junit.Assert; 079import org.junit.BeforeClass; 080import org.junit.ClassRule; 081import org.junit.Ignore; 082import org.junit.Test; 083import org.junit.experimental.categories.Category; 084import org.mockito.Mockito; 085import org.slf4j.Logger; 086import org.slf4j.LoggerFactory; 087 088import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector; 089import org.apache.hbase.thirdparty.org.eclipse.jetty.util.ajax.JSON; 090 091@Category({ MiscTests.class, SmallTests.class }) 092public class TestHttpServer extends HttpServerFunctionalTest { 093 @ClassRule 094 public static final HBaseClassTestRule CLASS_RULE = 095 HBaseClassTestRule.forClass(TestHttpServer.class); 096 097 private static final Logger LOG = LoggerFactory.getLogger(TestHttpServer.class); 098 private static HttpServer server; 099 private static URL baseUrl; 100 // jetty 9.4.x needs this many threads to start, even in the small. 101 static final int MAX_THREADS = 16; 102 103 @SuppressWarnings("serial") 104 public static class EchoMapServlet extends HttpServlet { 105 @Override 106 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 107 PrintWriter out = response.getWriter(); 108 Map<String, String[]> params = request.getParameterMap(); 109 SortedSet<String> keys = new TreeSet<>(params.keySet()); 110 for (String key : keys) { 111 out.print(key); 112 out.print(':'); 113 String[] values = params.get(key); 114 if (values.length > 0) { 115 out.print(values[0]); 116 for (int i = 1; i < values.length; ++i) { 117 out.print(','); 118 out.print(values[i]); 119 } 120 } 121 out.print('\n'); 122 } 123 out.close(); 124 } 125 } 126 127 @SuppressWarnings("serial") 128 public static class EchoServlet extends HttpServlet { 129 @Override 130 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 131 PrintWriter out = response.getWriter(); 132 SortedSet<String> sortedKeys = new TreeSet<>(); 133 Enumeration<String> keys = request.getParameterNames(); 134 while (keys.hasMoreElements()) { 135 sortedKeys.add(keys.nextElement()); 136 } 137 for (String key : sortedKeys) { 138 out.print(key); 139 out.print(':'); 140 out.print(request.getParameter(key)); 141 out.print('\n'); 142 } 143 out.close(); 144 } 145 } 146 147 @SuppressWarnings("serial") 148 public static class LongHeaderServlet extends HttpServlet { 149 @Override 150 public void doGet(HttpServletRequest request, HttpServletResponse response) { 151 Assert.assertEquals(63 * 1024, request.getHeader("longheader").length()); 152 response.setStatus(HttpServletResponse.SC_OK); 153 } 154 } 155 156 @SuppressWarnings("serial") 157 public static class HtmlContentServlet extends HttpServlet { 158 @Override 159 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 160 response.setContentType("text/html"); 161 PrintWriter out = response.getWriter(); 162 out.print("hello world"); 163 out.close(); 164 } 165 } 166 167 @BeforeClass 168 public static void setup() throws Exception { 169 Configuration conf = new Configuration(); 170 conf.setInt(HttpServer.HTTP_MAX_THREADS, MAX_THREADS); 171 server = createTestServer(conf); 172 server.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 173 server.addUnprivilegedServlet("echomap", "/echomap", EchoMapServlet.class); 174 server.addUnprivilegedServlet("htmlcontent", "/htmlcontent", HtmlContentServlet.class); 175 server.addUnprivilegedServlet("longheader", "/longheader", LongHeaderServlet.class); 176 server.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*"); 177 server.start(); 178 baseUrl = getServerURL(server); 179 LOG.info("HTTP server started: " + baseUrl); 180 } 181 182 @AfterClass 183 public static void cleanup() throws Exception { 184 server.stop(); 185 } 186 187 /** 188 * Test the maximum number of threads cannot be exceeded. 189 */ 190 @Test 191 public void testMaxThreads() throws Exception { 192 int clientThreads = MAX_THREADS * 10; 193 Executor executor = Executors.newFixedThreadPool(clientThreads); 194 // Run many clients to make server reach its maximum number of threads 195 final CountDownLatch ready = new CountDownLatch(clientThreads); 196 final CountDownLatch start = new CountDownLatch(1); 197 for (int i = 0; i < clientThreads; i++) { 198 executor.execute(() -> { 199 ready.countDown(); 200 try { 201 start.await(); 202 assertEquals("a:b\nc:d\n", readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 203 int serverThreads = server.webServer.getThreadPool().getThreads(); 204 assertTrue( 205 "More threads are started than expected, Server Threads count: " + serverThreads, 206 serverThreads <= MAX_THREADS); 207 LOG.info("Number of threads = " + serverThreads 208 + " which is less or equal than the max = " + MAX_THREADS); 209 } catch (Exception e) { 210 // do nothing 211 } 212 }); 213 } 214 // Start the client threads when they are all ready 215 ready.await(); 216 start.countDown(); 217 } 218 219 @Test 220 public void testEcho() throws Exception { 221 assertEquals("a:b\nc:d\n", readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 222 assertEquals("a:b\nc<:d\ne:>\n", readOutput(new URL(baseUrl, "/echo?a=b&c<=d&e=>"))); 223 } 224 225 /** Test the echo map servlet that uses getParameterMap. */ 226 @Test 227 public void testEchoMap() throws Exception { 228 assertEquals("a:b\nc:d\n", readOutput(new URL(baseUrl, "/echomap?a=b&c=d"))); 229 assertEquals("a:b,>\nc<:d\n", readOutput(new URL(baseUrl, "/echomap?a=b&c<=d&a=>"))); 230 } 231 232 /** 233 * Test that verifies headers can be up to 64K long. The test adds a 63K header leaving 1K for 234 * other headers. This is because the header buffer setting is for ALL headers, names and values 235 * included. 236 */ 237 @Test 238 public void testLongHeader() throws Exception { 239 URL url = new URL(baseUrl, "/longheader"); 240 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 241 StringBuilder sb = new StringBuilder(); 242 for (int i = 0; i < 63 * 1024; i++) { 243 sb.append("a"); 244 } 245 conn.setRequestProperty("longheader", sb.toString()); 246 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 247 } 248 249 @Test 250 public void testContentTypes() throws Exception { 251 // Static CSS files should have text/css 252 URL cssUrl = new URL(baseUrl, "/static/test.css"); 253 HttpURLConnection conn = (HttpURLConnection) cssUrl.openConnection(); 254 conn.connect(); 255 assertEquals(200, conn.getResponseCode()); 256 assertEquals("text/css", conn.getContentType()); 257 258 // Servlets should have text/plain with proper encoding by default 259 URL servletUrl = new URL(baseUrl, "/echo?a=b"); 260 conn = (HttpURLConnection) servletUrl.openConnection(); 261 conn.connect(); 262 assertEquals(200, conn.getResponseCode()); 263 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 264 265 // We should ignore parameters for mime types - ie a parameter 266 // ending in .css should not change mime type 267 servletUrl = new URL(baseUrl, "/echo?a=b.css"); 268 conn = (HttpURLConnection) servletUrl.openConnection(); 269 conn.connect(); 270 assertEquals(200, conn.getResponseCode()); 271 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 272 273 // Servlets that specify text/html should get that content type 274 servletUrl = new URL(baseUrl, "/htmlcontent"); 275 conn = (HttpURLConnection) servletUrl.openConnection(); 276 conn.connect(); 277 assertEquals(200, conn.getResponseCode()); 278 assertEquals("text/html;charset=utf-8", conn.getContentType()); 279 280 // JSPs should default to text/html with utf8 281 // JSPs do not work from unit tests 282 // servletUrl = new URL(baseUrl, "/testjsp.jsp"); 283 // conn = (HttpURLConnection)servletUrl.openConnection(); 284 // conn.connect(); 285 // assertEquals(200, conn.getResponseCode()); 286 // assertEquals("text/html; charset=utf-8", conn.getContentType()); 287 } 288 289 @Test 290 public void testNegotiatesEncodingGzip() throws IOException { 291 final InputStream stream = ClassLoader.getSystemResourceAsStream("webapps/static/test.css"); 292 assertNotNull(stream); 293 final String sourceContent = readFully(stream); 294 295 try (final CloseableHttpClient client = HttpClients.createMinimal()) { 296 final HttpGet request = new HttpGet(new URL(baseUrl, "/static/test.css").toString()); 297 298 request.setHeader(HttpHeaders.ACCEPT_ENCODING, null); 299 final long unencodedContentLength; 300 try (final CloseableHttpResponse response = client.execute(request)) { 301 final HttpEntity entity = response.getEntity(); 302 assertNotNull(entity); 303 assertNull(entity.getContentEncoding()); 304 unencodedContentLength = entity.getContentLength(); 305 MatcherAssert.assertThat(unencodedContentLength, greaterThan(0L)); 306 final String unencodedEntityBody = readFully(entity.getContent()); 307 assertEquals(sourceContent, unencodedEntityBody); 308 } 309 310 request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip"); 311 final long encodedContentLength; 312 try (final CloseableHttpResponse response = client.execute(request)) { 313 final HttpEntity entity = response.getEntity(); 314 assertNotNull(entity); 315 assertNotNull(entity.getContentEncoding()); 316 assertEquals("gzip", entity.getContentEncoding().getValue()); 317 encodedContentLength = entity.getContentLength(); 318 MatcherAssert.assertThat(encodedContentLength, greaterThan(0L)); 319 final String encodedEntityBody = readFully(entity.getContent()); 320 // the encoding/decoding process, as implemented in this specific combination of dependency 321 // versions, does not perfectly preserve trailing whitespace. thus, `trim()`. 322 assertEquals(sourceContent.trim(), encodedEntityBody.trim()); 323 } 324 MatcherAssert.assertThat(unencodedContentLength, greaterThan(encodedContentLength)); 325 } 326 } 327 328 private static String readFully(final InputStream input) throws IOException { 329 // TODO: when the time comes, delete me and replace with a JDK11 IO helper API. 330 try (final BufferedReader reader = 331 new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) { 332 final StringBuilder sb = new StringBuilder(); 333 final CharBuffer buffer = CharBuffer.allocate(1024 * 2); 334 while (reader.read(buffer) > 0) { 335 sb.append(buffer); 336 buffer.clear(); 337 } 338 return sb.toString(); 339 } finally { 340 input.close(); 341 } 342 } 343 344 /** 345 * Dummy filter that mimics as an authentication filter. Obtains user identity from the request 346 * parameter user.name. Wraps around the request so that request.getRemoteUser() returns the user 347 * identity. 348 */ 349 public static class DummyServletFilter implements Filter { 350 @Override 351 public void destroy() { 352 } 353 354 @Override 355 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 356 throws IOException, ServletException { 357 final String userName = request.getParameter("user.name"); 358 ServletRequest requestModified = new HttpServletRequestWrapper((HttpServletRequest) request) { 359 @Override 360 public String getRemoteUser() { 361 return userName; 362 } 363 }; 364 filterChain.doFilter(requestModified, response); 365 } 366 367 @Override 368 public void init(FilterConfig arg0) { 369 } 370 } 371 372 /** 373 * FilterInitializer that initialized the DummyFilter. 374 */ 375 public static class DummyFilterInitializer extends FilterInitializer { 376 public DummyFilterInitializer() { 377 } 378 379 @Override 380 public void initFilter(FilterContainer container, Configuration conf) { 381 container.addFilter("DummyFilter", DummyServletFilter.class.getName(), null); 382 } 383 } 384 385 /** 386 * Access a URL and get the corresponding return Http status code. The URL will be accessed as the 387 * passed user, by sending user.name request parameter. 388 * @param urlstring The url to access 389 * @param userName The user to perform access as 390 * @return The HTTP response code 391 * @throws IOException if there is a problem communicating with the server 392 */ 393 private static int getHttpStatusCode(String urlstring, String userName) throws IOException { 394 URL url = new URL(urlstring + "?user.name=" + userName); 395 System.out.println("Accessing " + url + " as user " + userName); 396 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 397 connection.connect(); 398 return connection.getResponseCode(); 399 } 400 401 /** 402 * Custom user->group mapping service. 403 */ 404 public static class MyGroupsProvider extends ShellBasedUnixGroupsMapping { 405 static Map<String, List<String>> mapping = new HashMap<>(); 406 407 static void clearMapping() { 408 mapping.clear(); 409 } 410 411 @Override 412 public List<String> getGroups(String user) { 413 return mapping.get(user); 414 } 415 } 416 417 /** 418 * Verify the access for /logs, /stacks, /conf, /logLevel and /metrics servlets, when 419 * authentication filters are set, but authorization is not enabled. 420 */ 421 @Test 422 @Ignore 423 public void testDisabledAuthorizationOfDefaultServlets() throws Exception { 424 Configuration conf = new Configuration(); 425 426 // Authorization is disabled by default 427 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, DummyFilterInitializer.class.getName()); 428 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 429 MyGroupsProvider.class.getName()); 430 Groups.getUserToGroupsMappingService(conf); 431 MyGroupsProvider.clearMapping(); 432 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 433 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 434 435 HttpServer myServer = new HttpServer.Builder().setName("test") 436 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).build(); 437 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 438 myServer.start(); 439 String serverURL = 440 "http://" + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 441 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 442 for (String user : new String[] { "userA", "userB" }) { 443 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL + servlet, user)); 444 } 445 } 446 myServer.stop(); 447 } 448 449 /** 450 * Verify the administrator access for /logs, /stacks, /conf, /logLevel and /metrics servlets. 451 */ 452 @Test 453 @Ignore 454 public void testAuthorizationOfDefaultServlets() throws Exception { 455 Configuration conf = new Configuration(); 456 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 457 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, true); 458 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, DummyFilterInitializer.class.getName()); 459 460 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 461 MyGroupsProvider.class.getName()); 462 Groups.getUserToGroupsMappingService(conf); 463 MyGroupsProvider.clearMapping(); 464 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 465 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 466 MyGroupsProvider.mapping.put("userC", Collections.singletonList("groupC")); 467 MyGroupsProvider.mapping.put("userD", Collections.singletonList("groupD")); 468 MyGroupsProvider.mapping.put("userE", Collections.singletonList("groupE")); 469 470 HttpServer myServer = new HttpServer.Builder().setName("test") 471 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).setConf(conf) 472 .setACL(new AccessControlList("userA,userB groupC,groupD")).build(); 473 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 474 myServer.start(); 475 476 String serverURL = 477 "http://" + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 478 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 479 for (String user : new String[] { "userA", "userB", "userC", "userD" }) { 480 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL + servlet, user)); 481 } 482 assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, 483 getHttpStatusCode(serverURL + servlet, "userE")); 484 } 485 myServer.stop(); 486 } 487 488 @Test 489 public void testRequestQuoterWithNull() { 490 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 491 Mockito.doReturn(null).when(request).getParameterValues("dummy"); 492 RequestQuoter requestQuoter = new RequestQuoter(request); 493 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 494 Assert.assertNull("It should return null " + "when there are no values for the parameter", 495 parameterValues); 496 } 497 498 @Test 499 public void testRequestQuoterWithNotNull() { 500 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 501 String[] values = new String[] { "abc", "def" }; 502 Mockito.doReturn(values).when(request).getParameterValues("dummy"); 503 RequestQuoter requestQuoter = new RequestQuoter(request); 504 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 505 Assert.assertTrue("It should return Parameter Values", Arrays.equals(values, parameterValues)); 506 } 507 508 @SuppressWarnings("unchecked") 509 private static Map<String, Object> parse(String jsonString) { 510 return (Map<String, Object>) JSON.parse(jsonString); 511 } 512 513 @Test 514 public void testJersey() throws Exception { 515 LOG.info("BEGIN testJersey()"); 516 final String js = readOutput(new URL(baseUrl, "/jersey/foo?op=bar")); 517 final Map<String, Object> m = parse(js); 518 LOG.info("m=" + m); 519 assertEquals("foo", m.get(JerseyResource.PATH)); 520 assertEquals("bar", m.get(JerseyResource.OP)); 521 LOG.info("END testJersey()"); 522 } 523 524 @Test 525 public void testHasAdministratorAccess() throws Exception { 526 Configuration conf = new Configuration(); 527 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false); 528 ServletContext context = Mockito.mock(ServletContext.class); 529 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 530 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(null); 531 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 532 Mockito.when(request.getRemoteUser()).thenReturn(null); 533 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 534 535 // authorization OFF 536 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 537 538 // authorization ON & user NULL 539 response = Mockito.mock(HttpServletResponse.class); 540 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 541 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 542 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), 543 Mockito.anyString()); 544 545 // authorization ON & user NOT NULL & ACLs NULL 546 response = Mockito.mock(HttpServletResponse.class); 547 Mockito.when(request.getRemoteUser()).thenReturn("foo"); 548 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 549 550 // authorization ON & user NOT NULL & ACLs NOT NULL & user not in ACLs 551 response = Mockito.mock(HttpServletResponse.class); 552 AccessControlList acls = Mockito.mock(AccessControlList.class); 553 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation> any())).thenReturn(false); 554 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 555 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 556 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_FORBIDDEN), 557 Mockito.anyString()); 558 559 // authorization ON & user NOT NULL & ACLs NOT NULL & user in in ACLs 560 response = Mockito.mock(HttpServletResponse.class); 561 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation> any())).thenReturn(true); 562 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 563 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 564 565 } 566 567 @Test 568 public void testRequiresAuthorizationAccess() throws Exception { 569 Configuration conf = new Configuration(); 570 ServletContext context = Mockito.mock(ServletContext.class); 571 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 572 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 573 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 574 575 // requires admin access to instrumentation, FALSE by default 576 Assert.assertTrue(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 577 578 // requires admin access to instrumentation, TRUE 579 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, true); 580 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 581 AccessControlList acls = Mockito.mock(AccessControlList.class); 582 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation> any())).thenReturn(false); 583 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 584 Assert.assertFalse(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 585 } 586 587 @Test 588 public void testBindAddress() throws Exception { 589 checkBindAddress("localhost", 0, false).stop(); 590 // hang onto this one for a bit more testing 591 HttpServer myServer = checkBindAddress("localhost", 0, false); 592 HttpServer myServer2 = null; 593 try { 594 int port = myServer.getConnectorAddress(0).getPort(); 595 // it's already in use, true = expect a higher port 596 myServer2 = checkBindAddress("localhost", port, true); 597 // try to reuse the port 598 port = myServer2.getConnectorAddress(0).getPort(); 599 myServer2.stop(); 600 assertNull(myServer2.getConnectorAddress(0)); // not bound 601 myServer2.openListeners(); 602 assertEquals(port, myServer2.getConnectorAddress(0).getPort()); // expect same port 603 } finally { 604 myServer.stop(); 605 if (myServer2 != null) { 606 myServer2.stop(); 607 } 608 } 609 } 610 611 private HttpServer checkBindAddress(String host, int port, boolean findPort) throws Exception { 612 HttpServer server = createServer(host, port); 613 try { 614 // not bound, ephemeral should return requested port (0 for ephemeral) 615 ServerConnector listener = server.getServerConnectors().get(0); 616 617 assertEquals(port, listener.getPort()); 618 // verify hostname is what was given 619 server.openListeners(); 620 assertEquals(host, server.getConnectorAddress(0).getHostName()); 621 622 int boundPort = server.getConnectorAddress(0).getPort(); 623 if (port == 0) { 624 assertTrue(boundPort != 0); // ephemeral should now return bound port 625 } else if (findPort) { 626 assertTrue(boundPort > port); 627 // allow a little wiggle room to prevent random test failures if 628 // some consecutive ports are already in use 629 assertTrue(boundPort - port < 8); 630 } 631 } catch (Exception e) { 632 server.stop(); 633 throw e; 634 } 635 return server; 636 } 637 638 @Test 639 public void testXFrameHeaderSameOrigin() throws Exception { 640 Configuration conf = new Configuration(); 641 conf.set("hbase.http.filter.xframeoptions.mode", "SAMEORIGIN"); 642 643 HttpServer myServer = new HttpServer.Builder().setName("test") 644 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).setConf(conf).build(); 645 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 646 myServer.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 647 myServer.start(); 648 649 String serverURL = "http://" + NetUtils.getHostPortString(myServer.getConnectorAddress(0)); 650 URL url = new URL(new URL(serverURL), "/echo?a=b&c=d"); 651 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 652 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 653 assertEquals("SAMEORIGIN", conn.getHeaderField("X-Frame-Options")); 654 myServer.stop(); 655 } 656 657 @Test 658 public void testNoCacheHeader() throws Exception { 659 URL url = new URL(baseUrl, "/echo?a=b&c=d"); 660 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 661 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 662 assertEquals("no-cache", conn.getHeaderField("Cache-Control")); 663 assertEquals("no-cache", conn.getHeaderField("Pragma")); 664 assertNotNull(conn.getHeaderField("Expires")); 665 assertNotNull(conn.getHeaderField("Date")); 666 assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date")); 667 assertEquals("DENY", conn.getHeaderField("X-Frame-Options")); 668 } 669 670 @Test 671 public void testHttpMethods() throws Exception { 672 // HTTP TRACE method should be disabled for security 673 // See https://www.owasp.org/index.php/Cross_Site_Tracing 674 URL url = new URL(baseUrl, "/echo?a=b"); 675 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 676 conn.setRequestMethod("TRACE"); 677 conn.connect(); 678 assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); 679 } 680}