1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.yarn.server.resourcemanager.webapp;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.net.HttpURLConnection;
28 import java.net.URL;
29 import java.util.Arrays;
30 import java.util.Collection;
31 
32 import javax.ws.rs.core.MediaType;
33 
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.CommonConfigurationKeys;
36 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
37 import org.apache.hadoop.minikdc.MiniKdc;
38 import org.apache.hadoop.security.UserGroupInformation;
39 import org.apache.hadoop.security.authentication.KerberosTestUtils;
40 import org.apache.hadoop.yarn.conf.YarnConfiguration;
41 import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
42 import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp;
43 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
44 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler;
45 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ApplicationSubmissionContextInfo;
46 import org.apache.hadoop.yarn.util.ConverterUtils;
47 import org.junit.AfterClass;
48 import org.junit.BeforeClass;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.junit.runners.Parameterized;
52 import org.junit.runners.Parameterized.Parameters;
53 
54 import com.sun.jersey.api.client.ClientResponse.Status;
55 
56 /* Just a simple test class to ensure that the RM handles the static web user
57  * correctly for secure and un-secure modes
58  *
59  */
60 @RunWith(Parameterized.class)
61 public class TestRMWebappAuthentication {
62 
63   private static MockRM rm;
64   private static Configuration simpleConf;
65   private static Configuration kerberosConf;
66 
67   private static final File testRootDir = new File("target",
68     TestRMWebServicesDelegationTokenAuthentication.class.getName() + "-root");
69   private static File httpSpnegoKeytabFile = new File(
70     KerberosTestUtils.getKeytabFile());
71 
72   private static boolean miniKDCStarted = false;
73   private static MiniKdc testMiniKDC;
74 
75   static {
76     simpleConf = new Configuration();
simpleConf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS)77     simpleConf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS,
78       YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS);
simpleConf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class, ResourceScheduler.class)79     simpleConf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class,
80       ResourceScheduler.class);
81     simpleConf.setBoolean("mockrm.webapp.enabled", true);
82     kerberosConf = new Configuration();
kerberosConf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS)83     kerberosConf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS,
84       YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS);
kerberosConf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class, ResourceScheduler.class)85     kerberosConf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class,
86       ResourceScheduler.class);
kerberosConf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true)87     kerberosConf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true);
kerberosConf.set( CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, R)88     kerberosConf.set(
89       CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
kerberosConf.set(YarnConfiguration.RM_KEYTAB, httpSpnegoKeytabFile.getAbsolutePath())90     kerberosConf.set(YarnConfiguration.RM_KEYTAB,
91       httpSpnegoKeytabFile.getAbsolutePath());
92     kerberosConf.setBoolean("mockrm.webapp.enabled", true);
93   }
94 
95   @Parameters
params()96   public static Collection params() {
97     return Arrays.asList(new Object[][] { { 1, simpleConf },
98         { 2, kerberosConf } });
99   }
100 
TestRMWebappAuthentication(int run, Configuration conf)101   public TestRMWebappAuthentication(int run, Configuration conf) {
102     super();
103     setupAndStartRM(conf);
104   }
105 
106   @BeforeClass
setUp()107   public static void setUp() {
108     try {
109       testMiniKDC = new MiniKdc(MiniKdc.createConf(), testRootDir);
110       setupKDC();
111     } catch (Exception e) {
112       assertTrue("Couldn't create MiniKDC", false);
113     }
114   }
115 
116   @AfterClass
tearDown()117   public static void tearDown() {
118     if (testMiniKDC != null) {
119       testMiniKDC.stop();
120     }
121   }
122 
setupKDC()123   private static void setupKDC() throws Exception {
124     if (!miniKDCStarted) {
125       testMiniKDC.start();
126       getKdc().createPrincipal(httpSpnegoKeytabFile, "HTTP/localhost",
127         "client", UserGroupInformation.getLoginUser().getShortUserName());
128       miniKDCStarted = true;
129     }
130   }
131 
getKdc()132   private static MiniKdc getKdc() {
133     return testMiniKDC;
134   }
135 
setupAndStartRM(Configuration conf)136   private static void setupAndStartRM(Configuration conf) {
137     UserGroupInformation.setConfiguration(conf);
138     rm = new MockRM(conf);
139   }
140 
141   // ensure that in a non-secure cluster users can access
142   // the web pages as earlier and submit apps as anonymous
143   // user or by identifying themselves
144   @Test
testSimpleAuth()145   public void testSimpleAuth() throws Exception {
146 
147     rm.start();
148 
149     // ensure users can access web pages
150     // this should work for secure and non-secure clusters
151     URL url = new URL("http://localhost:8088/cluster");
152     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
153     try {
154       conn.getInputStream();
155       assertEquals(Status.OK.getStatusCode(), conn.getResponseCode());
156     } catch (Exception e) {
157       fail("Fetching url failed");
158     }
159 
160     if (UserGroupInformation.isSecurityEnabled()) {
161       testAnonymousKerberosUser();
162     } else {
163       testAnonymousSimpleUser();
164     }
165 
166     rm.stop();
167   }
168 
testAnonymousKerberosUser()169   private void testAnonymousKerberosUser() throws Exception {
170 
171     ApplicationSubmissionContextInfo app =
172         new ApplicationSubmissionContextInfo();
173     String appid = "application_123_0";
174     app.setApplicationId(appid);
175     String requestBody =
176         TestRMWebServicesDelegationTokenAuthentication
177           .getMarshalledAppInfo(app);
178 
179     URL url =
180         new URL("http://localhost:8088/ws/v1/cluster/apps/new-application");
181     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
182     TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "POST",
183       "application/xml", requestBody);
184 
185     try {
186       conn.getInputStream();
187       fail("Anonymous users should not be allowed to get new application ids in secure mode.");
188     } catch (IOException ie) {
189       assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
190     }
191 
192     url = new URL("http://localhost:8088/ws/v1/cluster/apps");
193     conn = (HttpURLConnection) url.openConnection();
194     TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "POST",
195       "application/xml", requestBody);
196 
197     try {
198       conn.getInputStream();
199       fail("Anonymous users should not be allowed to submit apps in secure mode.");
200     } catch (IOException ie) {
201       assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
202     }
203 
204     requestBody = "{ \"state\": \"KILLED\"}";
205     url =
206         new URL(
207           "http://localhost:8088/ws/v1/cluster/apps/application_123_0/state");
208     conn = (HttpURLConnection) url.openConnection();
209     TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "PUT",
210       "application/json", requestBody);
211 
212     try {
213       conn.getInputStream();
214       fail("Anonymous users should not be allowed to kill apps in secure mode.");
215     } catch (IOException ie) {
216       assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
217     }
218   }
219 
testAnonymousSimpleUser()220   private void testAnonymousSimpleUser() throws Exception {
221 
222     ApplicationSubmissionContextInfo app =
223         new ApplicationSubmissionContextInfo();
224     String appid = "application_123_0";
225     app.setApplicationId(appid);
226     String requestBody =
227         TestRMWebServicesDelegationTokenAuthentication
228           .getMarshalledAppInfo(app);
229 
230     URL url = new URL("http://localhost:8088/ws/v1/cluster/apps");
231     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
232     TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "POST",
233       "application/xml", requestBody);
234 
235     conn.getInputStream();
236     assertEquals(Status.ACCEPTED.getStatusCode(), conn.getResponseCode());
237     boolean appExists =
238         rm.getRMContext().getRMApps()
239           .containsKey(ConverterUtils.toApplicationId(appid));
240     assertTrue(appExists);
241     RMApp actualApp =
242         rm.getRMContext().getRMApps()
243           .get(ConverterUtils.toApplicationId(appid));
244     String owner = actualApp.getUser();
245     assertEquals(
246       rm.getConfig().get(CommonConfigurationKeys.HADOOP_HTTP_STATIC_USER,
247         CommonConfigurationKeys.DEFAULT_HADOOP_HTTP_STATIC_USER), owner);
248 
249     appid = "application_123_1";
250     app.setApplicationId(appid);
251     requestBody =
252         TestRMWebServicesDelegationTokenAuthentication
253           .getMarshalledAppInfo(app);
254     url = new URL("http://localhost:8088/ws/v1/cluster/apps?user.name=client");
255     conn = (HttpURLConnection) url.openConnection();
256     TestRMWebServicesDelegationTokenAuthentication.setupConn(conn, "POST",
257       MediaType.APPLICATION_XML, requestBody);
258 
259     conn.getInputStream();
260     appExists =
261         rm.getRMContext().getRMApps()
262           .containsKey(ConverterUtils.toApplicationId(appid));
263     assertTrue(appExists);
264     actualApp =
265         rm.getRMContext().getRMApps()
266           .get(ConverterUtils.toApplicationId(appid));
267     owner = actualApp.getUser();
268     assertEquals("client", owner);
269 
270   }
271 
272 }
273