001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hdfs.security.token.delegation; 020 021import java.io.DataInput; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.net.InetSocketAddress; 025import java.util.ArrayList; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map.Entry; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.apache.hadoop.classification.InterfaceAudience; 033import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; 034import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection; 035import org.apache.hadoop.hdfs.server.namenode.NameNode; 036import org.apache.hadoop.hdfs.server.namenode.NameNode.OperationCategory; 037import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase; 038import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress; 039import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Counter; 040import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step; 041import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType; 042import org.apache.hadoop.io.Text; 043import org.apache.hadoop.ipc.RetriableException; 044import org.apache.hadoop.ipc.StandbyException; 045import org.apache.hadoop.security.Credentials; 046import org.apache.hadoop.security.SecurityUtil; 047import org.apache.hadoop.security.UserGroupInformation; 048import org.apache.hadoop.security.token.Token; 049import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager; 050import org.apache.hadoop.security.token.delegation.DelegationKey; 051 052import com.google.common.base.Preconditions; 053import com.google.common.collect.Lists; 054import com.google.protobuf.ByteString; 055 056/** 057 * A HDFS specific delegation token secret manager. 058 * The secret manager is responsible for generating and accepting the password 059 * for each token. 060 */ 061@InterfaceAudience.Private 062public class DelegationTokenSecretManager 063 extends AbstractDelegationTokenSecretManager<DelegationTokenIdentifier> { 064 065 private static final Log LOG = LogFactory 066 .getLog(DelegationTokenSecretManager.class); 067 068 private final FSNamesystem namesystem; 069 private final SerializerCompat serializerCompat = new SerializerCompat(); 070 071 public DelegationTokenSecretManager(long delegationKeyUpdateInterval, 072 long delegationTokenMaxLifetime, long delegationTokenRenewInterval, 073 long delegationTokenRemoverScanInterval, FSNamesystem namesystem) { 074 this(delegationKeyUpdateInterval, delegationTokenMaxLifetime, 075 delegationTokenRenewInterval, delegationTokenRemoverScanInterval, false, 076 namesystem); 077 } 078 079 /** 080 * Create a secret manager 081 * @param delegationKeyUpdateInterval the number of seconds for rolling new 082 * secret keys. 083 * @param delegationTokenMaxLifetime the maximum lifetime of the delegation 084 * tokens 085 * @param delegationTokenRenewInterval how often the tokens must be renewed 086 * @param delegationTokenRemoverScanInterval how often the tokens are scanned 087 * for expired tokens 088 * @param storeTokenTrackingId whether to store the token's tracking id 089 */ 090 public DelegationTokenSecretManager(long delegationKeyUpdateInterval, 091 long delegationTokenMaxLifetime, long delegationTokenRenewInterval, 092 long delegationTokenRemoverScanInterval, boolean storeTokenTrackingId, 093 FSNamesystem namesystem) { 094 super(delegationKeyUpdateInterval, delegationTokenMaxLifetime, 095 delegationTokenRenewInterval, delegationTokenRemoverScanInterval); 096 this.namesystem = namesystem; 097 this.storeTokenTrackingId = storeTokenTrackingId; 098 } 099 100 @Override //SecretManager 101 public DelegationTokenIdentifier createIdentifier() { 102 return new DelegationTokenIdentifier(); 103 } 104 105 @Override 106 public byte[] retrievePassword( 107 DelegationTokenIdentifier identifier) throws InvalidToken { 108 try { 109 // this check introduces inconsistency in the authentication to a 110 // HA standby NN. non-token auths are allowed into the namespace which 111 // decides whether to throw a StandbyException. tokens are a bit 112 // different in that a standby may be behind and thus not yet know 113 // of all tokens issued by the active NN. the following check does 114 // not allow ANY token auth, however it should allow known tokens in 115 namesystem.checkOperation(OperationCategory.READ); 116 } catch (StandbyException se) { 117 // FIXME: this is a hack to get around changing method signatures by 118 // tunneling a non-InvalidToken exception as the cause which the 119 // RPC server will unwrap before returning to the client 120 InvalidToken wrappedStandby = new InvalidToken("StandbyException"); 121 wrappedStandby.initCause(se); 122 throw wrappedStandby; 123 } 124 return super.retrievePassword(identifier); 125 } 126 127 @Override 128 public byte[] retriableRetrievePassword(DelegationTokenIdentifier identifier) 129 throws InvalidToken, StandbyException, RetriableException, IOException { 130 namesystem.checkOperation(OperationCategory.READ); 131 try { 132 return super.retrievePassword(identifier); 133 } catch (InvalidToken it) { 134 if (namesystem.inTransitionToActive()) { 135 // if the namesystem is currently in the middle of transition to 136 // active state, let client retry since the corresponding editlog may 137 // have not been applied yet 138 throw new RetriableException(it); 139 } else { 140 throw it; 141 } 142 } 143 } 144 145 /** 146 * Returns expiry time of a token given its identifier. 147 * 148 * @param dtId DelegationTokenIdentifier of a token 149 * @return Expiry time of the token 150 * @throws IOException 151 */ 152 public synchronized long getTokenExpiryTime( 153 DelegationTokenIdentifier dtId) throws IOException { 154 DelegationTokenInformation info = currentTokens.get(dtId); 155 if (info != null) { 156 return info.getRenewDate(); 157 } else { 158 throw new IOException("No delegation token found for this identifier"); 159 } 160 } 161 162 /** 163 * Load SecretManager state from fsimage. 164 * 165 * @param in input stream to read fsimage 166 * @throws IOException 167 */ 168 public synchronized void loadSecretManagerStateCompat(DataInput in) 169 throws IOException { 170 if (running) { 171 // a safety check 172 throw new IOException( 173 "Can't load state from image in a running SecretManager."); 174 } 175 serializerCompat.load(in); 176 } 177 178 public static class SecretManagerState { 179 public final SecretManagerSection section; 180 public final List<SecretManagerSection.DelegationKey> keys; 181 public final List<SecretManagerSection.PersistToken> tokens; 182 183 public SecretManagerState( 184 SecretManagerSection s, 185 List<SecretManagerSection.DelegationKey> keys, 186 List<SecretManagerSection.PersistToken> tokens) { 187 this.section = s; 188 this.keys = keys; 189 this.tokens = tokens; 190 } 191 } 192 193 public synchronized void loadSecretManagerState(SecretManagerState state) 194 throws IOException { 195 Preconditions.checkState(!running, 196 "Can't load state from image in a running SecretManager."); 197 198 currentId = state.section.getCurrentId(); 199 delegationTokenSequenceNumber = state.section.getTokenSequenceNumber(); 200 for (SecretManagerSection.DelegationKey k : state.keys) { 201 addKey(new DelegationKey(k.getId(), k.getExpiryDate(), k.hasKey() ? k 202 .getKey().toByteArray() : null)); 203 } 204 205 for (SecretManagerSection.PersistToken t : state.tokens) { 206 DelegationTokenIdentifier id = new DelegationTokenIdentifier(new Text( 207 t.getOwner()), new Text(t.getRenewer()), new Text(t.getRealUser())); 208 id.setIssueDate(t.getIssueDate()); 209 id.setMaxDate(t.getMaxDate()); 210 id.setSequenceNumber(t.getSequenceNumber()); 211 id.setMasterKeyId(t.getMasterKeyId()); 212 addPersistedDelegationToken(id, t.getExpiryDate()); 213 } 214 } 215 216 /** 217 * Store the current state of the SecretManager for persistence 218 * 219 * @param out Output stream for writing into fsimage. 220 * @param sdPath String storage directory path 221 * @throws IOException 222 */ 223 public synchronized void saveSecretManagerStateCompat(DataOutputStream out, 224 String sdPath) throws IOException { 225 serializerCompat.save(out, sdPath); 226 } 227 228 public synchronized SecretManagerState saveSecretManagerState() { 229 SecretManagerSection s = SecretManagerSection.newBuilder() 230 .setCurrentId(currentId) 231 .setTokenSequenceNumber(delegationTokenSequenceNumber) 232 .setNumKeys(allKeys.size()).setNumTokens(currentTokens.size()).build(); 233 ArrayList<SecretManagerSection.DelegationKey> keys = Lists 234 .newArrayListWithCapacity(allKeys.size()); 235 ArrayList<SecretManagerSection.PersistToken> tokens = Lists 236 .newArrayListWithCapacity(currentTokens.size()); 237 238 for (DelegationKey v : allKeys.values()) { 239 SecretManagerSection.DelegationKey.Builder b = SecretManagerSection.DelegationKey 240 .newBuilder().setId(v.getKeyId()).setExpiryDate(v.getExpiryDate()); 241 if (v.getEncodedKey() != null) { 242 b.setKey(ByteString.copyFrom(v.getEncodedKey())); 243 } 244 keys.add(b.build()); 245 } 246 247 for (Entry<DelegationTokenIdentifier, DelegationTokenInformation> e : currentTokens 248 .entrySet()) { 249 DelegationTokenIdentifier id = e.getKey(); 250 SecretManagerSection.PersistToken.Builder b = SecretManagerSection.PersistToken 251 .newBuilder().setOwner(id.getOwner().toString()) 252 .setRenewer(id.getRenewer().toString()) 253 .setRealUser(id.getRealUser().toString()) 254 .setIssueDate(id.getIssueDate()).setMaxDate(id.getMaxDate()) 255 .setSequenceNumber(id.getSequenceNumber()) 256 .setMasterKeyId(id.getMasterKeyId()) 257 .setExpiryDate(e.getValue().getRenewDate()); 258 tokens.add(b.build()); 259 } 260 261 return new SecretManagerState(s, keys, tokens); 262 } 263 264 /** 265 * This method is intended to be used only while reading edit logs. 266 * 267 * @param identifier DelegationTokenIdentifier read from the edit logs or 268 * fsimage 269 * 270 * @param expiryTime token expiry time 271 * @throws IOException 272 */ 273 public synchronized void addPersistedDelegationToken( 274 DelegationTokenIdentifier identifier, long expiryTime) throws IOException { 275 if (running) { 276 // a safety check 277 throw new IOException( 278 "Can't add persisted delegation token to a running SecretManager."); 279 } 280 int keyId = identifier.getMasterKeyId(); 281 DelegationKey dKey = allKeys.get(keyId); 282 if (dKey == null) { 283 LOG 284 .warn("No KEY found for persisted identifier " 285 + identifier.toString()); 286 return; 287 } 288 byte[] password = createPassword(identifier.getBytes(), dKey.getKey()); 289 if (identifier.getSequenceNumber() > this.delegationTokenSequenceNumber) { 290 this.delegationTokenSequenceNumber = identifier.getSequenceNumber(); 291 } 292 if (currentTokens.get(identifier) == null) { 293 currentTokens.put(identifier, new DelegationTokenInformation(expiryTime, 294 password, getTrackingIdIfEnabled(identifier))); 295 } else { 296 throw new IOException( 297 "Same delegation token being added twice; invalid entry in fsimage or editlogs"); 298 } 299 } 300 301 /** 302 * Add a MasterKey to the list of keys. 303 * 304 * @param key DelegationKey 305 * @throws IOException 306 */ 307 public synchronized void updatePersistedMasterKey(DelegationKey key) 308 throws IOException { 309 addKey(key); 310 } 311 312 /** 313 * Update the token cache with renewal record in edit logs. 314 * 315 * @param identifier DelegationTokenIdentifier of the renewed token 316 * @param expiryTime expirty time in milliseconds 317 * @throws IOException 318 */ 319 public synchronized void updatePersistedTokenRenewal( 320 DelegationTokenIdentifier identifier, long expiryTime) throws IOException { 321 if (running) { 322 // a safety check 323 throw new IOException( 324 "Can't update persisted delegation token renewal to a running SecretManager."); 325 } 326 DelegationTokenInformation info = null; 327 info = currentTokens.get(identifier); 328 if (info != null) { 329 int keyId = identifier.getMasterKeyId(); 330 byte[] password = createPassword(identifier.getBytes(), allKeys 331 .get(keyId).getKey()); 332 currentTokens.put(identifier, new DelegationTokenInformation(expiryTime, 333 password, getTrackingIdIfEnabled(identifier))); 334 } 335 } 336 337 /** 338 * Update the token cache with the cancel record in edit logs 339 * 340 * @param identifier DelegationTokenIdentifier of the canceled token 341 * @throws IOException 342 */ 343 public synchronized void updatePersistedTokenCancellation( 344 DelegationTokenIdentifier identifier) throws IOException { 345 if (running) { 346 // a safety check 347 throw new IOException( 348 "Can't update persisted delegation token renewal to a running SecretManager."); 349 } 350 currentTokens.remove(identifier); 351 } 352 353 /** 354 * Returns the number of delegation keys currently stored. 355 * @return number of delegation keys 356 */ 357 public synchronized int getNumberOfKeys() { 358 return allKeys.size(); 359 } 360 361 /** 362 * Call namesystem to update editlogs for new master key. 363 */ 364 @Override //AbstractDelegationTokenManager 365 protected void logUpdateMasterKey(DelegationKey key) 366 throws IOException { 367 try { 368 // The edit logging code will fail catastrophically if it 369 // is interrupted during a logSync, since the interrupt 370 // closes the edit log files. Doing this inside the 371 // fsn lock will prevent being interrupted when stopping 372 // the secret manager. 373 namesystem.readLockInterruptibly(); 374 try { 375 // this monitor isn't necessary if stopped while holding write lock 376 // but for safety, guard against a stop with read lock. 377 synchronized (noInterruptsLock) { 378 if (Thread.currentThread().isInterrupted()) { 379 return; // leave flag set so secret monitor exits. 380 } 381 namesystem.logUpdateMasterKey(key); 382 } 383 } finally { 384 namesystem.readUnlock(); 385 } 386 } catch (InterruptedException ie) { 387 // AbstractDelegationTokenManager may crash if an exception is thrown. 388 // The interrupt flag will be detected when it attempts to sleep. 389 Thread.currentThread().interrupt(); 390 } 391 } 392 393 @Override //AbstractDelegationTokenManager 394 protected void logExpireToken(final DelegationTokenIdentifier dtId) 395 throws IOException { 396 try { 397 // The edit logging code will fail catastrophically if it 398 // is interrupted during a logSync, since the interrupt 399 // closes the edit log files. Doing this inside the 400 // fsn lock will prevent being interrupted when stopping 401 // the secret manager. 402 namesystem.readLockInterruptibly(); 403 try { 404 // this monitor isn't necessary if stopped while holding write lock 405 // but for safety, guard against a stop with read lock. 406 synchronized (noInterruptsLock) { 407 if (Thread.currentThread().isInterrupted()) { 408 return; // leave flag set so secret monitor exits. 409 } 410 namesystem.logExpireDelegationToken(dtId); 411 } 412 } finally { 413 namesystem.readUnlock(); 414 } 415 } catch (InterruptedException ie) { 416 // AbstractDelegationTokenManager may crash if an exception is thrown. 417 // The interrupt flag will be detected when it attempts to sleep. 418 Thread.currentThread().interrupt(); 419 } 420 } 421 422 /** A utility method for creating credentials. */ 423 public static Credentials createCredentials(final NameNode namenode, 424 final UserGroupInformation ugi, final String renewer) throws IOException { 425 final Token<DelegationTokenIdentifier> token = namenode.getRpcServer( 426 ).getDelegationToken(new Text(renewer)); 427 if (token == null) { 428 return null; 429 } 430 431 final InetSocketAddress addr = namenode.getNameNodeAddress(); 432 SecurityUtil.setTokenService(token, addr); 433 final Credentials c = new Credentials(); 434 c.addToken(new Text(ugi.getShortUserName()), token); 435 return c; 436 } 437 438 private final class SerializerCompat { 439 private void load(DataInput in) throws IOException { 440 currentId = in.readInt(); 441 loadAllKeys(in); 442 delegationTokenSequenceNumber = in.readInt(); 443 loadCurrentTokens(in); 444 } 445 446 private void save(DataOutputStream out, String sdPath) throws IOException { 447 out.writeInt(currentId); 448 saveAllKeys(out, sdPath); 449 out.writeInt(delegationTokenSequenceNumber); 450 saveCurrentTokens(out, sdPath); 451 } 452 453 /** 454 * Private helper methods to save delegation keys and tokens in fsimage 455 */ 456 private synchronized void saveCurrentTokens(DataOutputStream out, 457 String sdPath) throws IOException { 458 StartupProgress prog = NameNode.getStartupProgress(); 459 Step step = new Step(StepType.DELEGATION_TOKENS, sdPath); 460 prog.beginStep(Phase.SAVING_CHECKPOINT, step); 461 prog.setTotal(Phase.SAVING_CHECKPOINT, step, currentTokens.size()); 462 Counter counter = prog.getCounter(Phase.SAVING_CHECKPOINT, step); 463 out.writeInt(currentTokens.size()); 464 Iterator<DelegationTokenIdentifier> iter = currentTokens.keySet() 465 .iterator(); 466 while (iter.hasNext()) { 467 DelegationTokenIdentifier id = iter.next(); 468 id.write(out); 469 DelegationTokenInformation info = currentTokens.get(id); 470 out.writeLong(info.getRenewDate()); 471 counter.increment(); 472 } 473 prog.endStep(Phase.SAVING_CHECKPOINT, step); 474 } 475 476 /* 477 * Save the current state of allKeys 478 */ 479 private synchronized void saveAllKeys(DataOutputStream out, String sdPath) 480 throws IOException { 481 StartupProgress prog = NameNode.getStartupProgress(); 482 Step step = new Step(StepType.DELEGATION_KEYS, sdPath); 483 prog.beginStep(Phase.SAVING_CHECKPOINT, step); 484 prog.setTotal(Phase.SAVING_CHECKPOINT, step, currentTokens.size()); 485 Counter counter = prog.getCounter(Phase.SAVING_CHECKPOINT, step); 486 out.writeInt(allKeys.size()); 487 Iterator<Integer> iter = allKeys.keySet().iterator(); 488 while (iter.hasNext()) { 489 Integer key = iter.next(); 490 allKeys.get(key).write(out); 491 counter.increment(); 492 } 493 prog.endStep(Phase.SAVING_CHECKPOINT, step); 494 } 495 496 /** 497 * Private helper methods to load Delegation tokens from fsimage 498 */ 499 private synchronized void loadCurrentTokens(DataInput in) 500 throws IOException { 501 StartupProgress prog = NameNode.getStartupProgress(); 502 Step step = new Step(StepType.DELEGATION_TOKENS); 503 prog.beginStep(Phase.LOADING_FSIMAGE, step); 504 int numberOfTokens = in.readInt(); 505 prog.setTotal(Phase.LOADING_FSIMAGE, step, numberOfTokens); 506 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step); 507 for (int i = 0; i < numberOfTokens; i++) { 508 DelegationTokenIdentifier id = new DelegationTokenIdentifier(); 509 id.readFields(in); 510 long expiryTime = in.readLong(); 511 addPersistedDelegationToken(id, expiryTime); 512 counter.increment(); 513 } 514 prog.endStep(Phase.LOADING_FSIMAGE, step); 515 } 516 517 /** 518 * Private helper method to load delegation keys from fsimage. 519 * @throws IOException on error 520 */ 521 private synchronized void loadAllKeys(DataInput in) throws IOException { 522 StartupProgress prog = NameNode.getStartupProgress(); 523 Step step = new Step(StepType.DELEGATION_KEYS); 524 prog.beginStep(Phase.LOADING_FSIMAGE, step); 525 int numberOfKeys = in.readInt(); 526 prog.setTotal(Phase.LOADING_FSIMAGE, step, numberOfKeys); 527 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step); 528 for (int i = 0; i < numberOfKeys; i++) { 529 DelegationKey value = new DelegationKey(); 530 value.readFields(in); 531 addKey(value); 532 counter.increment(); 533 } 534 prog.endStep(Phase.LOADING_FSIMAGE, step); 535 } 536 } 537 538}