瀏覽代碼

HADOOP-6415. Adds a common token interface for both job token and delegation token. Contributed by Kan Zhang.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@892113 13f79535-47bb-0310-9956-ffa450edef68
Devaraj Das 15 年之前
父節點
當前提交
fe479755c3

+ 3 - 0
CHANGES.txt

@@ -26,6 +26,9 @@ Trunk (unreleased changes)
     threads. This can be used to delete files in the Distributed
     Cache. (Zheng Shao via dhruba)
 
+    HADOOP-6415. Adds a common token interface for both job token and 
+    delegation token. (Kan Zhang via ddas)
+
   IMPROVEMENTS
 
     HADOOP-6283. Improve the exception messages thrown by

+ 142 - 0
src/java/org/apache/hadoop/security/token/SecretManager.java

@@ -0,0 +1,142 @@
+/**
+ * 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;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ * The server-side secret manager for each token type.
+ * @param <T> The type of the token identifier
+ */
+public abstract class SecretManager<T extends TokenIdentifier> {
+  /**
+   * The token was invalid and the message explains why.
+   */
+  @SuppressWarnings("serial")
+  public static class InvalidToken extends IOException {
+    public InvalidToken(String msg) { 
+      super(msg);
+    }
+  }
+  
+  /**
+   * Create the password for the given identifier.
+   * @param identifier the identifier to use
+   * @return the new password
+   */
+  public abstract byte[] createPassword(T identifier);
+  
+  /**
+   * Retrieve the password for the given token identifier. Should check the date
+   * or registry to make sure the token hasn't expired or been revoked. Returns 
+   * the relevant password.
+   * @param identifier the identifier to validate
+   * @return the password to use
+   * @throws InvalidToken the token was invalid
+   */
+  public abstract byte[] retrievePassword(T identifier) throws InvalidToken;
+  
+  /**
+   * The name of the hashing algorithm.
+   */
+  private static final String DEFAULT_HMAC_ALGORITHM = "HmacSHA1";
+
+  /**
+   * The length of the random keys to use.
+   */
+  private static final int KEY_LENGTH = 20;
+
+  /**
+   * A thread local store for the Macs.
+   */
+  private static final ThreadLocal<Mac> threadLocalMac =
+    new ThreadLocal<Mac>(){
+    @Override
+    protected Mac initialValue() {
+      try {
+        return Mac.getInstance(DEFAULT_HMAC_ALGORITHM);
+      } catch (NoSuchAlgorithmException nsa) {
+        throw new IllegalArgumentException("Can't find " + DEFAULT_HMAC_ALGORITHM +
+                                           " algorithm.");
+      }
+    }
+  };
+
+  /**
+   * Key generator to use.
+   */
+  private final KeyGenerator keyGen;
+  {
+    try {
+      keyGen = KeyGenerator.getInstance(DEFAULT_HMAC_ALGORITHM);
+      keyGen.init(KEY_LENGTH);
+    } catch (NoSuchAlgorithmException nsa) {
+      throw new IllegalArgumentException("Can't find " + DEFAULT_HMAC_ALGORITHM +
+      " algorithm.");
+    }
+  }
+
+  /**
+   * Generate a new random secret key.
+   * @return the new key
+   */
+  protected SecretKey generateSecret() {
+    SecretKey key;
+    synchronized (keyGen) {
+      key = keyGen.generateKey();
+    }
+    return key;
+  }
+
+  /**
+   * Compute HMAC of the identifier using the secret key and return the 
+   * output as password
+   * @param identifier the bytes of the identifier
+   * @param key the secret key
+   * @return the bytes of the generated password
+   */
+  protected static byte[] createPassword(byte[] identifier, 
+                                         SecretKey key) {
+    Mac mac = threadLocalMac.get();
+    try {
+      mac.init(key);
+    } catch (InvalidKeyException ike) {
+      throw new IllegalArgumentException("Invalid key to HMAC computation", 
+                                         ike);
+    }
+    return mac.doFinal(identifier);
+  }
+  
+  /**
+   * Convert the byte[] to a secret key
+   * @param key the byte[] to create a secret key from
+   * @return the secret key
+   */
+  protected static SecretKey createSecretKey(byte[] key) {
+    return new SecretKeySpec(key, DEFAULT_HMAC_ALGORITHM);
+  }
+}

+ 126 - 0
src/java/org/apache/hadoop/security/token/Token.java

@@ -0,0 +1,126 @@
+/**
+ * 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;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.io.WritableUtils;
+
+/**
+ * The client-side form of the token.
+ */
+public class Token<T extends TokenIdentifier> implements Writable {
+  private byte[] identifier;
+  private byte[] password;
+  private Text kind;
+  private Text service;
+  
+  /**
+   * Construct a token given a token identifier and a secret manager for the
+   * type of the token identifier.
+   * @param id the token identifier
+   * @param mgr the secret manager
+   */
+  public Token(T id, SecretManager<T> mgr) {
+    identifier = id.getBytes();
+    password = mgr.createPassword(id);
+    kind = id.getKind();
+    service = new Text();
+  }
+  
+  /**
+   * Default constructor
+   */
+  public Token() {
+    identifier = new byte[0];
+    password = new byte[0];
+    kind = new Text();
+    service = new Text();
+  }
+
+  /**
+   * Get the token identifier
+   * @return the token identifier
+   */
+  public byte[] getIdentifier() {
+    return identifier;
+  }
+  
+  /**
+   * Get the token password/secret
+   * @return the token password/secret
+   */
+  public byte[] getPassword() {
+    return password;
+  }
+  
+  /**
+   * Get the token kind
+   * @return the kind of the token
+   */
+  public Text getKind() {
+    return kind;
+  }
+
+  /**
+   * Get the service on which the token is supposed to be used
+   * @return the service name
+   */
+  public Text getService() {
+    return service;
+  }
+  
+  /**
+   * Set the service on which the token is supposed to be used
+   * @param newService the service name
+   */
+  public void setService(Text newService) {
+    service = newService;
+  }
+
+  /** {@inheritDoc} */
+  public void readFields(DataInput in) throws IOException {
+    int len = WritableUtils.readVInt(in);
+    if (identifier == null || identifier.length != len) {
+      identifier = new byte[len];
+    }
+    in.readFully(identifier);
+    len = WritableUtils.readVInt(in);
+    if (password == null || password.length != len) {
+      password = new byte[len];
+    }
+    in.readFully(password);
+    kind.readFields(in);
+    service.readFields(in);
+  }
+
+  /** {@inheritDoc} */
+  public void write(DataOutput out) throws IOException {
+    WritableUtils.writeVInt(out, identifier.length);
+    out.write(identifier);
+    WritableUtils.writeVInt(out, password.length);
+    out.write(password);
+    kind.write(out);
+    service.write(out);
+  }
+}

+ 52 - 0
src/java/org/apache/hadoop/security/token/TokenIdentifier.java

@@ -0,0 +1,52 @@
+/**
+ * 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;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.hadoop.io.DataOutputBuffer;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.Writable;
+
+/**
+ * An identifier that identifies a token, may contain public information 
+ * about a token, including its kind (or type).
+ */
+public abstract class TokenIdentifier implements Writable {
+  /**
+   * Get the token kind
+   * @return the kind of the token
+   */
+  public abstract Text getKind();
+
+  /**
+   * Get the bytes for the token identifier
+   * @return the bytes of the identifier
+   */
+  public byte[] getBytes() {
+    DataOutputBuffer buf = new DataOutputBuffer(4096);
+    try {
+      this.write(buf);
+    } catch (IOException ie) {
+      throw new RuntimeException("i/o error in getBytes", ie);
+    }
+    return Arrays.copyOf(buf.getData(), buf.getLength());
+  }
+}

+ 61 - 0
src/test/core/org/apache/hadoop/security/token/TestToken.java

@@ -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;
+
+import java.io.*;
+import java.util.Arrays;
+
+import org.apache.hadoop.io.*;
+
+import junit.framework.TestCase;
+
+/** Unit tests for Token */
+public class TestToken extends TestCase {
+
+  static boolean isEqual(Object a, Object b) {
+    return a == null ? b == null : a.equals(b);
+  }
+
+  static boolean checkEqual(Token<TokenIdentifier> a, Token<TokenIdentifier> b) {
+    return Arrays.equals(a.getIdentifier(), b.getIdentifier())
+        && Arrays.equals(a.getPassword(), b.getPassword())
+        && isEqual(a.getKind(), b.getKind())
+        && isEqual(a.getService(), b.getService());
+  }
+
+  /**
+   * Test token serialization
+   */
+  public void testTokenSerialization() throws IOException {
+    // Get a token
+    Token<TokenIdentifier> sourceToken = new Token<TokenIdentifier>();
+    sourceToken.setService(new Text("service"));
+
+    // Write it to an output buffer
+    DataOutputBuffer out = new DataOutputBuffer();
+    sourceToken.write(out);
+
+    // Read the token back
+    DataInputBuffer in = new DataInputBuffer();
+    in.reset(out.getData(), out.getLength());
+    Token<TokenIdentifier> destToken = new Token<TokenIdentifier>();
+    destToken.readFields(in);
+    assertTrue(checkEqual(sourceToken, destToken));
+  }
+}