|
@@ -0,0 +1,154 @@
|
|
|
+/**
|
|
|
+ * 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 static org.junit.Assert.assertEquals;
|
|
|
+import static org.junit.Assert.assertFalse;
|
|
|
+import static org.junit.Assert.assertNotNull;
|
|
|
+import static org.junit.Assert.assertTrue;
|
|
|
+import static org.junit.Assert.fail;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+
|
|
|
+import org.apache.hadoop.io.Text;
|
|
|
+import org.apache.hadoop.security.token.SecretManager.InvalidToken;
|
|
|
+import org.apache.hadoop.security.token.Token;
|
|
|
+import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
|
|
+import org.apache.hadoop.yarn.api.records.ApplicationId;
|
|
|
+import org.apache.hadoop.yarn.api.records.NodeId;
|
|
|
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
|
|
+import org.apache.hadoop.yarn.security.NMTokenIdentifier;
|
|
|
+import org.apache.hadoop.yarn.server.api.records.MasterKey;
|
|
|
+import org.apache.hadoop.yarn.server.nodemanager.recovery.NMMemoryStateStoreService;
|
|
|
+import org.apache.hadoop.yarn.server.security.BaseNMTokenSecretManager;
|
|
|
+import org.apache.hadoop.yarn.util.ConverterUtils;
|
|
|
+import org.junit.Test;
|
|
|
+
|
|
|
+public class TestNMTokenSecretManagerInNM {
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testRecovery() throws IOException {
|
|
|
+ YarnConfiguration conf = new YarnConfiguration();
|
|
|
+ conf.setBoolean(YarnConfiguration.NM_RECOVERY_ENABLED, true);
|
|
|
+ final NodeId nodeId = NodeId.newInstance("somehost", 1234);
|
|
|
+ final ApplicationAttemptId attempt1 =
|
|
|
+ ApplicationAttemptId.newInstance(ApplicationId.newInstance(1, 1), 1);
|
|
|
+ final ApplicationAttemptId attempt2 =
|
|
|
+ ApplicationAttemptId.newInstance(ApplicationId.newInstance(2, 2), 2);
|
|
|
+ NMTokenKeyGeneratorForTest keygen = new NMTokenKeyGeneratorForTest();
|
|
|
+ NMMemoryStateStoreService stateStore = new NMMemoryStateStoreService();
|
|
|
+ stateStore.init(conf);
|
|
|
+ stateStore.start();
|
|
|
+ NMTokenSecretManagerInNM secretMgr =
|
|
|
+ new NMTokenSecretManagerInNM(stateStore);
|
|
|
+ secretMgr.setNodeId(nodeId);
|
|
|
+ MasterKey currentKey = keygen.generateKey();
|
|
|
+ secretMgr.setMasterKey(currentKey);
|
|
|
+ NMTokenIdentifier attemptToken1 =
|
|
|
+ getNMTokenId(secretMgr.createNMToken(attempt1, nodeId, "user1"));
|
|
|
+ NMTokenIdentifier attemptToken2 =
|
|
|
+ getNMTokenId(secretMgr.createNMToken(attempt2, nodeId, "user2"));
|
|
|
+ secretMgr.appAttemptStartContainer(attemptToken1);
|
|
|
+ secretMgr.appAttemptStartContainer(attemptToken2);
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt1));
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt2));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken1));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken2));
|
|
|
+
|
|
|
+ // restart and verify key is still there and token still valid
|
|
|
+ secretMgr = new NMTokenSecretManagerInNM(stateStore);
|
|
|
+ secretMgr.recover(stateStore.loadNMTokenState());
|
|
|
+ secretMgr.setNodeId(nodeId);
|
|
|
+ assertEquals(currentKey, secretMgr.getCurrentKey());
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt1));
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt2));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken1));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken2));
|
|
|
+
|
|
|
+ // roll master key and remove an app
|
|
|
+ currentKey = keygen.generateKey();
|
|
|
+ secretMgr.setMasterKey(currentKey);
|
|
|
+ secretMgr.appFinished(attempt1.getApplicationId());
|
|
|
+
|
|
|
+ // restart and verify attempt1 key is still valid due to prev key persist
|
|
|
+ secretMgr = new NMTokenSecretManagerInNM(stateStore);
|
|
|
+ secretMgr.recover(stateStore.loadNMTokenState());
|
|
|
+ secretMgr.setNodeId(nodeId);
|
|
|
+ assertEquals(currentKey, secretMgr.getCurrentKey());
|
|
|
+ assertFalse(secretMgr.isAppAttemptNMTokenKeyPresent(attempt1));
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt2));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken1));
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken2));
|
|
|
+
|
|
|
+ // roll master key again, restart, and verify attempt1 key is bad but
|
|
|
+ // attempt2 is still good due to app key persist
|
|
|
+ currentKey = keygen.generateKey();
|
|
|
+ secretMgr.setMasterKey(currentKey);
|
|
|
+ secretMgr = new NMTokenSecretManagerInNM(stateStore);
|
|
|
+ secretMgr.recover(stateStore.loadNMTokenState());
|
|
|
+ secretMgr.setNodeId(nodeId);
|
|
|
+ assertEquals(currentKey, secretMgr.getCurrentKey());
|
|
|
+ assertFalse(secretMgr.isAppAttemptNMTokenKeyPresent(attempt1));
|
|
|
+ assertTrue(secretMgr.isAppAttemptNMTokenKeyPresent(attempt2));
|
|
|
+ try {
|
|
|
+ secretMgr.retrievePassword(attemptToken1);
|
|
|
+ fail("attempt token should not still be valid");
|
|
|
+ } catch (InvalidToken e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ assertNotNull(secretMgr.retrievePassword(attemptToken2));
|
|
|
+
|
|
|
+ // remove last attempt, restart, verify both tokens are now bad
|
|
|
+ secretMgr.appFinished(attempt2.getApplicationId());
|
|
|
+ secretMgr = new NMTokenSecretManagerInNM(stateStore);
|
|
|
+ secretMgr.recover(stateStore.loadNMTokenState());
|
|
|
+ secretMgr.setNodeId(nodeId);
|
|
|
+ assertEquals(currentKey, secretMgr.getCurrentKey());
|
|
|
+ assertFalse(secretMgr.isAppAttemptNMTokenKeyPresent(attempt1));
|
|
|
+ assertFalse(secretMgr.isAppAttemptNMTokenKeyPresent(attempt2));
|
|
|
+ try {
|
|
|
+ secretMgr.retrievePassword(attemptToken1);
|
|
|
+ fail("attempt token should not still be valid");
|
|
|
+ } catch (InvalidToken e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ secretMgr.retrievePassword(attemptToken2);
|
|
|
+ fail("attempt token should not still be valid");
|
|
|
+ } catch (InvalidToken e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+
|
|
|
+ stateStore.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ private NMTokenIdentifier getNMTokenId(
|
|
|
+ org.apache.hadoop.yarn.api.records.Token token) throws IOException {
|
|
|
+ Token<NMTokenIdentifier> convertedToken =
|
|
|
+ ConverterUtils.convertFromYarn(token, (Text) null);
|
|
|
+ return convertedToken.decodeIdentifier();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class NMTokenKeyGeneratorForTest extends
|
|
|
+ BaseNMTokenSecretManager {
|
|
|
+ public MasterKey generateKey() {
|
|
|
+ return createNewMasterKey().getMasterKey();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|