From b2c2039dfc5c99bfc669dfefd88ddfdb24d44223 Mon Sep 17 00:00:00 2001 From: Sunil G Date: Tue, 5 Jun 2018 15:33:08 +0530 Subject: [PATCH] YARN-8258 --- .../org/apache/hadoop/yarn/webapp/WebApps.java | 35 ++---- .../server/resourcemanager/ResourceManager.java | 14 ++- .../resourcemanager/webapp/RMWebAppUtil.java | 76 ++++++++++++ .../yarn/server/resourcemanager/TestWebApps.java | 127 +++++++++++++++++++++ 4 files changed, 224 insertions(+), 28 deletions(-) create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestWebApps.java diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java index 73644452140..8258b0860e0 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java @@ -44,7 +44,6 @@ import org.apache.hadoop.security.http.XFrameOptionsFilter; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; -import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -417,16 +416,8 @@ public WebApp start() { } public WebApp start(WebApp webapp) { - return start(webapp, null); - } - - public WebApp start(WebApp webapp, WebAppContext ui2Context) { WebApp webApp = build(webapp); HttpServer2 httpServer = webApp.httpServer(); - if (ui2Context != null) { - addFiltersForNewContext(ui2Context); - httpServer.addHandlerAtFront(ui2Context); - } try { httpServer.start(); LOG.info("Web app " + name + " started at " @@ -437,24 +428,14 @@ public WebApp start(WebApp webapp, WebAppContext ui2Context) { return webApp; } - private void addFiltersForNewContext(WebAppContext ui2Context) { - Map params = getConfigParameters(csrfConfigPrefix); - - if (hasCSRFEnabled(params)) { - LOG.info("CSRF Protection has been enabled for the {} application. " - + "Please ensure that there is an authentication mechanism " - + "enabled (kerberos, custom, etc).", name); - String restCsrfClassName = RestCsrfPreventionFilter.class.getName(); - HttpServer2.defineFilter(ui2Context, restCsrfClassName, - restCsrfClassName, params, new String[]{"/*"}); - } - - params = getConfigParameters(xfsConfigPrefix); - - if (hasXFSEnabled()) { - String xfsClassName = XFrameOptionsFilter.class.getName(); - HttpServer2.defineFilter(ui2Context, xfsClassName, xfsClassName, params, - new String[]{"/*"}); + public void startWithOutBuild(WebApp webApp) { + HttpServer2 httpServer = webApp.httpServer(); + try { + httpServer.start(); + LOG.info("Web app " + name + " started at " + + httpServer.getConnectorAddress(0).getPort()); + } catch (IOException e) { + throw new WebAppException("Error starting http server", e); } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java index c53311127c0..54c18a88cd6 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java @@ -1167,7 +1167,19 @@ protected void startWepApp() { } } - webApp = builder.start(new RMWebApp(this), uiWebAppContext); + // Build the webapp. + webApp = builder.build(new RMWebApp(this)); + + // If UI2 is enabled, add UI2 context to webapp. + if (uiWebAppContext != null) { + // Copy all necessary filters from default context to UI2. + RMWebAppUtil.addFiltersForUI2Context(uiWebAppContext, + webApp.httpServer()); + webApp.httpServer().addHandlerAtFront(uiWebAppContext); + } + + // Start webapp after setting all required contexts. + builder.startWithOutBuild(webApp); } private String getWebAppsPath(String appName) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebAppUtil.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebAppUtil.java index 531ce975ab7..fca4b822e2f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebAppUtil.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebAppUtil.java @@ -23,7 +23,10 @@ import java.security.Principal; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; @@ -31,6 +34,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.http.HttpServer2; import org.apache.hadoop.http.lib.StaticUserWebFilter; import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.io.Text; @@ -60,6 +64,9 @@ import org.apache.hadoop.yarn.server.security.http.RMAuthenticationFilter; import org.apache.hadoop.yarn.server.security.http.RMAuthenticationFilterInitializer; import org.apache.hadoop.yarn.webapp.BadRequestException; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.webapp.WebAppContext; /** * Util class for ResourceManager WebApp. @@ -370,4 +377,73 @@ public static UserGroupInformation getCallerUserGroupInformation( return callerUGI; } + + /** + * UI2 Context doesn't have all custom filters configured for RM. Hence add + * all required filters to UI2 as well. + * + * Filters which are set as /* for pathSpec from Default Context: + * 1. NoCacheFilter + * 2. safety + * 3. org.apache.hadoop.security.http.XFrameOptionsFilter + * 4. AuthenticationFilter + * Default context has multiple pathSpecs such as /cluster, /jmx etc. + * These are not relevant for UI2 and hence only /* will be added as + * pathSpec for UI2. + * + * Filters which are to be set as NULL (pathSpec) similar to Default Context: + * 1. SpnegoFilter + * In Spnego Filter, pathSpec is specified as NULL. This is not ensure + * that this filter should come after AuthenticationFilter in filter + * chain. Hence using same for UI2 context as well. + * + * Filters which are skipped for UI2: + * 1. guice + * + * @param ui2Context New UI2 context + * @param httpServer Default httpServer from context. + */ + public static void addFiltersForUI2Context(WebAppContext ui2Context, + HttpServer2 httpServer) { + // Get filterHolders and filterMappings from default context's servlet. + FilterHolder[] filterHolders = httpServer.getWebAppContext() + .getServletHandler().getFilters(); + FilterMapping[] filterMappings = httpServer.getWebAppContext() + .getServletHandler().getFilterMappings(); + + // To define a filter for UI2 context, URL path also needed which is + // available only in FilterMapping. Hence convert this to a map for easy + // access. + Set nonNullFilterMappings = new HashSet<>(); + for (FilterMapping filterMapping : filterMappings) { + if (filterMapping.getPathSpecs() != null) { + nonNullFilterMappings.add(filterMapping.getFilterName()); + } + } + + // Loop through all filterHolders and add one by one to UI2. + LOG.info("Add filters from default webapp context to UI2 context."); + for (FilterHolder filterHolder : filterHolders) { + // Skip guice filter for UI2. + if ("guice".equals(filterHolder.getName())) { + continue; + } + + List pathSpecs = new ArrayList<>(); + + // Given a filter from default context has some specific pathSpec, + // UI2 can apply the same filter at /* on the root context path. + if (nonNullFilterMappings.contains(filterHolder.getName())) { + pathSpecs.add("/*"); + } + + // Define filter to UI2 context with corrected pathSpec. + HttpServer2.defineFilter(ui2Context, filterHolder.getName(), + filterHolder.getClassName(), filterHolder.getInitParameters(), + pathSpecs.toArray(new String[pathSpecs.size()])); + + LOG.info("UI2 context filter Name:" + filterHolder.getName() + + ", className=" + filterHolder.getClassName()); + } + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestWebApps.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestWebApps.java new file mode 100644 index 00000000000..8bf7495d505 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestWebApps.java @@ -0,0 +1,127 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.yarn.server.resourcemanager; + +import java.io.File; + +import static org.junit.Assert.assertTrue; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +import org.apache.hadoop.http.HttpServer2; +import org.apache.hadoop.minikdc.MiniKdc; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.KerberosTestUtils; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.server.security.http.RMAuthenticationFilterInitializer; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test TestWebApps. + * + */ +public class TestWebApps { + + private static final File TEST_ROOT_DIR = new File("target", + TestWebApps.class.getName() + "-root"); + private static File httpSpnegoKeytabFile = new File( + KerberosTestUtils.getKeytabFile()); + private static String httpSpnegoPrincipal = KerberosTestUtils + .getServerPrincipal(); + private static boolean miniKDCStarted = false; + private static MiniKdc testMiniKDC; + private static MockRM rm; + + @BeforeClass + public static void setUp() { + try { + testMiniKDC = new MiniKdc(MiniKdc.createConf(), TEST_ROOT_DIR); + setupKDC(); + } catch (Exception e) { + assertTrue("Couldn't create MiniKDC:" + e.getMessage(), false); + } + setupAndStartRM(); + } + + private static void setupAndStartRM() { + Configuration rmconf = new Configuration(); + rmconf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); + rmconf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, + "kerberos"); + rmconf.setBoolean(YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER, + true); + rmconf.set("hadoop.http.filter.initializers", + RMAuthenticationFilterInitializer.class.getName()); + rmconf.set(YarnConfiguration.RM_WEBAPP_SPNEGO_USER_NAME_KEY, + httpSpnegoPrincipal); + rmconf.set(YarnConfiguration.RM_KEYTAB, + httpSpnegoKeytabFile.getAbsolutePath()); + rmconf.set(YarnConfiguration.RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY, + httpSpnegoKeytabFile.getAbsolutePath()); + + rmconf.setBoolean(MockRM.ENABLE_WEBAPP, true); + UserGroupInformation.setConfiguration(rmconf); + rm = new MockRM(rmconf); + rm.start(); + } + + @AfterClass + public static void tearDown() { + if (testMiniKDC != null) { + testMiniKDC.stop(); + } + if (rm != null) { + rm.stop(); + } + } + + private static void setupKDC() throws Exception { + if (!miniKDCStarted) { + testMiniKDC.start(); + getKdc().createPrincipal(httpSpnegoKeytabFile, "HTTP/localhost", + UserGroupInformation.getLoginUser().getShortUserName()); + miniKDCStarted = true; + } + } + + private static MiniKdc getKdc() { + return testMiniKDC; + } + + @Test + public void testWebAppInitializersOrdering() throws Exception { + String[] filterNamesArray = {"NoCacheFilter", "safety", + "RMAuthenticationFilter", "SpnegoFilter", + "org.apache.hadoop.security.http.XFrameOptionsFilter", "guice"}; + + HttpServer2 server = rm.getWebapp().httpServer(); + WebAppContext context = server.getWebAppContext(); + FilterHolder[] filterHolders = context.getServletHandler().getFilters(); + Assert.assertEquals(filterHolders.length, 6); + for (int index = 0; index < filterHolders.length; index++) { + Assert.assertTrue( + filterHolders[index].getName().equals(filterNamesArray[index])); + } + } +} -- 2.14.3 (Apple Git-98)