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}