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.timeline.security;
20 
21 import static org.junit.Assert.assertTrue;
22 
23 import java.io.File;
24 import java.security.PrivilegedExceptionAction;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.concurrent.Callable;
28 
29 import org.apache.hadoop.conf.Configuration;
30 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
31 import org.apache.hadoop.fs.FileUtil;
32 import org.apache.hadoop.http.HttpConfig;
33 import org.apache.hadoop.io.Text;
34 import org.apache.hadoop.minikdc.MiniKdc;
35 import org.apache.hadoop.security.UserGroupInformation;
36 import org.apache.hadoop.security.authentication.KerberosTestUtils;
37 import org.apache.hadoop.security.authentication.client.AuthenticationException;
38 import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
39 import org.apache.hadoop.security.authorize.AuthorizationException;
40 import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
41 import org.apache.hadoop.security.token.Token;
42 import org.apache.hadoop.yarn.api.records.timeline.TimelineDomain;
43 import org.apache.hadoop.yarn.api.records.timeline.TimelineEntity;
44 import org.apache.hadoop.yarn.api.records.timeline.TimelinePutResponse;
45 import org.apache.hadoop.yarn.client.api.TimelineClient;
46 import org.apache.hadoop.yarn.conf.YarnConfiguration;
47 import org.apache.hadoop.yarn.security.client.TimelineDelegationTokenIdentifier;
48 import org.apache.hadoop.yarn.server.applicationhistoryservice.ApplicationHistoryServer;
49 import org.apache.hadoop.yarn.server.timeline.MemoryTimelineStore;
50 import org.apache.hadoop.yarn.server.timeline.TimelineStore;
51 import org.junit.AfterClass;
52 import org.junit.Assert;
53 import org.junit.BeforeClass;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.junit.runners.Parameterized;
57 
58 @RunWith(Parameterized.class)
59 public class TestTimelineAuthenticationFilter {
60 
61   private static final String FOO_USER = "foo";
62   private static final String BAR_USER = "bar";
63   private static final String HTTP_USER = "HTTP";
64 
65   private static final File testRootDir = new File(
66       System.getProperty("test.build.dir", "target/test-dir"),
67       TestTimelineAuthenticationFilter.class.getName() + "-root");
68   private static File httpSpnegoKeytabFile = new File(
69       KerberosTestUtils.getKeytabFile());
70   private static String httpSpnegoPrincipal =
71       KerberosTestUtils.getServerPrincipal();
72   private static final String BASEDIR =
73       System.getProperty("test.build.dir", "target/test-dir") + "/"
74           + TestTimelineAuthenticationFilter.class.getSimpleName();
75 
76   @Parameterized.Parameters
withSsl()77   public static Collection<Object[]> withSsl() {
78     return Arrays.asList(new Object[][] { { false }, { true } });
79   }
80 
81   private static MiniKdc testMiniKDC;
82   private static String keystoresDir;
83   private static String sslConfDir;
84   private static ApplicationHistoryServer testTimelineServer;
85   private static Configuration conf;
86   private static boolean withSsl;
87 
TestTimelineAuthenticationFilter(boolean withSsl)88   public TestTimelineAuthenticationFilter(boolean withSsl) {
89     TestTimelineAuthenticationFilter.withSsl = withSsl;
90   }
91 
92   @BeforeClass
setup()93   public static void setup() {
94     try {
95       testMiniKDC = new MiniKdc(MiniKdc.createConf(), testRootDir);
96       testMiniKDC.start();
97       testMiniKDC.createPrincipal(
98           httpSpnegoKeytabFile, HTTP_USER + "/localhost");
99     } catch (Exception e) {
100       assertTrue("Couldn't setup MiniKDC", false);
101     }
102 
103     try {
104       testTimelineServer = new ApplicationHistoryServer();
105       conf = new Configuration(false);
106       conf.setStrings(TimelineAuthenticationFilterInitializer.PREFIX + "type",
107           "kerberos");
108       conf.set(TimelineAuthenticationFilterInitializer.PREFIX +
109           KerberosAuthenticationHandler.PRINCIPAL, httpSpnegoPrincipal);
110       conf.set(TimelineAuthenticationFilterInitializer.PREFIX +
111           KerberosAuthenticationHandler.KEYTAB,
112           httpSpnegoKeytabFile.getAbsolutePath());
113       conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION,
114         "kerberos");
115       conf.set(YarnConfiguration.TIMELINE_SERVICE_PRINCIPAL,
116         httpSpnegoPrincipal);
117       conf.set(YarnConfiguration.TIMELINE_SERVICE_KEYTAB,
118         httpSpnegoKeytabFile.getAbsolutePath());
119       conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED, true);
120       conf.setClass(YarnConfiguration.TIMELINE_SERVICE_STORE,
121           MemoryTimelineStore.class, TimelineStore.class);
122       conf.set(YarnConfiguration.TIMELINE_SERVICE_ADDRESS,
123           "localhost:10200");
124       conf.set(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
125           "localhost:8188");
126       conf.set(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_HTTPS_ADDRESS,
127           "localhost:8190");
128       conf.set("hadoop.proxyuser.HTTP.hosts", "*");
129       conf.set("hadoop.proxyuser.HTTP.users", FOO_USER);
130       conf.setInt(YarnConfiguration.TIMELINE_SERVICE_CLIENT_MAX_RETRIES, 1);
131 
132       if (withSsl) {
133         conf.set(YarnConfiguration.YARN_HTTP_POLICY_KEY,
134             HttpConfig.Policy.HTTPS_ONLY.name());
135         File base = new File(BASEDIR);
136         FileUtil.fullyDelete(base);
137         base.mkdirs();
138         keystoresDir = new File(BASEDIR).getAbsolutePath();
139         sslConfDir =
140             KeyStoreTestUtil.getClasspathDir(TestTimelineAuthenticationFilter.class);
141         KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false);
142       }
143 
144       UserGroupInformation.setConfiguration(conf);
145       testTimelineServer.init(conf);
146       testTimelineServer.start();
147     } catch (Exception e) {
148       assertTrue("Couldn't setup TimelineServer", false);
149     }
150   }
151 
createTimelineClientForUGI()152   private TimelineClient createTimelineClientForUGI() {
153     TimelineClient client = TimelineClient.createTimelineClient();
154     client.init(conf);
155     client.start();
156     return client;
157   }
158 
159   @AfterClass
tearDown()160   public static void tearDown() throws Exception {
161     if (testMiniKDC != null) {
162       testMiniKDC.stop();
163     }
164 
165     if (testTimelineServer != null) {
166       testTimelineServer.stop();
167     }
168 
169     if (withSsl) {
170       KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfDir);
171       File base = new File(BASEDIR);
172       FileUtil.fullyDelete(base);
173     }
174   }
175 
176   @Test
testPutTimelineEntities()177   public void testPutTimelineEntities() throws Exception {
178     KerberosTestUtils.doAs(HTTP_USER + "/localhost", new Callable<Void>() {
179       @Override
180       public Void call() throws Exception {
181         TimelineClient client = createTimelineClientForUGI();
182         TimelineEntity entityToStore = new TimelineEntity();
183         entityToStore.setEntityType(
184             TestTimelineAuthenticationFilter.class.getName());
185         entityToStore.setEntityId("entity1");
186         entityToStore.setStartTime(0L);
187         TimelinePutResponse putResponse = client.putEntities(entityToStore);
188         Assert.assertEquals(0, putResponse.getErrors().size());
189         TimelineEntity entityToRead =
190             testTimelineServer.getTimelineStore().getEntity(
191                 "entity1", TestTimelineAuthenticationFilter.class.getName(), null);
192         Assert.assertNotNull(entityToRead);
193         return null;
194       }
195     });
196   }
197 
198   @Test
testPutDomains()199   public void testPutDomains() throws Exception {
200     KerberosTestUtils.doAs(HTTP_USER + "/localhost", new Callable<Void>() {
201       @Override
202       public Void call() throws Exception {
203         TimelineClient client = createTimelineClientForUGI();
204         TimelineDomain domainToStore = new TimelineDomain();
205         domainToStore.setId(TestTimelineAuthenticationFilter.class.getName());
206         domainToStore.setReaders("*");
207         domainToStore.setWriters("*");
208         client.putDomain(domainToStore);
209         TimelineDomain domainToRead =
210             testTimelineServer.getTimelineStore().getDomain(
211                 TestTimelineAuthenticationFilter.class.getName());
212         Assert.assertNotNull(domainToRead);
213         return null;
214       }
215     });
216   }
217 
218   @Test
testDelegationTokenOperations()219   public void testDelegationTokenOperations() throws Exception {
220     TimelineClient httpUserClient =
221       KerberosTestUtils.doAs(HTTP_USER + "/localhost", new Callable<TimelineClient>() {
222         @Override
223         public TimelineClient call() throws Exception {
224           return createTimelineClientForUGI();
225         }
226       });
227     UserGroupInformation httpUser =
228       KerberosTestUtils.doAs(HTTP_USER + "/localhost", new Callable<UserGroupInformation>() {
229         @Override
230         public UserGroupInformation call() throws Exception {
231           return UserGroupInformation.getCurrentUser();
232         }
233       });
234     // Let HTTP user to get the delegation for itself
235     Token<TimelineDelegationTokenIdentifier> token =
236       httpUserClient.getDelegationToken(httpUser.getShortUserName());
237     Assert.assertNotNull(token);
238     TimelineDelegationTokenIdentifier tDT = token.decodeIdentifier();
239     Assert.assertNotNull(tDT);
240     Assert.assertEquals(new Text(HTTP_USER), tDT.getOwner());
241 
242     // Renew token
243     Assert.assertFalse(token.getService().toString().isEmpty());
244     // Renew the token from the token service address
245     long renewTime1 = httpUserClient.renewDelegationToken(token);
246     Thread.sleep(100);
247     token.setService(new Text());
248     Assert.assertTrue(token.getService().toString().isEmpty());
249     // If the token service address is not avaiable, it still can be renewed
250     // from the configured address
251     long renewTime2 = httpUserClient.renewDelegationToken(token);
252     Assert.assertTrue(renewTime1 < renewTime2);
253 
254     // Cancel token
255     Assert.assertTrue(token.getService().toString().isEmpty());
256     // If the token service address is not avaiable, it still can be canceled
257     // from the configured address
258     httpUserClient.cancelDelegationToken(token);
259     // Renew should not be successful because the token is canceled
260     try {
261       httpUserClient.renewDelegationToken(token);
262       Assert.fail();
263     } catch (Exception e) {
264       Assert.assertTrue(e.getMessage().contains(
265             "Renewal request for unknown token"));
266     }
267 
268     // Let HTTP user to get the delegation token for FOO user
269     UserGroupInformation fooUgi = UserGroupInformation.createProxyUser(
270         FOO_USER, httpUser);
271     TimelineClient fooUserClient = fooUgi.doAs(
272         new PrivilegedExceptionAction<TimelineClient>() {
273           @Override
274           public TimelineClient run() throws Exception {
275             return createTimelineClientForUGI();
276           }
277         });
278     token = fooUserClient.getDelegationToken(httpUser.getShortUserName());
279     Assert.assertNotNull(token);
280     tDT = token.decodeIdentifier();
281     Assert.assertNotNull(tDT);
282     Assert.assertEquals(new Text(FOO_USER), tDT.getOwner());
283     Assert.assertEquals(new Text(HTTP_USER), tDT.getRealUser());
284 
285     // Renew token as the renewer
286     final Token<TimelineDelegationTokenIdentifier> tokenToRenew = token;
287     renewTime1 = httpUserClient.renewDelegationToken(tokenToRenew);
288     renewTime2 = httpUserClient.renewDelegationToken(tokenToRenew);
289     Assert.assertTrue(renewTime1 < renewTime2);
290 
291     // Cancel token
292     Assert.assertFalse(tokenToRenew.getService().toString().isEmpty());
293     // Cancel the token from the token service address
294     fooUserClient.cancelDelegationToken(tokenToRenew);
295 
296     // Renew should not be successful because the token is canceled
297     try {
298       httpUserClient.renewDelegationToken(tokenToRenew);
299       Assert.fail();
300     } catch (Exception e) {
301       Assert.assertTrue(
302           e.getMessage().contains("Renewal request for unknown token"));
303     }
304 
305     // Let HTTP user to get the delegation token for BAR user
306     UserGroupInformation barUgi = UserGroupInformation.createProxyUser(
307         BAR_USER, httpUser);
308     TimelineClient barUserClient = barUgi.doAs(
309         new PrivilegedExceptionAction<TimelineClient>() {
310           @Override
311           public TimelineClient run() {
312             return createTimelineClientForUGI();
313           }
314         });
315 
316     try {
317       barUserClient.getDelegationToken(httpUser.getShortUserName());
318       Assert.fail();
319     } catch (Exception e) {
320       Assert.assertTrue(e.getCause() instanceof AuthorizationException || e.getCause() instanceof AuthenticationException);
321     }
322   }
323 }
324