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