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