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