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}