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