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 com.google.common.base.Preconditions;
022    import com.google.common.collect.Lists;
023    import com.google.protobuf.ByteString;
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.apache.hadoop.classification.InterfaceAudience;
027    import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
028    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
029    import org.apache.hadoop.hdfs.server.namenode.NameNode;
030    import org.apache.hadoop.hdfs.server.namenode.NameNode.OperationCategory;
031    import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
032    import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
033    import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Counter;
034    import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
035    import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
036    import org.apache.hadoop.io.Text;
037    import org.apache.hadoop.ipc.RetriableException;
038    import org.apache.hadoop.ipc.StandbyException;
039    import org.apache.hadoop.security.Credentials;
040    import org.apache.hadoop.security.SecurityUtil;
041    import org.apache.hadoop.security.UserGroupInformation;
042    import org.apache.hadoop.security.token.Token;
043    import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
044    import org.apache.hadoop.security.token.delegation.DelegationKey;
045    
046    import java.io.DataInput;
047    import java.io.IOException;
048    import java.io.InterruptedIOException;
049    import java.net.InetSocketAddress;
050    import java.util.ArrayList;
051    import java.util.List;
052    import 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
060    public 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    }