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}