diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt index b5aec42187..287058373d 100644 --- a/hadoop-yarn-project/CHANGES.txt +++ b/hadoop-yarn-project/CHANGES.txt @@ -862,6 +862,9 @@ Release 2.6.0 - UNRELEASED YARN-2804. Fixed Timeline service to not fill the logs with JAXB bindings exceptions. (Zhijie Shen via vinodkv) + YARN-2767. Added a test case to verify that http static user cannot kill or submit + apps in the secure mode. (Varun Vasudev via zjshen) + Release 2.5.2 - UNRELEASED INCOMPATIBLE CHANGES diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesHttpStaticUserPermissions.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesHttpStaticUserPermissions.java new file mode 100644 index 0000000000..3d47233778 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesHttpStaticUserPermissions.java @@ -0,0 +1,195 @@ +/** + * 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.webapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; +import java.util.HashMap; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +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.resourcemanager.MockRM; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ApplicationSubmissionContextInfo; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.sun.jersey.api.client.ClientResponse.Status; + +public class TestRMWebServicesHttpStaticUserPermissions { + + private static final File testRootDir = new File("target", + TestRMWebServicesHttpStaticUserPermissions.class.getName() + "-root"); + private static File spnegoKeytabFile = new File( + KerberosTestUtils.getKeytabFile()); + + private static String spnegoPrincipal = KerberosTestUtils + .getServerPrincipal(); + + private static MiniKdc testMiniKDC; + private static MockRM rm; + + static class Helper { + String method; + String requestBody; + + Helper(String method, String requestBody) { + this.method = method; + this.requestBody = requestBody; + } + } + + @BeforeClass + public static void setUp() { + try { + testMiniKDC = new MiniKdc(MiniKdc.createConf(), testRootDir); + setupKDC(); + setupAndStartRM(); + } catch (Exception e) { + fail("Couldn't create MiniKDC"); + } + } + + @AfterClass + public static void tearDown() { + if (testMiniKDC != null) { + testMiniKDC.stop(); + } + if (rm != null) { + rm.stop(); + } + } + + public TestRMWebServicesHttpStaticUserPermissions() throws Exception { + super(); + } + + private static void setupAndStartRM() throws Exception { + Configuration rmconf = new Configuration(); + rmconf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, + YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS); + rmconf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class, + ResourceScheduler.class); + rmconf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); + rmconf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, + "kerberos"); + rmconf.set("yarn.resourcemanager.principal", spnegoPrincipal); + rmconf.set("yarn.resourcemanager.keytab", + spnegoKeytabFile.getAbsolutePath()); + rmconf.setBoolean("mockrm.webapp.enabled", true); + UserGroupInformation.setConfiguration(rmconf); + rm = new MockRM(rmconf); + rm.start(); + + } + + private static void setupKDC() throws Exception { + testMiniKDC.start(); + testMiniKDC.createPrincipal(spnegoKeytabFile, "HTTP/localhost", "client", + UserGroupInformation.getLoginUser().getShortUserName(), "client2"); + } + + // Test that the http static user can't submit or kill apps + // when secure mode is turned on + + @Test + public void testWebServiceAccess() throws Exception { + + ApplicationSubmissionContextInfo app = + new ApplicationSubmissionContextInfo(); + String appid = "application_123_0"; + app.setApplicationId(appid); + String submitAppRequestBody = + TestRMWebServicesDelegationTokenAuthentication + .getMarshalledAppInfo(app); + + URL url = new URL("http://localhost:8088/ws/v1/cluster/apps"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + // we should be access the apps page with the static user + TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "GET", "", + ""); + try { + conn.getInputStream(); + assertEquals(Status.OK.getStatusCode(), conn.getResponseCode()); + } catch (IOException e) { + fail("Got " + conn.getResponseCode() + " instead of 200 accessing " + + url.toString()); + } + conn.disconnect(); + + // new-application, submit app and kill should fail with + // forbidden + Map urlRequestMap = new HashMap(); + String killAppRequestBody = + "\n" + + "\n" + " KILLED\n" + ""; + + urlRequestMap.put("http://localhost:8088/ws/v1/cluster/apps", new Helper( + "POST", submitAppRequestBody)); + urlRequestMap.put( + "http://localhost:8088/ws/v1/cluster/apps/new-application", new Helper( + "POST", "")); + urlRequestMap.put( + "http://localhost:8088/ws/v1/cluster/apps/app_123_1/state", new Helper( + "PUT", killAppRequestBody)); + + for (Map.Entry entry : urlRequestMap.entrySet()) { + URL reqURL = new URL(entry.getKey()); + conn = (HttpURLConnection) reqURL.openConnection(); + String method = entry.getValue().method; + String body = entry.getValue().requestBody; + TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, method, + "application/xml", body); + try { + conn.getInputStream(); + fail("Request " + entry.getKey() + "succeeded but should have failed"); + } catch (IOException e) { + assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode()); + InputStream errorStream = conn.getErrorStream(); + String error = ""; + BufferedReader reader = + new BufferedReader(new InputStreamReader(errorStream, "UTF8")); + for (String line; (line = reader.readLine()) != null;) { + error += line; + } + reader.close(); + errorStream.close(); + assertEquals( + "The default static user cannot carry out this operation.", error); + } + conn.disconnect(); + } + } +}