HDFS-13193. Various Improvements for BlockTokenSecretManager. Contributed by BELUGA BEHR.

This commit is contained in:
Inigo Goiri 2018-02-27 08:48:03 -08:00
parent 28f644bf25
commit 225d877092
1 changed files with 36 additions and 35 deletions

View File

@ -22,18 +22,17 @@ import java.io.ByteArrayInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdfs.protocol.ExtendedBlock; import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
import org.apache.hadoop.hdfs.protocol.datatransfer.InvalidEncryptionKeyException; import org.apache.hadoop.hdfs.protocol.datatransfer.InvalidEncryptionKeyException;
import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.io.WritableUtils;
@ -41,11 +40,12 @@ import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.SecretManager;
import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.util.Time; import org.apache.hadoop.util.Time;
import org.apache.hadoop.util.Timer;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.apache.hadoop.fs.StorageType; import com.google.common.collect.HashMultiset;
import org.apache.hadoop.util.Timer; import com.google.common.collect.Multiset;
/** /**
* BlockTokenSecretManager can be instantiated in 2 modes, master mode * BlockTokenSecretManager can be instantiated in 2 modes, master mode
@ -153,8 +153,9 @@ public class BlockTokenSecretManager extends
/** Initialize block keys */ /** Initialize block keys */
private synchronized void generateKeys() { private synchronized void generateKeys() {
if (!isMaster) if (!isMaster) {
return; return;
}
/* /*
* Need to set estimated expiry dates for currentKey and nextKey so that if * Need to set estimated expiry dates for currentKey and nextKey so that if
* NN crashes, DN can still expire those keys. NN will stop using the newly * NN crashes, DN can still expire those keys. NN will stop using the newly
@ -179,9 +180,9 @@ public class BlockTokenSecretManager extends
/** Export block keys, only to be used in master mode */ /** Export block keys, only to be used in master mode */
public synchronized ExportedBlockKeys exportKeys() { public synchronized ExportedBlockKeys exportKeys() {
if (!isMaster) if (!isMaster) {
return null; return null;
if (LOG.isDebugEnabled()) }
LOG.debug("Exporting access keys"); LOG.debug("Exporting access keys");
return new ExportedBlockKeys(true, keyUpdateInterval, tokenLifetime, return new ExportedBlockKeys(true, keyUpdateInterval, tokenLifetime,
currentKey, allKeys.values().toArray(new BlockKey[0])); currentKey, allKeys.values().toArray(new BlockKey[0]));
@ -203,18 +204,19 @@ public class BlockTokenSecretManager extends
*/ */
public synchronized void addKeys(ExportedBlockKeys exportedKeys) public synchronized void addKeys(ExportedBlockKeys exportedKeys)
throws IOException { throws IOException {
if (isMaster || exportedKeys == null) if (isMaster || exportedKeys == null) {
return; return;
}
LOG.info("Setting block keys"); LOG.info("Setting block keys");
removeExpiredKeys(); removeExpiredKeys();
this.currentKey = exportedKeys.getCurrentKey(); this.currentKey = exportedKeys.getCurrentKey();
BlockKey[] receivedKeys = exportedKeys.getAllKeys(); BlockKey[] receivedKeys = exportedKeys.getAllKeys();
for (int i = 0; i < receivedKeys.length; i++) { for (int i = 0; i < receivedKeys.length; i++) {
if (receivedKeys[i] == null) if (receivedKeys[i] != null) {
continue;
this.allKeys.put(receivedKeys[i].getKeyId(), receivedKeys[i]); this.allKeys.put(receivedKeys[i].getKeyId(), receivedKeys[i]);
} }
} }
}
/** /**
* Update block keys if update time > update interval. * Update block keys if update time > update interval.
@ -231,8 +233,9 @@ public class BlockTokenSecretManager extends
* Update block keys, only to be used in master mode * Update block keys, only to be used in master mode
*/ */
synchronized boolean updateKeys() throws IOException { synchronized boolean updateKeys() throws IOException {
if (!isMaster) if (!isMaster) {
return false; return false;
}
LOG.info("Updating block keys"); LOG.info("Updating block keys");
removeExpiredKeys(); removeExpiredKeys();
@ -283,10 +286,10 @@ public class BlockTokenSecretManager extends
ExtendedBlock block, BlockTokenIdentifier.AccessMode mode, ExtendedBlock block, BlockTokenIdentifier.AccessMode mode,
StorageType[] storageTypes, String[] storageIds) throws InvalidToken { StorageType[] storageTypes, String[] storageIds) throws InvalidToken {
checkAccess(id, userId, block, mode); checkAccess(id, userId, block, mode);
if (storageTypes != null && storageTypes.length > 0) { if (ArrayUtils.isNotEmpty(storageTypes)) {
checkAccess(id.getStorageTypes(), storageTypes, "StorageTypes"); checkAccess(id.getStorageTypes(), storageTypes, "StorageTypes");
} }
if (storageIds != null && storageIds.length > 0) { if (ArrayUtils.isNotEmpty(storageIds)) {
checkAccess(id.getStorageIds(), storageIds, "StorageIDs"); checkAccess(id.getStorageIds(), storageIds, "StorageIDs");
} }
} }
@ -296,26 +299,26 @@ public class BlockTokenSecretManager extends
throws InvalidToken { throws InvalidToken {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Checking access for user=" + userId + ", block=" + block LOG.debug("Checking access for user=" + userId + ", block=" + block
+ ", access mode=" + mode + " using " + id.toString()); + ", access mode=" + mode + " using " + id);
} }
if (userId != null && !userId.equals(id.getUserId())) { if (userId != null && !userId.equals(id.getUserId())) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " doesn't belong to user " + userId); + " doesn't belong to user " + userId);
} }
if (!id.getBlockPoolId().equals(block.getBlockPoolId())) { if (!id.getBlockPoolId().equals(block.getBlockPoolId())) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " doesn't apply to block " + block); + " doesn't apply to block " + block);
} }
if (id.getBlockId() != block.getBlockId()) { if (id.getBlockId() != block.getBlockId()) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " doesn't apply to block " + block); + " doesn't apply to block " + block);
} }
if (isExpired(id.getExpiryDate())) { if (isExpired(id.getExpiryDate())) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " is expired."); + " is expired.");
} }
if (!id.getAccessModes().contains(mode)) { if (!id.getAccessModes().contains(mode)) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " doesn't have " + mode + " permission"); + " doesn't have " + mode + " permission");
} }
} }
@ -329,26 +332,23 @@ public class BlockTokenSecretManager extends
*/ */
public static <T> void checkAccess(T[] candidates, T[] requested, String msg) public static <T> void checkAccess(T[] candidates, T[] requested, String msg)
throws InvalidToken { throws InvalidToken {
if (requested.length == 0) { if (ArrayUtils.isEmpty(requested)) {
throw new InvalidToken("The request has no " + msg + ". " throw new InvalidToken("The request has no " + msg + ". "
+ "This is probably a configuration error."); + "This is probably a configuration error.");
} }
if (candidates.length == 0) { if (ArrayUtils.isEmpty(candidates)) {
return; return;
} }
List unseenCandidates = new ArrayList<T>(); Multiset<T> c = HashMultiset.create(Arrays.asList(candidates));
unseenCandidates.addAll(Arrays.asList(candidates));
for (T req : requested) { for (T req : requested) {
final int index = unseenCandidates.indexOf(req); if (!c.remove(req)) {
if (index == -1) {
throw new InvalidToken("Block token with " + msg + " " throw new InvalidToken("Block token with " + msg + " "
+ Arrays.toString(candidates) + Arrays.toString(candidates)
+ " not valid for access with " + msg + " " + " not valid for access with " + msg + " "
+ Arrays.toString(requested)); + Arrays.toString(requested));
} }
Collections.swap(unseenCandidates, index, unseenCandidates.size()-1);
unseenCandidates.remove(unseenCandidates.size()-1);
} }
} }
@ -367,7 +367,7 @@ public class BlockTokenSecretManager extends
} }
checkAccess(id, userId, block, mode, storageTypes, storageIds); checkAccess(id, userId, block, mode, storageTypes, storageIds);
if (!Arrays.equals(retrievePassword(id), token.getPassword())) { if (!Arrays.equals(retrievePassword(id), token.getPassword())) {
throw new InvalidToken("Block token with " + id.toString() throw new InvalidToken("Block token with " + id
+ " doesn't have the correct token password"); + " doesn't have the correct token password");
} }
} }
@ -416,12 +416,13 @@ public class BlockTokenSecretManager extends
synchronized (this) { synchronized (this) {
key = currentKey; key = currentKey;
} }
if (key == null) if (key == null) {
throw new IllegalStateException("currentKey hasn't been initialized."); throw new IllegalStateException("currentKey hasn't been initialized.");
}
identifier.setExpiryDate(timer.now() + tokenLifetime); identifier.setExpiryDate(timer.now() + tokenLifetime);
identifier.setKeyId(key.getKeyId()); identifier.setKeyId(key.getKeyId());
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Generating block token for " + identifier.toString()); LOG.debug("Generating block token for " + identifier);
} }
return createPassword(identifier.getBytes(), key.getKey()); return createPassword(identifier.getBytes(), key.getKey());
} }
@ -438,7 +439,7 @@ public class BlockTokenSecretManager extends
public byte[] retrievePassword(BlockTokenIdentifier identifier) public byte[] retrievePassword(BlockTokenIdentifier identifier)
throws InvalidToken { throws InvalidToken {
if (isExpired(identifier.getExpiryDate())) { if (isExpired(identifier.getExpiryDate())) {
throw new InvalidToken("Block token with " + identifier.toString() throw new InvalidToken("Block token with " + identifier
+ " is expired."); + " is expired.");
} }
BlockKey key = null; BlockKey key = null;
@ -447,7 +448,7 @@ public class BlockTokenSecretManager extends
} }
if (key == null) { if (key == null) {
throw new InvalidToken("Can't re-compute password for " throw new InvalidToken("Can't re-compute password for "
+ identifier.toString() + ", since the required block key (keyID=" + identifier + ", since the required block key (keyID="
+ identifier.getKeyId() + ") doesn't exist."); + identifier.getKeyId() + ") doesn't exist.");
} }
return createPassword(identifier.getBytes(), key.getKey()); return createPassword(identifier.getBytes(), key.getKey());