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}