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
This commit is contained in:
parent
ae93ba7501
commit
fe479755c3
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue