SLIDER-710 restore AM filter (with an XML conf key to enable it)
Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/e5fb7f83 Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/e5fb7f83 Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/e5fb7f83 Branch: refs/heads/develop Commit: e5fb7f83ea1e7d45020aaa962bfe2518672450db Parents: ceb21e1 Author: Steve Loughran <[email protected]> Authored: Wed Jan 7 14:19:56 2015 +0000 Committer: Steve Loughran <[email protected]> Committed: Wed Jan 7 14:19:56 2015 +0000 ---------------------------------------------------------------------- .../common/SliderXMLConfKeysForTesting.java | 9 +- .../apache/slider/common/SliderXmlConfKeys.java | 5 + .../server/appmaster/SliderAppMaster.java | 104 ++++++++++++------- .../appmaster/web/rest/InsecureAmFilter.java | 101 ++++++++++++++++++ .../web/rest/InsecureAmFilterInitializer.java | 103 ++++++++++++++++++ 5 files changed, 278 insertions(+), 44 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e5fb7f83/slider-core/src/main/java/org/apache/slider/common/SliderXMLConfKeysForTesting.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/common/SliderXMLConfKeysForTesting.java b/slider-core/src/main/java/org/apache/slider/common/SliderXMLConfKeysForTesting.java index 41c61d4..0a7f292 100644 --- a/slider-core/src/main/java/org/apache/slider/common/SliderXMLConfKeysForTesting.java +++ b/slider-core/src/main/java/org/apache/slider/common/SliderXMLConfKeysForTesting.java @@ -22,14 +22,7 @@ package org.apache.slider.common; * Keys shared across tests */ public interface SliderXMLConfKeysForTesting { - - String KEY_TEST_HBASE_HOME = "slider.test.hbase.home"; - String KEY_TEST_HBASE_TAR = "slider.test.hbase.tar"; - String KEY_TEST_HBASE_APPCONF = "slider.test.hbase.appconf"; - String KEY_TEST_ACCUMULO_HOME = "slider.test.accumulo.home"; - String KEY_TEST_ACCUMULO_TAR = "slider.test.accumulo.tar"; - String KEY_TEST_ACCUMULO_APPCONF = "slider.test.accumulo.appconf"; - + String KEY_TEST_THAW_WAIT_TIME = "slider.test.thaw.wait.seconds"; int DEFAULT_THAW_WAIT_TIME_SECONDS = 60; http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e5fb7f83/slider-core/src/main/java/org/apache/slider/common/SliderXmlConfKeys.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/common/SliderXmlConfKeys.java b/slider-core/src/main/java/org/apache/slider/common/SliderXmlConfKeys.java index 9b1316e..0672955 100644 --- a/slider-core/src/main/java/org/apache/slider/common/SliderXmlConfKeys.java +++ b/slider-core/src/main/java/org/apache/slider/common/SliderXmlConfKeys.java @@ -143,4 +143,9 @@ public interface SliderXmlConfKeys { * The path to the python executable utilized to launch the agent. */ String PYTHON_EXECUTABLE_PATH = "agent.python.exec.path"; + + /** + * Flag to enable the insecure AM filter: {@value} + */ + String X_DEV_INSECURE_WS = "slider.dev.ws.insecure"; } http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e5fb7f83/slider-core/src/main/java/org/apache/slider/server/appmaster/SliderAppMaster.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/SliderAppMaster.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/SliderAppMaster.java index 7b9f6db..c34c692 100644 --- a/slider-core/src/main/java/org/apache/slider/server/appmaster/SliderAppMaster.java +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/SliderAppMaster.java @@ -146,6 +146,7 @@ import org.apache.slider.server.appmaster.state.RoleInstance; import org.apache.slider.server.appmaster.state.RoleStatus; import org.apache.slider.server.appmaster.state.SimpleReleaseSelector; import org.apache.slider.server.appmaster.web.AgentService; +import org.apache.slider.server.appmaster.web.rest.InsecureAmFilterInitializer; import org.apache.slider.server.appmaster.web.rest.agent.AgentWebApp; import org.apache.slider.server.appmaster.web.SliderAMWebApp; import org.apache.slider.server.appmaster.web.WebAppApi; @@ -385,6 +386,7 @@ public class SliderAppMaster extends AbstractSliderLaunchedService * The port for the web application */ private int webAppPort; + private boolean securityEnabled; /** * Service Constructor @@ -593,7 +595,7 @@ public class SliderAppMaster extends AbstractSliderLaunchedService securityConfiguration = new SecurityConfiguration( serviceConf, instanceDefinition, clustername); // obtain security state - boolean securityEnabled = securityConfiguration.isSecurityEnabled(); + securityEnabled = securityConfiguration.isSecurityEnabled(); // set the global security flag for the instance definition instanceDefinition.getAppConfOperations().set( KEY_SECURITY_ENABLED, securityEnabled); @@ -607,9 +609,6 @@ public class SliderAppMaster extends AbstractSliderLaunchedService File parentFile = confDir.getParentFile(); log.info("Parent dir {}:\n{}", parentFile, SliderUtils.listDir(parentFile)); } - - // IP filtering - serviceConf.set(HADOOP_HTTP_FILTER_INITIALIZERS, AM_FILTER_NAME); //get our provider MapOperations globalInternalOptions = getGlobalInternalOptions(); @@ -655,6 +654,7 @@ public class SliderAppMaster extends AbstractSliderLaunchedService Map<String, String> envVars; List<Container> liveContainers; + /** * It is critical this section is synchronized, to stop async AM events * arriving while registering a restarting AM. @@ -719,7 +719,6 @@ public class SliderAppMaster extends AbstractSliderLaunchedService uploadServerCertForLocalization(clustername, fs); } - startAgentWebApp(appInformation, serviceConf); webAppPort = getPortToRequest(); if (webAppPort == 0) { @@ -795,7 +794,7 @@ public class SliderAppMaster extends AbstractSliderLaunchedService providerService.validateApplicationConfiguration(instanceDefinition, confDir, - securityEnabled); + securityEnabled); //determine the location for the role history data Path historyDir = new Path(clusterDirPath, HISTORY_DIR_NAME); @@ -815,11 +814,11 @@ public class SliderAppMaster extends AbstractSliderLaunchedService instanceDefinition.getName(), appState.getRolePriorityMap()); // add the AM to the list of nodes in the cluster - + appState.buildAppMasterNode(appMasterContainerID, - appMasterHostname, + appMasterHostname, webAppPort, - appMasterHostname + ":" + webAppPort); + appMasterHostname + ":" + webAppPort); // build up environment variables that the AM wants set in every container // irrespective of provider and role. @@ -872,13 +871,7 @@ public class SliderAppMaster extends AbstractSliderLaunchedService service_user_name = RegistryUtils.currentUser(); log.info("Registry service username ={}", service_user_name); - // now do the registration - registerServiceInstance(clustername, appid); - // log the YARN and web UIs - log.info("RM Webapp address {}", serviceConf.get(YarnConfiguration.RM_WEBAPP_ADDRESS)); - log.info("slider Webapp address {}", appMasterTrackingUrl); - // declare the cluster initialized log.info("Application Master Initialization Completed"); initCompleted.set(true); @@ -891,8 +884,28 @@ public class SliderAppMaster extends AbstractSliderLaunchedService startQueueProcessing(); - deployWebApplication(serviceConf, webAppPort); + // Web service endpoints: initialize + + WebAppApiImpl webAppApi = + new WebAppApiImpl(this, + stateForProviders, + providerService, + certificateManager, + registryOperations, + metricsAndMonitoring); + initAMFilterOptions(serviceConf); + // start the agent web app + startAgentWebApp(appInformation, serviceConf, webAppApi); + deployWebApplication(serviceConf, webAppPort, webAppApi); + + // YARN Registry do the registration + registerServiceInstance(clustername, appid); + + // log the YARN and web UIs + log.info("RM Webapp address {}", + serviceConf.get(YarnConfiguration.RM_WEBAPP_ADDRESS)); + log.info("slider Webapp address {}", appMasterTrackingUrl); // Start the Slider AM provider sliderAMProvider.start(); @@ -917,17 +930,13 @@ public class SliderAppMaster extends AbstractSliderLaunchedService * Creates and starts the web application, and adds a * <code>WebAppService</code> service under the AM, to ensure * a managed web application shutdown. - * - * @param serviceConf AM configuration + * @param serviceConf AM configuration * @param port port to deploy the web application on + * @param webAppApi web app API instance */ - private void deployWebApplication(Configuration serviceConf, int port) { - WebAppApi webAppApi = new WebAppApiImpl(this, - stateForProviders, - providerService, - certificateManager, - registryOperations, - metricsAndMonitoring); + private void deployWebApplication(Configuration serviceConf, + int port, WebAppApiImpl webAppApi) { + webApp = new SliderAMWebApp(webAppApi); WebApps.$for(SliderAMWebApp.BASE_PATH, WebAppApi.class, @@ -1061,26 +1070,31 @@ public class SliderAppMaster extends AbstractSliderLaunchedService } } + /** + * Set up and start the agent web application + * @param appInformation application information + * @param serviceConf service configuration + * @param webAppApi web app API instance to bind to + * @throws IOException + */ private void startAgentWebApp(MapOperations appInformation, - Configuration serviceConf) throws IOException { + Configuration serviceConf, WebAppApiImpl webAppApi) throws IOException { URL[] urls = ((URLClassLoader) AgentWebApp.class.getClassLoader() ).getURLs(); StringBuilder sb = new StringBuilder("AM classpath:"); for (URL url : urls) { sb.append("\n").append(url.toString()); } - LOG_YARN.info(sb.append("\n").toString()); + LOG_YARN.debug(sb.append("\n").toString()); + initAMFilterOptions(serviceConf); + + // Start up the agent web app and track the URL for it + MapOperations appMasterConfig = getInstanceDefinition() + .getAppConfOperations().getComponent(SliderKeys.COMPONENT_AM); AgentWebApp agentWebApp = AgentWebApp.$for(AgentWebApp.BASE_PATH, - new WebAppApiImpl(this, - stateForProviders, - providerService, - certificateManager, - registryOperations, - metricsAndMonitoring), + webAppApi, RestPaths.AGENT_WS_CONTEXT) - .withComponentConfig(getInstanceDefinition().getAppConfOperations() - .getComponent( - SliderKeys.COMPONENT_AM)) + .withComponentConfig(appMasterConfig) .start(); agentOpsUrl = "https://" + appMasterHostname + ":" + agentWebApp.getSecuredPort(); @@ -1102,6 +1116,24 @@ public class SliderAppMaster extends AbstractSliderLaunchedService } /** + * Set up the AM filter + * @param serviceConf configuration to patch + */ + private void initAMFilterOptions(Configuration serviceConf) { + // IP filtering + String amFilterName = AM_FILTER_NAME; + + // This is here until YARN supports proxy & redirect operations + // on verbs other than GET, and is only supported for testing + if (serviceConf.getBoolean(SliderXmlConfKeys.X_DEV_INSECURE_WS, false)) { + log.warn("Insecure filter enabled: REST operations are unauthenticated"); + amFilterName = InsecureAmFilterInitializer.NAME; + } + + serviceConf.set(HADOOP_HTTP_FILTER_INITIALIZERS, amFilterName); + } + + /** * This registers the service instance and its external values * @param instanceName name of this instance * @param appid application ID http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e5fb7f83/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilter.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilter.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilter.java new file mode 100644 index 0000000..07b19e7 --- /dev/null +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilter.java @@ -0,0 +1,101 @@ +/* + * 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.slider.server.appmaster.web.rest; + +import org.apache.hadoop.yarn.server.webproxy.WebAppProxyServlet; +import org.apache.hadoop.yarn.server.webproxy.amfilter.AmIpFilter; +import org.apache.hadoop.yarn.server.webproxy.amfilter.AmIpPrincipal; +import org.apache.hadoop.yarn.server.webproxy.amfilter.AmIpServletRequestWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * This is a filter which is used to forward insecure operations + * There's some metrics to track all operations too + */ +public class InsecureAmFilter extends AmIpFilter { + public static final String WS_CONTEXT_ROOT = "slider.rest.context.root"; + protected static final Logger log = + LoggerFactory.getLogger(InsecureAmFilter.class); + + private String wsContextRoot; + + + @Override + public void init(FilterConfig conf) throws ServletException { + super.init(conf); + wsContextRoot = conf.getInitParameter(WS_CONTEXT_ROOT); + } + + private void rejectNonHttpRequests(ServletRequest req) throws + ServletException { + if (!(req instanceof HttpServletRequest)) { + throw new ServletException("This filter only works for HTTP/HTTPS"); + } + } + + @Override + public void doFilter(ServletRequest req, + ServletResponse resp, + FilterChain chain) throws IOException, ServletException { + rejectNonHttpRequests(req); + HttpServletRequest httpReq = (HttpServletRequest) req; + HttpServletResponse httpResp = (HttpServletResponse) resp; + + + if (!httpReq.getRequestURI().startsWith(wsContextRoot)) { + // hand off to the AM filter if it is not the context root + super.doFilter(req, resp, chain); + return; + } + + String user = null; + + if (httpReq.getCookies() != null) { + for (Cookie c : httpReq.getCookies()) { + if (WebAppProxyServlet.PROXY_USER_COOKIE_NAME.equals(c.getName())) { + user = c.getValue(); + break; + } + } + } + + if (user == null) { + log.warn("Could not find " + WebAppProxyServlet.PROXY_USER_COOKIE_NAME + + " cookie, so user will not be set"); + chain.doFilter(req, resp); + } else { + final AmIpPrincipal principal = new AmIpPrincipal(user); + ServletRequest requestWrapper = new AmIpServletRequestWrapper(httpReq, + principal); + chain.doFilter(requestWrapper, resp); + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e5fb7f83/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilterInitializer.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilterInitializer.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilterInitializer.java new file mode 100644 index 0000000..111d715 --- /dev/null +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/InsecureAmFilterInitializer.java @@ -0,0 +1,103 @@ +/** + * 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.slider.server.appmaster.web.rest; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.http.FilterContainer; +import org.apache.hadoop.http.FilterInitializer; +import org.apache.hadoop.http.HttpConfig; +import org.apache.hadoop.yarn.api.ApplicationConstants; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.webapp.util.WebAppUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InsecureAmFilterInitializer extends FilterInitializer { + private static final String FILTER_NAME = "AM_PROXY_FILTER"; + private static final String FILTER_CLASS = + InsecureAmFilter.class.getCanonicalName(); + private static final String HTTPS_PREFIX = "https://"; + private static final String HTTP_PREFIX = "http://"; + + static final String PROXY_HOSTS = "PROXY_HOSTS"; + static final String PROXY_HOSTS_DELIMITER = ","; + static final String PROXY_URI_BASES = "PROXY_URI_BASES"; + static final String PROXY_URI_BASES_DELIMITER = ","; + + private Configuration configuration; + + public static final String NAME = + "org.apache.slider.server.appmaster.web.InsecureAmFilterInitializer"; + + @Override + public void initFilter(FilterContainer container, Configuration conf) { + configuration = conf; + Map<String, String> params = new HashMap<String, String>(); + String proxy = WebAppUtils.getProxyHostAndPort(conf); + String[] parts = proxy.split(":"); + params.put(InsecureAmFilter.PROXY_HOST, parts[0]); + // todo: eventually call WebAppUtils.getHttpSchemePrefix + params.put(InsecureAmFilter.PROXY_URI_BASE, getHttpSchemePrefix() + + proxy + + getApplicationWebProxyBase()); + params.put(InsecureAmFilter.WS_CONTEXT_ROOT, + conf.get(InsecureAmFilter.WS_CONTEXT_ROOT)); + container.addFilter(FILTER_NAME, FILTER_CLASS, params); + } + + private void classicAmFilterInitializerInit(FilterContainer container, + Configuration conf) { + Map<String, String> params = new HashMap<String, String>(); + List<String> proxies = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf); + StringBuilder sb = new StringBuilder(); + for (String proxy : proxies) { + sb.append(proxy.split(":")[0]).append(PROXY_HOSTS_DELIMITER); + } + sb.setLength(sb.length() - 1); + params.put(PROXY_HOSTS, sb.toString()); + + String prefix = WebAppUtils.getHttpSchemePrefix(conf); + String proxyBase = getApplicationWebProxyBase(); + sb = new StringBuilder(); + for (String proxy : proxies) { + sb.append(prefix).append(proxy).append(proxyBase) + .append(PROXY_HOSTS_DELIMITER); + } + sb.setLength(sb.length() - 1); + params.put(PROXY_URI_BASES, sb.toString()); + + } + + @VisibleForTesting + protected String getApplicationWebProxyBase() { + return System.getenv(ApplicationConstants.APPLICATION_WEB_PROXY_BASE_ENV); + } + + private String getHttpSchemePrefix() { + return HttpConfig.Policy.HTTPS_ONLY == + HttpConfig.Policy.fromString(configuration + .get( + YarnConfiguration.YARN_HTTP_POLICY_KEY, + YarnConfiguration.YARN_HTTP_POLICY_DEFAULT)) + ? HTTPS_PREFIX : HTTP_PREFIX; + } +}
