blob: 0956e77f7fa37753f24c7bd40d519e59bfee58d8 [file] [log] [blame]
/**
* 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.yarn.server.nodemanager.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.Container;
import org.apache.hadoop.yarn.api.records.NMToken;
import org.apache.hadoop.yarn.api.records.NodeId;
import org.apache.hadoop.yarn.api.records.Token;
import org.apache.hadoop.yarn.security.NMTokenIdentifier;
import org.apache.hadoop.yarn.server.api.records.MasterKey;
import org.apache.hadoop.yarn.server.nodemanager.recovery.NMNullStateStoreService;
import org.apache.hadoop.yarn.server.nodemanager.recovery.NMStateStoreService;
import org.apache.hadoop.yarn.server.nodemanager.recovery.NMStateStoreService.RecoveredNMTokensState;
import org.apache.hadoop.yarn.server.security.BaseNMTokenSecretManager;
import org.apache.hadoop.yarn.server.security.MasterKeyData;
import com.google.common.annotations.VisibleForTesting;
public class NMTokenSecretManagerInNM extends BaseNMTokenSecretManager {
private static final Logger LOG =
LoggerFactory.getLogger(NMTokenSecretManagerInNM.class);
private MasterKeyData previousMasterKey;
private final Map<ApplicationAttemptId, MasterKeyData> oldMasterKeys;
private final Map<ApplicationId, List<ApplicationAttemptId>> appToAppAttemptMap;
private final NMStateStoreService stateStore;
private NodeId nodeId;
public NMTokenSecretManagerInNM() {
this(new NMNullStateStoreService());
}
public NMTokenSecretManagerInNM(NMStateStoreService stateStore) {
this.oldMasterKeys =
new HashMap<ApplicationAttemptId, MasterKeyData>();
appToAppAttemptMap =
new HashMap<ApplicationId, List<ApplicationAttemptId>>();
this.stateStore = stateStore;
}
public synchronized void recover()
throws IOException {
RecoveredNMTokensState state = stateStore.loadNMTokensState();
MasterKey key = state.getCurrentMasterKey();
if (key != null) {
super.currentMasterKey =
new MasterKeyData(key, createSecretKey(key.getBytes().array()));
}
key = state.getPreviousMasterKey();
if (key != null) {
previousMasterKey =
new MasterKeyData(key, createSecretKey(key.getBytes().array()));
}
// restore the serial number from the current master key
if (super.currentMasterKey != null) {
super.serialNo = super.currentMasterKey.getMasterKey().getKeyId() + 1;
}
for (Map.Entry<ApplicationAttemptId, MasterKey> entry :
state.getApplicationMasterKeys().entrySet()) {
key = entry.getValue();
oldMasterKeys.put(entry.getKey(),
new MasterKeyData(key, createSecretKey(key.getBytes().array())));
}
// reconstruct app to app attempts map
appToAppAttemptMap.clear();
for (ApplicationAttemptId attempt : oldMasterKeys.keySet()) {
ApplicationId app = attempt.getApplicationId();
List<ApplicationAttemptId> attempts = appToAppAttemptMap.get(app);
if (attempts == null) {
attempts = new ArrayList<ApplicationAttemptId>();
appToAppAttemptMap.put(app, attempts);
}
attempts.add(attempt);
}
}
private void updateCurrentMasterKey(MasterKeyData key) {
super.currentMasterKey = key;
try {
stateStore.storeNMTokenCurrentMasterKey(key.getMasterKey());
} catch (IOException e) {
LOG.error("Unable to update current master key in state store", e);
}
}
private void updatePreviousMasterKey(MasterKeyData key) {
previousMasterKey = key;
try {
stateStore.storeNMTokenPreviousMasterKey(key.getMasterKey());
} catch (IOException e) {
LOG.error("Unable to update previous master key in state store", e);
}
}
/**
* Used by NodeManagers to create a token-secret-manager with the key
* obtained from the RM. This can happen during registration or when the RM
* rolls the master-key and signal the NM.
*/
@Private
public synchronized void setMasterKey(MasterKey masterKey) {
// Update keys only if the key has changed.
if (super.currentMasterKey == null || super.currentMasterKey.getMasterKey()
.getKeyId() != masterKey.getKeyId()) {
LOG.info("Rolling master-key for container-tokens, got key with id "
+ masterKey.getKeyId());
if (super.currentMasterKey != null) {
updatePreviousMasterKey(super.currentMasterKey);
}
updateCurrentMasterKey(new MasterKeyData(masterKey,
createSecretKey(masterKey.getBytes().array())));
}
}
/**
* This method will be used to verify NMTokens generated by different master
* keys.
*/
@Override
public synchronized byte[] retrievePassword(NMTokenIdentifier identifier)
throws InvalidToken {
int keyId = identifier.getKeyId();
ApplicationAttemptId appAttemptId = identifier.getApplicationAttemptId();
/*
* MasterKey used for retrieving password will be as follows. 1) By default
* older saved master key will be used. 2) If identifier's master key id
* matches that of previous master key id then previous key will be used. 3)
* If identifier's master key id matches that of current master key id then
* current key will be used.
*/
MasterKeyData oldMasterKey = oldMasterKeys.get(appAttemptId);
MasterKeyData masterKeyToUse = oldMasterKey;
if (previousMasterKey != null
&& keyId == previousMasterKey.getMasterKey().getKeyId()) {
masterKeyToUse = previousMasterKey;
} else if (keyId == currentMasterKey.getMasterKey().getKeyId()) {
masterKeyToUse = currentMasterKey;
}
if (nodeId != null && !identifier.getNodeId().equals(nodeId)) {
throw new InvalidToken("Given NMToken for application : "
+ appAttemptId.toString() + " is not valid for current node manager."
+ "expected : " + nodeId.toString() + " found : "
+ identifier.getNodeId().toString());
}
if (masterKeyToUse != null) {
byte[] password = retrivePasswordInternal(identifier, masterKeyToUse);
LOG.debug("NMToken password retrieved successfully!!");
return password;
}
throw new InvalidToken("Given NMToken for application : "
+ appAttemptId.toString() + " seems to have been generated illegally.");
}
public synchronized void appFinished(ApplicationId appId) {
List<ApplicationAttemptId> appAttemptList = appToAppAttemptMap.get(appId);
if (appAttemptList != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Removing application attempts NMToken keys for application "
+ appId);
}
for (ApplicationAttemptId appAttemptId : appAttemptList) {
removeAppAttemptKey(appAttemptId);
}
appToAppAttemptMap.remove(appId);
} else {
LOG.error("No application Attempt for application : " + appId
+ " started on this NM.");
}
}
/**
* This will be called by startContainer. It will add the master key into
* the cache used for starting this container. This should be called before
* validating the startContainer request.
*/
public synchronized void appAttemptStartContainer(
NMTokenIdentifier identifier)
throws org.apache.hadoop.security.token.SecretManager.InvalidToken {
ApplicationAttemptId appAttemptId = identifier.getApplicationAttemptId();
if (!appToAppAttemptMap.containsKey(appAttemptId.getApplicationId())) {
// First application attempt for the given application
appToAppAttemptMap.put(appAttemptId.getApplicationId(),
new ArrayList<ApplicationAttemptId>());
}
MasterKeyData oldKey = oldMasterKeys.get(appAttemptId);
if (oldKey == null) {
// This is a new application attempt.
appToAppAttemptMap.get(appAttemptId.getApplicationId()).add(appAttemptId);
}
if (oldKey == null
|| oldKey.getMasterKey().getKeyId() != identifier.getKeyId()) {
// Update key only if it is modified.
if (LOG.isDebugEnabled()) {
LOG.debug("NMToken key updated for application attempt : "
+ identifier.getApplicationAttemptId().toString());
}
if (identifier.getKeyId() == currentMasterKey.getMasterKey()
.getKeyId()) {
updateAppAttemptKey(appAttemptId, currentMasterKey);
} else if (previousMasterKey != null
&& identifier.getKeyId() == previousMasterKey.getMasterKey()
.getKeyId()) {
updateAppAttemptKey(appAttemptId, previousMasterKey);
} else {
throw new InvalidToken(
"Older NMToken should not be used while starting the container.");
}
}
}
public synchronized void setNodeId(NodeId nodeId) {
if (LOG.isDebugEnabled()) {
LOG.debug("updating nodeId : " + nodeId);
}
this.nodeId = nodeId;
}
@Private
@VisibleForTesting
public synchronized boolean
isAppAttemptNMTokenKeyPresent(ApplicationAttemptId appAttemptId) {
return oldMasterKeys.containsKey(appAttemptId);
}
@Private
@VisibleForTesting
public synchronized NodeId getNodeId() {
return this.nodeId;
}
private void updateAppAttemptKey(ApplicationAttemptId attempt,
MasterKeyData key) {
this.oldMasterKeys.put(attempt, key);
try {
stateStore.storeNMTokenApplicationMasterKey(attempt,
key.getMasterKey());
} catch (IOException e) {
LOG.error("Unable to store master key for application " + attempt, e);
}
}
private void removeAppAttemptKey(ApplicationAttemptId attempt) {
this.oldMasterKeys.remove(attempt);
try {
stateStore.removeNMTokenApplicationMasterKey(attempt);
} catch (IOException e) {
LOG.error("Unable to remove master key for application " + attempt, e);
}
}
/**
* Used by the Distributed Scheduler framework to generate NMTokens
* @param applicationSubmitter
* @param container
* @return NMToken
*/
public NMToken generateNMToken(
String applicationSubmitter, Container container) {
this.readLock.lock();
try {
Token token =
createNMToken(container.getId().getApplicationAttemptId(),
container.getNodeId(), applicationSubmitter);
return NMToken.newInstance(container.getNodeId(), token);
} finally {
this.readLock.unlock();
}
}
}