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    }