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.hdfs.security;
20 
21 
22 
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertTrue;
25 
26 import java.io.ByteArrayInputStream;
27 import java.io.DataInputStream;
28 import java.io.IOException;
29 import java.net.URI;
30 import java.security.PrivilegedExceptionAction;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.commons.logging.impl.Log4JLogger;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.hdfs.DFSConfigKeys;
38 import org.apache.hadoop.hdfs.DFSTestUtil;
39 import org.apache.hadoop.hdfs.DistributedFileSystem;
40 import org.apache.hadoop.hdfs.HdfsConfiguration;
41 import org.apache.hadoop.hdfs.MiniDFSCluster;
42 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
43 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager;
44 import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
45 import org.apache.hadoop.hdfs.server.namenode.NameNode;
46 import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter;
47 import org.apache.hadoop.hdfs.server.namenode.web.resources.NamenodeWebHdfsMethods;
48 import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
49 import org.apache.hadoop.io.Text;
50 import org.apache.hadoop.security.AccessControlException;
51 import org.apache.hadoop.security.Credentials;
52 import org.apache.hadoop.security.UserGroupInformation;
53 import org.apache.hadoop.security.token.SecretManager.InvalidToken;
54 import org.apache.hadoop.security.token.Token;
55 import org.apache.log4j.Level;
56 import org.junit.After;
57 import org.junit.Assert;
58 import org.junit.Before;
59 import org.junit.Test;
60 
61 public class TestDelegationToken {
62   private MiniDFSCluster cluster;
63   private DelegationTokenSecretManager dtSecretManager;
64   private Configuration config;
65   private static final Log LOG = LogFactory.getLog(TestDelegationToken.class);
66 
67   @Before
setUp()68   public void setUp() throws Exception {
69     config = new HdfsConfiguration();
70     config.setBoolean(DFSConfigKeys.DFS_WEBHDFS_ENABLED_KEY, true);
71     config.setLong(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_MAX_LIFETIME_KEY, 10000);
72     config.setLong(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_RENEW_INTERVAL_KEY, 5000);
73     config.setBoolean(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_ALWAYS_USE_KEY, true);
74     config.set("hadoop.security.auth_to_local",
75         "RULE:[2:$1@$0](JobTracker@.*FOO.COM)s/@.*//" + "DEFAULT");
76     FileSystem.setDefaultUri(config, "hdfs://localhost:" + "0");
77     cluster = new MiniDFSCluster.Builder(config).numDataNodes(0).build();
78     cluster.waitActive();
79     dtSecretManager = NameNodeAdapter.getDtSecretManager(
80         cluster.getNamesystem());
81   }
82 
83   @After
tearDown()84   public void tearDown() throws Exception {
85     if(cluster!=null) {
86       cluster.shutdown();
87     }
88   }
89 
generateDelegationToken( String owner, String renewer)90   private Token<DelegationTokenIdentifier> generateDelegationToken(
91       String owner, String renewer) {
92     DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(new Text(
93         owner), new Text(renewer), null);
94     return new Token<DelegationTokenIdentifier>(dtId, dtSecretManager);
95   }
96 
97   @Test
testDelegationTokenSecretManager()98   public void testDelegationTokenSecretManager() throws Exception {
99     Token<DelegationTokenIdentifier> token = generateDelegationToken(
100         "SomeUser", "JobTracker");
101     // Fake renewer should not be able to renew
102     try {
103   	  dtSecretManager.renewToken(token, "FakeRenewer");
104   	  Assert.fail("should have failed");
105     } catch (AccessControlException ace) {
106       // PASS
107     }
108 	  dtSecretManager.renewToken(token, "JobTracker");
109     DelegationTokenIdentifier identifier = new DelegationTokenIdentifier();
110     byte[] tokenId = token.getIdentifier();
111     identifier.readFields(new DataInputStream(
112              new ByteArrayInputStream(tokenId)));
113     Assert.assertTrue(null != dtSecretManager.retrievePassword(identifier));
114     LOG.info("Sleep to expire the token");
115 	  Thread.sleep(6000);
116 	  //Token should be expired
117 	  try {
118 	    dtSecretManager.retrievePassword(identifier);
119 	    //Should not come here
120 	    Assert.fail("Token should have expired");
121 	  } catch (InvalidToken e) {
122 	    //Success
123 	  }
124 	  dtSecretManager.renewToken(token, "JobTracker");
125 	  LOG.info("Sleep beyond the max lifetime");
126 	  Thread.sleep(5000);
127 	  try {
128   	  dtSecretManager.renewToken(token, "JobTracker");
129   	  Assert.fail("should have been expired");
130 	  } catch (InvalidToken it) {
131 	    // PASS
132 	  }
133   }
134 
135   @Test
testCancelDelegationToken()136   public void testCancelDelegationToken() throws Exception {
137     Token<DelegationTokenIdentifier> token = generateDelegationToken(
138         "SomeUser", "JobTracker");
139     //Fake renewer should not be able to renew
140     try {
141       dtSecretManager.cancelToken(token, "FakeCanceller");
142       Assert.fail("should have failed");
143     } catch (AccessControlException ace) {
144       // PASS
145     }
146     dtSecretManager.cancelToken(token, "JobTracker");
147     try {
148       dtSecretManager.renewToken(token, "JobTracker");
149       Assert.fail("should have failed");
150     } catch (InvalidToken it) {
151       // PASS
152     }
153   }
154 
155   @Test
testAddDelegationTokensDFSApi()156   public void testAddDelegationTokensDFSApi() throws Exception {
157     UserGroupInformation ugi = UserGroupInformation.createRemoteUser("JobTracker");
158     DistributedFileSystem dfs = cluster.getFileSystem();
159     Credentials creds = new Credentials();
160     final Token<?> tokens[] = dfs.addDelegationTokens("JobTracker", creds);
161     Assert.assertEquals(1, tokens.length);
162     Assert.assertEquals(1, creds.numberOfTokens());
163     checkTokenIdentifier(ugi, tokens[0]);
164 
165     final Token<?> tokens2[] = dfs.addDelegationTokens("JobTracker", creds);
166     Assert.assertEquals(0, tokens2.length); // already have token
167     Assert.assertEquals(1, creds.numberOfTokens());
168   }
169 
170   @SuppressWarnings("deprecation")
171   @Test
testDelegationTokenWebHdfsApi()172   public void testDelegationTokenWebHdfsApi() throws Exception {
173     ((Log4JLogger)NamenodeWebHdfsMethods.LOG).getLogger().setLevel(Level.ALL);
174     final String uri = WebHdfsFileSystem.SCHEME  + "://"
175         + config.get(DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY);
176     //get file system as JobTracker
177     final UserGroupInformation ugi = UserGroupInformation.createUserForTesting(
178         "JobTracker", new String[]{"user"});
179     final WebHdfsFileSystem webhdfs = ugi.doAs(
180         new PrivilegedExceptionAction<WebHdfsFileSystem>() {
181       @Override
182       public WebHdfsFileSystem run() throws Exception {
183         return (WebHdfsFileSystem)FileSystem.get(new URI(uri), config);
184       }
185     });
186 
187     { //test addDelegationTokens(..)
188       Credentials creds = new Credentials();
189       final Token<?> tokens[] = webhdfs.addDelegationTokens("JobTracker", creds);
190       Assert.assertEquals(1, tokens.length);
191       Assert.assertEquals(1, creds.numberOfTokens());
192       Assert.assertSame(tokens[0], creds.getAllTokens().iterator().next());
193       checkTokenIdentifier(ugi, tokens[0]);
194       final Token<?> tokens2[] = webhdfs.addDelegationTokens("JobTracker", creds);
195       Assert.assertEquals(0, tokens2.length);
196     }
197   }
198 
199   @Test
testDelegationTokenWithDoAs()200   public void testDelegationTokenWithDoAs() throws Exception {
201     final DistributedFileSystem dfs = cluster.getFileSystem();
202     final Credentials creds = new Credentials();
203     final Token<?> tokens[] = dfs.addDelegationTokens("JobTracker", creds);
204     Assert.assertEquals(1, tokens.length);
205     @SuppressWarnings("unchecked")
206     final Token<DelegationTokenIdentifier> token =
207         (Token<DelegationTokenIdentifier>) tokens[0];
208     final UserGroupInformation longUgi = UserGroupInformation
209         .createRemoteUser("JobTracker/foo.com@FOO.COM");
210     final UserGroupInformation shortUgi = UserGroupInformation
211         .createRemoteUser("JobTracker");
212     longUgi.doAs(new PrivilegedExceptionAction<Object>() {
213       @Override
214       public Object run() throws IOException {
215         try {
216           token.renew(config);
217         } catch (Exception e) {
218           Assert.fail("Could not renew delegation token for user "+longUgi);
219         }
220         return null;
221       }
222     });
223     shortUgi.doAs(new PrivilegedExceptionAction<Object>() {
224       @Override
225       public Object run() throws Exception {
226         token.renew(config);
227         return null;
228       }
229     });
230     longUgi.doAs(new PrivilegedExceptionAction<Object>() {
231       @Override
232       public Object run() throws IOException {
233         try {
234           token.cancel(config);
235         } catch (Exception e) {
236           Assert.fail("Could not cancel delegation token for user "+longUgi);
237         }
238         return null;
239       }
240     });
241   }
242 
243   /**
244    * Test that the delegation token secret manager only runs when the
245    * NN is out of safe mode. This is because the secret manager
246    * has to log to the edit log, which should not be written in
247    * safe mode. Regression test for HDFS-2579.
248    */
249   @Test
testDTManagerInSafeMode()250   public void testDTManagerInSafeMode() throws Exception {
251     cluster.startDataNodes(config, 1, true, StartupOption.REGULAR, null);
252     FileSystem fs = cluster.getFileSystem();
253     for (int i = 0; i < 5; i++) {
254       DFSTestUtil.createFile(fs, new Path("/test-" + i), 100, (short)1, 1L);
255     }
256     cluster.getConfiguration(0).setInt(
257         DFSConfigKeys.DFS_NAMENODE_DELEGATION_KEY_UPDATE_INTERVAL_KEY, 500);
258     cluster.getConfiguration(0).setInt(
259         DFSConfigKeys.DFS_NAMENODE_SAFEMODE_EXTENSION_KEY, 30000);
260     cluster.setWaitSafeMode(false);
261     cluster.restartNameNode();
262     NameNode nn = cluster.getNameNode();
263     assertTrue(nn.isInSafeMode());
264     DelegationTokenSecretManager sm =
265       NameNodeAdapter.getDtSecretManager(nn.getNamesystem());
266     assertFalse("Secret manager should not run in safe mode", sm.isRunning());
267 
268     NameNodeAdapter.leaveSafeMode(nn);
269     assertTrue("Secret manager should start when safe mode is exited",
270         sm.isRunning());
271 
272     LOG.info("========= entering safemode again");
273 
274     NameNodeAdapter.enterSafeMode(nn, false);
275     assertFalse("Secret manager should stop again when safe mode " +
276         "is manually entered", sm.isRunning());
277 
278     // Set the cluster to leave safemode quickly on its own.
279     cluster.getConfiguration(0).setInt(
280         DFSConfigKeys.DFS_NAMENODE_SAFEMODE_EXTENSION_KEY, 0);
281     cluster.setWaitSafeMode(true);
282     cluster.restartNameNode();
283     nn = cluster.getNameNode();
284     sm = NameNodeAdapter.getDtSecretManager(nn.getNamesystem());
285 
286     assertFalse(nn.isInSafeMode());
287     assertTrue(sm.isRunning());
288   }
289 
290   @SuppressWarnings("unchecked")
checkTokenIdentifier(UserGroupInformation ugi, final Token<?> token)291   private void checkTokenIdentifier(UserGroupInformation ugi, final Token<?> token)
292       throws Exception {
293     Assert.assertNotNull(token);
294     // should be able to use token.decodeIdentifier() but webhdfs isn't
295     // registered with the service loader for token decoding
296     DelegationTokenIdentifier identifier = new DelegationTokenIdentifier();
297     byte[] tokenId = token.getIdentifier();
298     DataInputStream in = new DataInputStream(new ByteArrayInputStream(tokenId));
299     try {
300       identifier.readFields(in);
301     } finally {
302       in.close();
303     }
304     Assert.assertNotNull(identifier);
305     LOG.info("A valid token should have non-null password, and should be renewed successfully");
306     Assert.assertTrue(null != dtSecretManager.retrievePassword(identifier));
307     dtSecretManager.renewToken((Token<DelegationTokenIdentifier>) token, "JobTracker");
308     ugi.doAs(
309         new PrivilegedExceptionAction<Object>() {
310           @Override
311           public Object run() throws Exception {
312             token.renew(config);
313             token.cancel(config);
314             return null;
315           }
316         });
317   }
318 }
319