|
@@ -19,22 +19,28 @@
|
|
package org.apache.hadoop.yarn.server.resourcemanager.security;
|
|
package org.apache.hadoop.yarn.server.resourcemanager.security;
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
-import java.util.HashMap;
|
|
|
|
-import java.util.Map;
|
|
|
|
|
|
+import java.security.SecureRandom;
|
|
|
|
+import java.util.HashSet;
|
|
|
|
+import java.util.Set;
|
|
import java.util.Timer;
|
|
import java.util.Timer;
|
|
import java.util.TimerTask;
|
|
import java.util.TimerTask;
|
|
-
|
|
|
|
-import javax.crypto.SecretKey;
|
|
|
|
|
|
+import java.util.concurrent.locks.Lock;
|
|
|
|
+import java.util.concurrent.locks.ReadWriteLock;
|
|
|
|
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
|
|
|
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.Private;
|
|
import org.apache.hadoop.classification.InterfaceAudience.Private;
|
|
import org.apache.hadoop.conf.Configuration;
|
|
import org.apache.hadoop.conf.Configuration;
|
|
|
|
+import org.apache.hadoop.io.Text;
|
|
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.yarn.api.records.ApplicationAttemptId;
|
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
|
import org.apache.hadoop.yarn.security.AMRMTokenIdentifier;
|
|
import org.apache.hadoop.yarn.security.AMRMTokenIdentifier;
|
|
|
|
+import org.apache.hadoop.yarn.server.security.MasterKeyData;
|
|
|
|
+
|
|
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
|
|
|
|
/**
|
|
/**
|
|
* AMRM-tokens are per ApplicationAttempt. If users redistribute their
|
|
* AMRM-tokens are per ApplicationAttempt. If users redistribute their
|
|
@@ -49,40 +55,66 @@ public class AMRMTokenSecretManager extends
|
|
private static final Log LOG = LogFactory
|
|
private static final Log LOG = LogFactory
|
|
.getLog(AMRMTokenSecretManager.class);
|
|
.getLog(AMRMTokenSecretManager.class);
|
|
|
|
|
|
- private SecretKey masterKey;
|
|
|
|
|
|
+ private int serialNo = new SecureRandom().nextInt();
|
|
|
|
+ private MasterKeyData nextMasterKey;
|
|
|
|
+ private MasterKeyData currentMasterKey;
|
|
|
|
+
|
|
|
|
+ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
|
|
|
+ private final Lock readLock = readWriteLock.readLock();
|
|
|
|
+ private final Lock writeLock = readWriteLock.writeLock();
|
|
|
|
+
|
|
private final Timer timer;
|
|
private final Timer timer;
|
|
private final long rollingInterval;
|
|
private final long rollingInterval;
|
|
|
|
+ private final long activationDelay;
|
|
|
|
|
|
- private final Map<ApplicationAttemptId, byte[]> passwords =
|
|
|
|
- new HashMap<ApplicationAttemptId, byte[]>();
|
|
|
|
|
|
+ private final Set<ApplicationAttemptId> appAttemptSet =
|
|
|
|
+ new HashSet<ApplicationAttemptId>();
|
|
|
|
|
|
/**
|
|
/**
|
|
* Create an {@link AMRMTokenSecretManager}
|
|
* Create an {@link AMRMTokenSecretManager}
|
|
*/
|
|
*/
|
|
public AMRMTokenSecretManager(Configuration conf) {
|
|
public AMRMTokenSecretManager(Configuration conf) {
|
|
- rollMasterKey();
|
|
|
|
this.timer = new Timer();
|
|
this.timer = new Timer();
|
|
this.rollingInterval =
|
|
this.rollingInterval =
|
|
conf
|
|
conf
|
|
.getLong(
|
|
.getLong(
|
|
YarnConfiguration.RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS,
|
|
YarnConfiguration.RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS,
|
|
YarnConfiguration.DEFAULT_RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS) * 1000;
|
|
YarnConfiguration.DEFAULT_RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS) * 1000;
|
|
|
|
+ // Adding delay = 1.5 * expiry interval makes sure that all active AMs get
|
|
|
|
+ // the updated shared-key.
|
|
|
|
+ this.activationDelay =
|
|
|
|
+ (long) (conf.getLong(YarnConfiguration.RM_AM_EXPIRY_INTERVAL_MS,
|
|
|
|
+ YarnConfiguration.DEFAULT_RM_AM_EXPIRY_INTERVAL_MS) * 1.5);
|
|
|
|
+ LOG.info("AMRMTokenKeyRollingInterval: " + this.rollingInterval
|
|
|
|
+ + "ms and AMRMTokenKeyActivationDelay: " + this.activationDelay + " ms");
|
|
|
|
+ if (rollingInterval <= activationDelay * 2) {
|
|
|
|
+ throw new IllegalArgumentException(
|
|
|
|
+ YarnConfiguration.RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS
|
|
|
|
+ + " should be more than 2 X "
|
|
|
|
+ + YarnConfiguration.RM_AM_EXPIRY_INTERVAL_MS);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
public void start() {
|
|
public void start() {
|
|
- this.timer.scheduleAtFixedRate(new MasterKeyRoller(), 0, rollingInterval);
|
|
|
|
|
|
+ if (this.currentMasterKey == null) {
|
|
|
|
+ this.currentMasterKey = createNewMasterKey();
|
|
|
|
+ }
|
|
|
|
+ this.timer.scheduleAtFixedRate(new MasterKeyRoller(), rollingInterval,
|
|
|
|
+ rollingInterval);
|
|
}
|
|
}
|
|
|
|
|
|
public void stop() {
|
|
public void stop() {
|
|
this.timer.cancel();
|
|
this.timer.cancel();
|
|
}
|
|
}
|
|
|
|
|
|
- public synchronized void applicationMasterFinished(
|
|
|
|
- ApplicationAttemptId appAttemptId) {
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
- LOG.debug("Application finished, removing password for " + appAttemptId);
|
|
|
|
|
|
+ public void applicationMasterFinished(ApplicationAttemptId appAttemptId) {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ LOG.info("Application finished, removing password for " + appAttemptId);
|
|
|
|
+ this.appAttemptSet.remove(appAttemptId);
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
}
|
|
}
|
|
- this.passwords.remove(appAttemptId);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
private class MasterKeyRoller extends TimerTask {
|
|
private class MasterKeyRoller extends TimerTask {
|
|
@@ -93,49 +125,89 @@ public class AMRMTokenSecretManager extends
|
|
}
|
|
}
|
|
|
|
|
|
@Private
|
|
@Private
|
|
- public synchronized void setMasterKey(SecretKey masterKey) {
|
|
|
|
- this.masterKey = masterKey;
|
|
|
|
|
|
+ void rollMasterKey() {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ LOG.info("Rolling master-key for amrm-tokens");
|
|
|
|
+ this.nextMasterKey = createNewMasterKey();
|
|
|
|
+ this.timer.schedule(new NextKeyActivator(), this.activationDelay);
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- @Private
|
|
|
|
- public synchronized SecretKey getMasterKey() {
|
|
|
|
- return this.masterKey;
|
|
|
|
|
|
+ private class NextKeyActivator extends TimerTask {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ activateNextMasterKey();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void activateNextMasterKey() {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ LOG.info("Activating next master key with id: "
|
|
|
|
+ + this.nextMasterKey.getMasterKey().getKeyId());
|
|
|
|
+ this.currentMasterKey = this.nextMasterKey;
|
|
|
|
+ this.nextMasterKey = null;
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
@Private
|
|
@Private
|
|
- synchronized void rollMasterKey() {
|
|
|
|
- LOG.info("Rolling master-key for amrm-tokens");
|
|
|
|
- this.masterKey = generateSecret();
|
|
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ public MasterKeyData createNewMasterKey() {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ return new MasterKeyData(serialNo++, generateSecret());
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Create a password for a given {@link AMRMTokenIdentifier}. Used to
|
|
|
|
- * send to the AppicationAttempt which can give it back during authentication.
|
|
|
|
- */
|
|
|
|
- @Override
|
|
|
|
- public synchronized byte[] createPassword(
|
|
|
|
- AMRMTokenIdentifier identifier) {
|
|
|
|
- ApplicationAttemptId applicationAttemptId =
|
|
|
|
- identifier.getApplicationAttemptId();
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
- LOG.debug("Creating password for " + applicationAttemptId);
|
|
|
|
|
|
+ public Token<AMRMTokenIdentifier> createAndGetAMRMToken(
|
|
|
|
+ ApplicationAttemptId appAttemptId) {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ LOG.info("Create AMRMToken for ApplicationAttempt: " + appAttemptId);
|
|
|
|
+ AMRMTokenIdentifier identifier =
|
|
|
|
+ new AMRMTokenIdentifier(appAttemptId, getMasterKey().getMasterKey()
|
|
|
|
+ .getKeyId());
|
|
|
|
+ byte[] password = this.createPassword(identifier);
|
|
|
|
+ appAttemptSet.add(appAttemptId);
|
|
|
|
+ return new Token<AMRMTokenIdentifier>(identifier.getBytes(), password,
|
|
|
|
+ identifier.getKind(), new Text());
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If nextMasterKey is not Null, then return nextMasterKey
|
|
|
|
+ // otherwise return currentMasterKey
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ public MasterKeyData getMasterKey() {
|
|
|
|
+ this.readLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ return nextMasterKey == null ? currentMasterKey : nextMasterKey;
|
|
|
|
+ } finally {
|
|
|
|
+ this.readLock.unlock();
|
|
}
|
|
}
|
|
- byte[] password = createPassword(identifier.getBytes(), masterKey);
|
|
|
|
- this.passwords.put(applicationAttemptId, password);
|
|
|
|
- return password;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Populate persisted password of AMRMToken back to AMRMTokenSecretManager.
|
|
* Populate persisted password of AMRMToken back to AMRMTokenSecretManager.
|
|
*/
|
|
*/
|
|
- public synchronized void
|
|
|
|
- addPersistedPassword(Token<AMRMTokenIdentifier> token) throws IOException {
|
|
|
|
- AMRMTokenIdentifier identifier = token.decodeIdentifier();
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
|
|
+ public void addPersistedPassword(Token<AMRMTokenIdentifier> token)
|
|
|
|
+ throws IOException {
|
|
|
|
+ this.writeLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ AMRMTokenIdentifier identifier = token.decodeIdentifier();
|
|
LOG.debug("Adding password for " + identifier.getApplicationAttemptId());
|
|
LOG.debug("Adding password for " + identifier.getApplicationAttemptId());
|
|
|
|
+ appAttemptSet.add(identifier.getApplicationAttemptId());
|
|
|
|
+ } finally {
|
|
|
|
+ this.writeLock.unlock();
|
|
}
|
|
}
|
|
- this.passwords.put(identifier.getApplicationAttemptId(),
|
|
|
|
- token.getPassword());
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -143,19 +215,35 @@ public class AMRMTokenSecretManager extends
|
|
* Used by RPC layer to validate a remote {@link AMRMTokenIdentifier}.
|
|
* Used by RPC layer to validate a remote {@link AMRMTokenIdentifier}.
|
|
*/
|
|
*/
|
|
@Override
|
|
@Override
|
|
- public synchronized byte[] retrievePassword(
|
|
|
|
- AMRMTokenIdentifier identifier) throws InvalidToken {
|
|
|
|
- ApplicationAttemptId applicationAttemptId =
|
|
|
|
- identifier.getApplicationAttemptId();
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
- LOG.debug("Trying to retrieve password for " + applicationAttemptId);
|
|
|
|
|
|
+ public byte[] retrievePassword(AMRMTokenIdentifier identifier)
|
|
|
|
+ throws InvalidToken {
|
|
|
|
+ this.readLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ ApplicationAttemptId applicationAttemptId =
|
|
|
|
+ identifier.getApplicationAttemptId();
|
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
|
+ LOG.debug("Trying to retrieve password for " + applicationAttemptId);
|
|
|
|
+ }
|
|
|
|
+ if (!appAttemptSet.contains(applicationAttemptId)) {
|
|
|
|
+ throw new InvalidToken("Password not found for ApplicationAttempt "
|
|
|
|
+ + applicationAttemptId);
|
|
|
|
+ }
|
|
|
|
+ if (identifier.getKeyId() == this.currentMasterKey.getMasterKey()
|
|
|
|
+ .getKeyId()) {
|
|
|
|
+ return createPassword(identifier.getBytes(),
|
|
|
|
+ this.currentMasterKey.getSecretKey());
|
|
|
|
+ } else if (nextMasterKey != null
|
|
|
|
+ && identifier.getKeyId() == this.nextMasterKey.getMasterKey()
|
|
|
|
+ .getKeyId()) {
|
|
|
|
+ return createPassword(identifier.getBytes(),
|
|
|
|
+ this.nextMasterKey.getSecretKey());
|
|
|
|
+ }
|
|
|
|
+ throw new InvalidToken("Given AMRMToken for application : "
|
|
|
|
+ + applicationAttemptId.toString()
|
|
|
|
+ + " seems to have been generated illegally.");
|
|
|
|
+ } finally {
|
|
|
|
+ this.readLock.unlock();
|
|
}
|
|
}
|
|
- byte[] password = this.passwords.get(applicationAttemptId);
|
|
|
|
- if (password == null) {
|
|
|
|
- throw new InvalidToken("Password not found for ApplicationAttempt "
|
|
|
|
- + applicationAttemptId);
|
|
|
|
- }
|
|
|
|
- return password;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -167,4 +255,40 @@ public class AMRMTokenSecretManager extends
|
|
return new AMRMTokenIdentifier();
|
|
return new AMRMTokenIdentifier();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ @Private
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ public MasterKeyData getCurrnetMasterKeyData() {
|
|
|
|
+ this.readLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ return this.currentMasterKey;
|
|
|
|
+ } finally {
|
|
|
|
+ this.readLock.unlock();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Private
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ public MasterKeyData getNextMasterKeyData() {
|
|
|
|
+ this.readLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ return this.nextMasterKey;
|
|
|
|
+ } finally {
|
|
|
|
+ this.readLock.unlock();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ @Private
|
|
|
|
+ protected byte[] createPassword(AMRMTokenIdentifier identifier) {
|
|
|
|
+ this.readLock.lock();
|
|
|
|
+ try {
|
|
|
|
+ ApplicationAttemptId applicationAttemptId =
|
|
|
|
+ identifier.getApplicationAttemptId();
|
|
|
|
+ LOG.info("Creating password for " + applicationAttemptId);
|
|
|
|
+ return createPassword(identifier.getBytes(), getMasterKey()
|
|
|
|
+ .getSecretKey());
|
|
|
|
+ } finally {
|
|
|
|
+ this.readLock.unlock();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|