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
019 package org.apache.hadoop.hdfs.security.token.delegation;
020
021 import java.io.DataInput;
022 import java.io.DataOutputStream;
023 import java.io.IOException;
024 import java.io.InterruptedIOException;
025 import java.net.InetSocketAddress;
026 import java.util.ArrayList;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Map.Entry;
030
031 import org.apache.commons.logging.Log;
032 import org.apache.commons.logging.LogFactory;
033 import org.apache.hadoop.classification.InterfaceAudience;
034 import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
035 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
036 import org.apache.hadoop.hdfs.server.namenode.NameNode;
037 import org.apache.hadoop.hdfs.server.namenode.NameNode.OperationCategory;
038 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
039 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
040 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Counter;
041 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
042 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
043 import org.apache.hadoop.io.Text;
044 import org.apache.hadoop.ipc.RetriableException;
045 import org.apache.hadoop.ipc.StandbyException;
046 import org.apache.hadoop.security.Credentials;
047 import org.apache.hadoop.security.SecurityUtil;
048 import org.apache.hadoop.security.UserGroupInformation;
049 import org.apache.hadoop.security.token.Token;
050 import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
051 import org.apache.hadoop.security.token.delegation.DelegationKey;
052
053 import com.google.common.base.Preconditions;
054 import com.google.common.collect.Lists;
055 import 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
063 public 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 }