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