You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by om...@apache.org on 2010/02/09 09:10:38 UTC

svn commit: r907956 - in /hadoop/common/trunk: ./ src/java/org/apache/hadoop/security/token/delegation/ src/test/core/org/apache/hadoop/security/token/delegation/

Author: omalley
Date: Tue Feb  9 08:10:37 2010
New Revision: 907956

URL: http://svn.apache.org/viewvc?rev=907956&view=rev
Log:
HADOOP-6547. Move DelegationToken into Common, so that it can be used by
MapReduce also. (devaraj via omalley)

Added:
    hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/
    hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java
    hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java
    hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSelector.java
    hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/DelegationKey.java
    hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/
    hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/TestDelegationToken.java
Modified:
    hadoop/common/trunk/CHANGES.txt

Modified: hadoop/common/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/CHANGES.txt?rev=907956&r1=907955&r2=907956&view=diff
==============================================================================
--- hadoop/common/trunk/CHANGES.txt (original)
+++ hadoop/common/trunk/CHANGES.txt Tue Feb  9 08:10:37 2010
@@ -131,6 +131,9 @@
     HADOOP-6531. Enhance FileUtil with an API to delete all contents of a
     directory. (Amareshwari Sriramadasu via yhemanth)
 
+    HADOOP-6547. Move DelegationToken into Common, so that it can be used by
+    MapReduce also. (devaraj via omalley)
+
   OPTIMIZATIONS
 
   BUG FIXES

Added: hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java?rev=907956&view=auto
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java (added)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java Tue Feb  9 08:10:37 2010
@@ -0,0 +1,166 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.security.token.delegation;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.HDFS;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.MAPREDUCE;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.WritableUtils;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.TokenIdentifier;
+
+@InterfaceAudience.LimitedPrivate({HDFS, MAPREDUCE})
+public abstract class AbstractDelegationTokenIdentifier 
+extends TokenIdentifier {
+
+  private Text owner;
+  private Text renewer;
+  private Text realUser;
+  private long issueDate;
+  private long maxDate;
+  private int sequenceNumber;
+  private int masterKeyId = 0;
+  
+  public AbstractDelegationTokenIdentifier() {
+    this(new Text(), new Text(), new Text());
+  }
+  
+  public AbstractDelegationTokenIdentifier(Text owner, Text renewer, Text realUser) {
+    this.owner = owner;
+    this.renewer = renewer;
+    if (realUser == null) {
+      this.realUser = new Text();
+    } else {
+      this.realUser = realUser;
+    }
+    issueDate = 0;
+    maxDate = 0;
+  }
+
+  @Override
+  public abstract Text getKind();
+  
+  /**
+   * Get the username encoded in the token identifier
+   * 
+   * @return the username or owner
+   */
+  public UserGroupInformation getUser() {
+    if ( (owner == null) || ("".equals(owner.toString()))) {
+      return null;
+    }
+    if ((realUser == null) || ("".equals(realUser.toString()))
+        || realUser.equals(owner)) {
+      return UserGroupInformation.createRemoteUser(owner.toString());
+    } else {
+      UserGroupInformation realUgi = UserGroupInformation
+          .createRemoteUser(realUser.toString());
+      return UserGroupInformation.createProxyUser(owner.toString(), realUgi);
+    }
+  }
+
+  public Text getRenewer() {
+    return renewer;
+  }
+  
+  public void setIssueDate(long issueDate) {
+    this.issueDate = issueDate;
+  }
+  
+  public long getIssueDate() {
+    return issueDate;
+  }
+  
+  public void setMaxDate(long maxDate) {
+    this.maxDate = maxDate;
+  }
+  
+  public long getMaxDate() {
+    return maxDate;
+  }
+
+  public void setSequenceNumber(int seqNum) {
+    this.sequenceNumber = seqNum;
+  }
+  
+  public int getSequenceNumber() {
+    return sequenceNumber;
+  }
+
+  public void setMasterKeyId(int newId) {
+    masterKeyId = newId;
+  }
+
+  public int getMasterKeyId() {
+    return masterKeyId;
+  }
+
+  static boolean isEqual(Object a, Object b) {
+    return a == null ? b == null : a.equals(b);
+  }
+  
+  /** {@inheritDoc} */
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (obj instanceof AbstractDelegationTokenIdentifier) {
+      AbstractDelegationTokenIdentifier that = (AbstractDelegationTokenIdentifier) obj;
+      return this.sequenceNumber == that.sequenceNumber 
+          && this.issueDate == that.issueDate 
+          && this.maxDate == that.maxDate
+          && this.masterKeyId == that.masterKeyId
+          && isEqual(this.owner, that.owner) 
+          && isEqual(this.renewer, that.renewer)
+          && isEqual(this.realUser, that.realUser);
+    }
+    return false;
+  }
+
+  /** {@inheritDoc} */
+  public int hashCode() {
+    return this.sequenceNumber;
+  }
+  
+  public void readFields(DataInput in) throws IOException {
+    owner.readFields(in);
+    renewer.readFields(in);
+    realUser.readFields(in);
+    issueDate = WritableUtils.readVLong(in);
+    maxDate = WritableUtils.readVLong(in);
+    sequenceNumber = WritableUtils.readVInt(in);
+    masterKeyId = WritableUtils.readVInt(in);
+  }
+
+  public void write(DataOutput out) throws IOException {
+    owner.write(out);
+    renewer.write(out);
+    realUser.write(out);
+    WritableUtils.writeVLong(out, issueDate);
+    WritableUtils.writeVLong(out, maxDate);
+    WritableUtils.writeVInt(out, sequenceNumber);
+    WritableUtils.writeVInt(out, masterKeyId);
+  }
+}

Added: hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java?rev=907956&view=auto
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java (added)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java Tue Feb  9 08:10:37 2010
@@ -0,0 +1,354 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.security.token.delegation;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.HDFS;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.MAPREDUCE;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.crypto.SecretKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.SecretManager;
+import org.apache.hadoop.util.Daemon;
+import org.apache.hadoop.util.StringUtils;
+
+@InterfaceAudience.LimitedPrivate({HDFS, MAPREDUCE})
+public abstract 
+class AbstractDelegationTokenSecretManager<TokenIdent 
+extends AbstractDelegationTokenIdentifier> 
+   extends SecretManager<TokenIdent> {
+  private static final Log LOG = LogFactory
+      .getLog(AbstractDelegationTokenSecretManager.class);
+
+  /** 
+   * Cache of currently valid tokens, mapping from DelegationTokenIdentifier 
+   * to DelegationTokenInformation. Protected by its own lock.
+   */
+  private final Map<TokenIdent, DelegationTokenInformation> currentTokens 
+      = new HashMap<TokenIdent, DelegationTokenInformation>();
+  
+  /**
+   * Sequence number to create DelegationTokenIdentifier
+   */
+  private int delegationTokenSequenceNumber = 0;
+  
+  private final Map<Integer, DelegationKey> allKeys 
+      = new HashMap<Integer, DelegationKey>();
+  
+  /**
+   * Access to currentId and currentKey is protected by this object lock.
+   */
+  private int currentId = 0;
+  private DelegationKey currentKey;
+  
+  private long keyUpdateInterval;
+  private long tokenMaxLifetime;
+  private long tokenRemoverScanInterval;
+  private long tokenRenewInterval;
+  private Thread tokenRemoverThread;
+  private volatile boolean running;
+
+  public AbstractDelegationTokenSecretManager(long delegationKeyUpdateInterval,
+      long delegationTokenMaxLifetime, long delegationTokenRenewInterval,
+      long delegationTokenRemoverScanInterval) {
+    this.keyUpdateInterval = delegationKeyUpdateInterval;
+    this.tokenMaxLifetime = delegationTokenMaxLifetime;
+    this.tokenRenewInterval = delegationTokenRenewInterval;
+    this.tokenRemoverScanInterval = delegationTokenRemoverScanInterval;
+  }
+
+  /** should be called before this object is used */
+  public synchronized void startThreads() throws IOException {
+    updateCurrentKey();
+    running = true;
+    tokenRemoverThread = new Daemon(new ExpiredTokenRemover());
+    tokenRemoverThread.start();
+  }
+  
+  /** 
+   * Add a previously used master key to cache (when NN restarts), 
+   * should be called before activate().
+   * */
+  public synchronized void addKey(DelegationKey key) throws IOException {
+    if (running) // a safety check
+      throw new IOException("Can't add delegation key to a running SecretManager.");
+    if (key.getKeyId() > currentId) {
+      currentId = key.getKeyId();
+    }
+    allKeys.put(key.getKeyId(), key);
+  }
+
+  public synchronized DelegationKey[] getAllKeys() {
+    return allKeys.values().toArray(new DelegationKey[0]);
+  }
+  
+  /** Update the current master key */
+  private synchronized void updateCurrentKey() throws IOException {
+    LOG.info("Updating the current master key for generating delegation tokens");
+    /* Create a new currentKey with an estimated expiry date. */
+    currentId++;
+    currentKey = new DelegationKey(currentId, System.currentTimeMillis()
+        + keyUpdateInterval + tokenMaxLifetime, generateSecret());
+    allKeys.put(currentKey.getKeyId(), currentKey);
+  }
+  
+  /** Update the current master key for generating delegation tokens */
+  public synchronized void rollMasterKey() throws IOException {
+    removeExpiredKeys();
+    /* set final expiry date for retiring currentKey */
+    currentKey.setExpiryDate(System.currentTimeMillis() + tokenMaxLifetime);
+    /*
+     * currentKey might have been removed by removeExpiredKeys(), if
+     * updateMasterKey() isn't called at expected interval. Add it back to
+     * allKeys just in case.
+     */
+    allKeys.put(currentKey.getKeyId(), currentKey);
+    updateCurrentKey();
+  }
+
+  private synchronized void removeExpiredKeys() {
+    long now = System.currentTimeMillis();
+    for (Iterator<Map.Entry<Integer, DelegationKey>> it = allKeys.entrySet()
+        .iterator(); it.hasNext();) {
+      Map.Entry<Integer, DelegationKey> e = it.next();
+      if (e.getValue().getExpiryDate() < now) {
+        it.remove();
+      }
+    }
+  }
+  
+  @Override
+  protected byte[] createPassword(TokenIdent identifier) {
+    int sequenceNum;
+    int id;
+    DelegationKey key;
+    long now = System.currentTimeMillis();    
+    synchronized (this) {
+      id = currentId;
+      key = currentKey;
+      sequenceNum = ++delegationTokenSequenceNumber;
+    }
+    identifier.setIssueDate(now);
+    identifier.setMaxDate(now + tokenMaxLifetime);
+    identifier.setMasterKeyId(id);
+    identifier.setSequenceNumber(sequenceNum);
+    byte[] password = createPassword(identifier.getBytes(), key.getKey());
+    synchronized (currentTokens) {
+      currentTokens.put(identifier, new DelegationTokenInformation(now
+          + tokenRenewInterval, password));
+    }
+    return password;
+  }
+
+  @Override
+  public byte[] retrievePassword(TokenIdent identifier
+                                 ) throws InvalidToken {
+    DelegationTokenInformation info = null;
+    synchronized (currentTokens) {
+      info = currentTokens.get(identifier);
+    }
+    if (info == null) {
+      throw new InvalidToken("token is expired or doesn't exist");
+    }
+    long now = System.currentTimeMillis();
+    if (info.getRenewDate() < now) {
+      throw new InvalidToken("token is expired");
+    }
+    return info.getPassword();
+  }
+
+  /**
+   * Renew a delegation token. Canceled tokens are not renewed. Return true if
+   * the token is successfully renewed; false otherwise.
+   */
+  public Boolean renewToken(Token<TokenIdent> token,
+      String renewer) throws InvalidToken, IOException {
+    long now = System.currentTimeMillis();
+    ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+    DataInputStream in = new DataInputStream(buf);
+    TokenIdent id = createIdentifier();
+    id.readFields(in);
+    synchronized (currentTokens) {
+      if (currentTokens.get(id) == null) {
+        LOG.warn("Renewal request for unknown token");
+        return false;
+      }
+    }
+    if (id.getMaxDate() < now) {
+      LOG.warn("Client " + renewer + " tries to renew an expired token");
+      return false;
+    }
+    if (id.getRenewer() == null || !id.getRenewer().toString().equals(renewer)) {
+      LOG.warn("Client " + renewer + " tries to renew a token with "
+          + "renewer specified as " + id.getRenewer());
+      return false;
+    }
+    DelegationKey key = null;
+    synchronized (this) {
+      key = allKeys.get(id.getMasterKeyId());
+    }
+    if (key == null) {
+      LOG.warn("Unable to find master key for keyId=" + id.getMasterKeyId() 
+          + " from cache. Failed to renew an unexpired token with sequenceNumber=" 
+          + id.getSequenceNumber() + ", issued by this key");
+      return false;
+    }
+    byte[] password = createPassword(token.getIdentifier(), key.getKey());
+    if (!Arrays.equals(password, token.getPassword())) {
+      LOG.warn("Client " + renewer + " is trying to renew a token with wrong password");
+      return false;
+    }
+    DelegationTokenInformation info = new DelegationTokenInformation(
+        Math.min(id.getMaxDate(), now + tokenRenewInterval), password);
+    synchronized (currentTokens) {
+      currentTokens.put(id, info);
+    }
+    return true;
+  }
+  
+  /**
+   * Cancel a token by removing it from cache. Return true if 
+   * token exists in cache; false otherwise.
+   */
+  public Boolean cancelToken(Token<TokenIdent> token,
+      String canceller) throws IOException {
+    ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+    DataInputStream in = new DataInputStream(buf);
+    TokenIdent id = createIdentifier();
+    id.readFields(in);
+    if (id.getRenewer() == null) {
+      LOG.warn("Renewer is null: Invalid Identifier");
+      return false;
+    }
+    if (id.getUser() == null) {
+      LOG.warn("owner is null: Invalid Identifier");
+      return false;
+    }
+    String owner = id.getUser().getUserName();
+    String renewer = id.getRenewer().toString();
+    if (!canceller.equals(owner) && !canceller.equals(renewer)) {
+      LOG.warn(canceller + " is not authorized to cancel the token");
+      return false;
+    }
+    DelegationTokenInformation info = null;
+    synchronized (currentTokens) {
+      info = currentTokens.remove(id);
+    }
+    return info != null;
+  }
+  
+  /**
+   * Convert the byte[] to a secret key
+   * @param key the byte[] to create the secret key from
+   * @return the secret key
+   */
+  public static SecretKey createSecretKey(byte[] key) {
+    return SecretManager.createSecretKey(key);
+  }
+
+  /** Utility class to encapsulate a token's renew date and password. */
+  private static class DelegationTokenInformation {
+    long renewDate;
+    byte[] password;
+    DelegationTokenInformation(long renewDate, byte[] password) {
+      this.renewDate = renewDate;
+      this.password = password;
+    }
+    /** returns renew date */
+    long getRenewDate() {
+      return renewDate;
+    }
+    /** returns password */
+    byte[] getPassword() {
+      return password;
+    }
+  }
+  
+  /** Remove expired delegation tokens from cache */
+  private void removeExpiredToken() {
+    long now = System.currentTimeMillis();
+    synchronized (currentTokens) {
+      Iterator<DelegationTokenInformation> i = currentTokens.values().iterator();
+      while (i.hasNext()) {
+        long renewDate = i.next().getRenewDate();
+        if (now > renewDate) {
+          i.remove();
+        }
+      }
+    }
+  }
+
+  public synchronized void stopThreads() {
+    if (LOG.isDebugEnabled())
+      LOG.debug("Stopping expired delegation token remover thread");
+    running = false;
+    tokenRemoverThread.interrupt();
+  }
+  
+  private class ExpiredTokenRemover extends Thread {
+    private long lastMasterKeyUpdate;
+    private long lastTokenCacheCleanup;
+
+    public void run() {
+      LOG.info("Starting expired delegation token remover thread, "
+          + "tokenRemoverScanInterval=" + tokenRemoverScanInterval
+          / (60 * 1000) + " min(s)");
+      try {
+        while (running) {
+          long now = System.currentTimeMillis();
+          if (lastMasterKeyUpdate + keyUpdateInterval < now) {
+            try {
+              rollMasterKey();
+              lastMasterKeyUpdate = now;
+            } catch (IOException e) {
+              LOG.error("Master key updating failed. "
+                  + StringUtils.stringifyException(e));
+            }
+          }
+          if (lastTokenCacheCleanup + tokenRemoverScanInterval < now) {
+            removeExpiredToken();
+            lastTokenCacheCleanup = now;
+          }
+          try {
+            Thread.sleep(5000); // 5 seconds
+          } catch (InterruptedException ie) {
+            LOG
+            .error("InterruptedExcpetion recieved for ExpiredTokenRemover thread "
+                + ie);
+          }
+        }
+      } catch (Throwable t) {
+        LOG.error("ExpiredTokenRemover thread received unexpected exception. "
+            + t);
+        Runtime.getRuntime().exit(-1);
+      }
+    }
+  }
+}

Added: hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSelector.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSelector.java?rev=907956&view=auto
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSelector.java (added)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSelector.java Tue Feb  9 08:10:37 2010
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.security.token.delegation;
+
+import java.util.Collection;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hadoop.security.token.TokenSelector;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.HDFS;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.MAPREDUCE;
+
+/**
+ * Look through tokens to find the first delegation token that matches the
+ * service and return it.
+ */
+@InterfaceAudience.LimitedPrivate({HDFS, MAPREDUCE})
+public 
+class AbstractDelegationTokenSelector<TokenIdent 
+extends AbstractDelegationTokenIdentifier> 
+    implements TokenSelector<TokenIdent> {
+  private Text kindName;
+  
+  protected AbstractDelegationTokenSelector(Text kindName) {
+    this.kindName = kindName;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Token<TokenIdent> selectToken(Text service,
+      Collection<Token<? extends TokenIdentifier>> tokens) {
+    if (service == null) {
+      return null;
+    }
+    for (Token<? extends TokenIdentifier> token : tokens) {
+      if (kindName.equals(token.getKind())
+          && service.equals(token.getService())) {
+        return (Token<TokenIdent>) token;
+      }
+    }
+    return null;
+  }
+}

Added: hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/DelegationKey.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/DelegationKey.java?rev=907956&view=auto
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/DelegationKey.java (added)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/token/delegation/DelegationKey.java Tue Feb  9 08:10:37 2010
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.security.token.delegation;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import javax.crypto.SecretKey;
+
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.io.WritableUtils;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.HDFS;
+import static org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project.MAPREDUCE;
+
+/**
+ * Key used for generating and verifying delegation tokens
+ */
+@InterfaceAudience.LimitedPrivate({HDFS, MAPREDUCE})
+public class DelegationKey implements Writable {
+  private int keyId;
+  private long expiryDate;
+  private SecretKey key;
+
+  public DelegationKey() {
+    this(0, 0L, null);
+  }
+
+  public DelegationKey(int keyId, long expiryDate, SecretKey key) {
+    this.keyId = keyId;
+    this.expiryDate = expiryDate;
+    this.key = key;
+  }
+
+  public int getKeyId() {
+    return keyId;
+  }
+
+  public long getExpiryDate() {
+    return expiryDate;
+  }
+
+  public SecretKey getKey() {
+    return key;
+  }
+
+  public void setExpiryDate(long expiryDate) {
+    this.expiryDate = expiryDate;
+  }
+
+  /**
+   */
+  public void write(DataOutput out) throws IOException {
+    WritableUtils.writeVInt(out, keyId);
+    WritableUtils.writeVLong(out, expiryDate);
+    byte[] keyBytes = key.getEncoded();
+    WritableUtils.writeVInt(out, keyBytes.length);
+    out.write(keyBytes);
+  }
+
+  /**
+   */
+  public void readFields(DataInput in) throws IOException {
+    keyId = WritableUtils.readVInt(in);
+    expiryDate = WritableUtils.readVLong(in);
+    int len = WritableUtils.readVInt(in);
+    byte[] keyBytes = new byte[len];
+    in.readFully(keyBytes);
+    key = AbstractDelegationTokenSecretManager.createSecretKey(keyBytes);
+  }
+}

Added: hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/TestDelegationToken.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/TestDelegationToken.java?rev=907956&view=auto
==============================================================================
--- hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/TestDelegationToken.java (added)
+++ hadoop/common/trunk/src/test/core/org/apache/hadoop/security/token/delegation/TestDelegationToken.java Tue Feb  9 08:10:37 2010
@@ -0,0 +1,262 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.security.token.delegation;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.apache.hadoop.io.DataInputBuffer;
+import org.apache.hadoop.io.DataOutputBuffer;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hadoop.security.token.SecretManager.InvalidToken;
+import org.junit.Test;
+import org.mortbay.log.Log;
+
+import static org.junit.Assert.*;
+
+public class TestDelegationToken {
+  private static final Text KIND = new Text("MY KIND");
+
+  public static class TestDelegationTokenIdentifier 
+  extends AbstractDelegationTokenIdentifier
+  implements Writable {
+
+    public TestDelegationTokenIdentifier() {
+    }
+
+    public TestDelegationTokenIdentifier(Text owner, Text renewer, Text realUser) {
+      super(owner, renewer, realUser);
+    }
+
+    @Override
+    public Text getKind() {
+      return KIND;
+    }
+    
+    public void write(DataOutput out) throws IOException {
+      super.write(out); 
+    }
+    public void readFields(DataInput in) throws IOException {
+      super.readFields(in);
+    }
+  }
+  
+  public static class TestDelegationTokenSecretManager 
+  extends AbstractDelegationTokenSecretManager<TestDelegationTokenIdentifier> {
+
+    public TestDelegationTokenSecretManager(long delegationKeyUpdateInterval,
+                         long delegationTokenMaxLifetime,
+                         long delegationTokenRenewInterval,
+                         long delegationTokenRemoverScanInterval) {
+      super(delegationKeyUpdateInterval, delegationTokenMaxLifetime,
+            delegationTokenRenewInterval, delegationTokenRemoverScanInterval);
+    }
+
+    @Override
+    public TestDelegationTokenIdentifier createIdentifier() {
+      return new TestDelegationTokenIdentifier();
+    }
+    
+    @Override
+    protected byte[] createPassword(TestDelegationTokenIdentifier t) {
+      return super.createPassword(t);
+    }
+  }
+  
+  public static class TokenSelector extends 
+  AbstractDelegationTokenSelector<TestDelegationTokenIdentifier>{
+
+    protected TokenSelector() {
+      super(KIND);
+    }    
+  }
+  
+  @Test
+  public void testSerialization() throws Exception {
+    TestDelegationTokenIdentifier origToken = new 
+                        TestDelegationTokenIdentifier(new Text("alice"), 
+                                          new Text("bob"), 
+                                          new Text("colin"));
+    TestDelegationTokenIdentifier newToken = new TestDelegationTokenIdentifier();
+    origToken.setIssueDate(123);
+    origToken.setMasterKeyId(321);
+    origToken.setMaxDate(314);
+    origToken.setSequenceNumber(12345);
+    
+    // clone origToken into newToken
+    DataInputBuffer inBuf = new DataInputBuffer();
+    DataOutputBuffer outBuf = new DataOutputBuffer();
+    origToken.write(outBuf);
+    inBuf.reset(outBuf.getData(), 0, outBuf.getLength());
+    newToken.readFields(inBuf);
+    
+    // now test the fields
+    assertEquals("alice", newToken.getUser().getUserName());
+    assertEquals(new Text("bob"), newToken.getRenewer());
+    assertEquals("colin", newToken.getUser().getRealUser().getUserName());
+    assertEquals(123, newToken.getIssueDate());
+    assertEquals(321, newToken.getMasterKeyId());
+    assertEquals(314, newToken.getMaxDate());
+    assertEquals(12345, newToken.getSequenceNumber());
+    assertEquals(origToken, newToken);
+  }
+  
+  private Token<TestDelegationTokenIdentifier> generateDelegationToken(
+      TestDelegationTokenSecretManager dtSecretManager,
+      String owner, String renewer) {
+    TestDelegationTokenIdentifier dtId = 
+      new TestDelegationTokenIdentifier(new Text(
+        owner), new Text(renewer), null);
+    return new Token<TestDelegationTokenIdentifier>(dtId, dtSecretManager);
+  }
+  @Test
+  public void testDelegationTokenSecretManager() throws Exception {
+    TestDelegationTokenSecretManager dtSecretManager = 
+      new TestDelegationTokenSecretManager(24*60*60*1000,
+          3*1000,1*1000,3600000);
+    try {
+      dtSecretManager.startThreads();
+      Token<TestDelegationTokenIdentifier> token = generateDelegationToken(
+          dtSecretManager, "SomeUser", "JobTracker");
+      // Fake renewer should not be able to renew
+      Assert.assertFalse(dtSecretManager.renewToken(token, "FakeRenewer"));
+      Assert.assertTrue(dtSecretManager.renewToken(token, "JobTracker"));
+      TestDelegationTokenIdentifier identifier = 
+        new TestDelegationTokenIdentifier();
+      byte[] tokenId = token.getIdentifier();
+      identifier.readFields(new DataInputStream(
+          new ByteArrayInputStream(tokenId)));
+      Assert.assertTrue(null != dtSecretManager.retrievePassword(identifier));
+      Log.info("Sleep to expire the token");
+      Thread.sleep(2000);
+      //Token should be expired
+      try {
+        dtSecretManager.retrievePassword(identifier);
+        //Should not come here
+        Assert.fail("Token should have expired");
+      } catch (InvalidToken e) {
+        //Success
+      }
+      Assert.assertTrue(dtSecretManager.renewToken(token, "JobTracker"));
+      Log.info("Sleep beyond the max lifetime");
+      Thread.sleep(2000);
+      Assert.assertFalse(dtSecretManager.renewToken(token, "JobTracker"));
+    } finally {
+      dtSecretManager.stopThreads();
+    }
+  }
+  @Test 
+  public void testCancelDelegationToken() throws Exception {
+    TestDelegationTokenSecretManager dtSecretManager = 
+      new TestDelegationTokenSecretManager(24*60*60*1000,
+        10*1000,1*1000,3600000);
+    try {
+      dtSecretManager.startThreads();
+      Token<TestDelegationTokenIdentifier> token = generateDelegationToken(
+          dtSecretManager, "SomeUser", "JobTracker");
+      //Fake renewer should not be able to renew
+      Assert.assertFalse(dtSecretManager.cancelToken(token, "FakeCanceller"));
+      Assert.assertTrue(dtSecretManager.cancelToken(token, "JobTracker"));
+      Assert.assertFalse(dtSecretManager.renewToken(token, "JobTracker"));
+    } finally {
+      dtSecretManager.stopThreads();
+    }
+  }
+  @Test
+  public void testRollMasterKey() throws Exception {
+    TestDelegationTokenSecretManager dtSecretManager = 
+      new TestDelegationTokenSecretManager(24*60*60*1000,
+        10*1000,1*1000,3600000);
+    try {
+      dtSecretManager.startThreads();
+      //generate a token and store the password
+      Token<TestDelegationTokenIdentifier> token = generateDelegationToken(
+          dtSecretManager, "SomeUser", "JobTracker");
+      byte[] oldPasswd = token.getPassword();
+      //store the length of the keys list
+      int prevNumKeys = dtSecretManager.getAllKeys().length;
+      
+      dtSecretManager.rollMasterKey();
+      
+      //after rolling, the length of the keys list must increase
+      int currNumKeys = dtSecretManager.getAllKeys().length;
+      Assert.assertEquals((currNumKeys - prevNumKeys) >= 1, true);
+      
+      //after rolling, the token that was generated earlier must
+      //still be valid (retrievePassword will fail if the token
+      //is not valid)
+      ByteArrayInputStream bi = 
+        new ByteArrayInputStream(token.getIdentifier());
+      TestDelegationTokenIdentifier identifier = 
+        dtSecretManager.createIdentifier();
+      identifier.readFields(new DataInputStream(bi));
+      byte[] newPasswd = 
+        dtSecretManager.retrievePassword(identifier);
+      //compare the passwords
+      Assert.assertEquals(oldPasswd, newPasswd);
+    } finally {
+      dtSecretManager.stopThreads();
+    }
+  }
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testDelegationTokenSelector() throws Exception {
+    TestDelegationTokenSecretManager dtSecretManager = 
+      new TestDelegationTokenSecretManager(24*60*60*1000,
+        10*1000,1*1000,3600000);
+    try {
+      dtSecretManager.startThreads();
+      AbstractDelegationTokenSelector ds = 
+      new AbstractDelegationTokenSelector<TestDelegationTokenIdentifier>(KIND);
+      
+      //Creates a collection of tokens
+      Token<TestDelegationTokenIdentifier> token1 = generateDelegationToken(
+          dtSecretManager, "SomeUser1", "JobTracker");
+      token1.setService(new Text("MY-SERVICE1"));
+      
+      Token<TestDelegationTokenIdentifier> token2 = generateDelegationToken(
+          dtSecretManager, "SomeUser2", "JobTracker");
+      token2.setService(new Text("MY-SERVICE2"));
+      
+      List<Token<TestDelegationTokenIdentifier>> tokens =
+        new ArrayList<Token<TestDelegationTokenIdentifier>>();
+      tokens.add(token1);
+      tokens.add(token2);
+      
+      //try to select a token with a given service name (created earlier)
+      Token<TestDelegationTokenIdentifier> t = 
+        ds.selectToken(new Text("MY-SERVICE1"), tokens);
+      Assert.assertEquals(t, token1);
+    } finally {
+      dtSecretManager.stopThreads();
+    }
+  }
+}