/** * 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.resourcemanager.security; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import javax.crypto.SecretKey; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.security.AMRMTokenIdentifier; /** * AMRM-tokens are per ApplicationAttempt. If users redistribute their * tokens, it is their headache, god save them. I mean you are not supposed to * distribute keys to your vault, right? Anyways, ResourceManager saves each * token locally in memory till application finishes and to a store for restart, * so no need to remember master-keys even after rolling them. */ public class AMRMTokenSecretManager extends SecretManager<AMRMTokenIdentifier> { private static final Log LOG = LogFactory .getLog(AMRMTokenSecretManager.class); private SecretKey masterKey; private final Timer timer; private final long rollingInterval; private final Map<ApplicationAttemptId, byte[]> passwords = new HashMap<ApplicationAttemptId, byte[]>(); /** * Create an {@link AMRMTokenSecretManager} */ public AMRMTokenSecretManager(Configuration conf) { rollMasterKey(); this.timer = new Timer(); this.rollingInterval = conf .getLong( YarnConfiguration.RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS, YarnConfiguration.DEFAULT_RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS) * 1000; } public void start() { this.timer.scheduleAtFixedRate(new MasterKeyRoller(), 0, rollingInterval); } public void stop() { this.timer.cancel(); } public synchronized void applicationMasterFinished( ApplicationAttemptId appAttemptId) { if (LOG.isDebugEnabled()) { LOG.debug("Application finished, removing password for " + appAttemptId); } this.passwords.remove(appAttemptId); } private class MasterKeyRoller extends TimerTask { @Override public void run() { rollMasterKey(); } } @Private public synchronized void setMasterKey(SecretKey masterKey) { this.masterKey = masterKey; } @Private public synchronized SecretKey getMasterKey() { return this.masterKey; } @Private synchronized void rollMasterKey() { LOG.info("Rolling master-key for amrm-tokens"); this.masterKey = generateSecret(); } /** * 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); } byte[] password = createPassword(identifier.getBytes(), masterKey); this.passwords.put(applicationAttemptId, password); return password; } /** * Populate persisted password of AMRMToken back to AMRMTokenSecretManager. */ public synchronized void addPersistedPassword(Token<AMRMTokenIdentifier> token) throws IOException { AMRMTokenIdentifier identifier = token.decodeIdentifier(); if (LOG.isDebugEnabled()) { LOG.debug("Adding password for " + identifier.getApplicationAttemptId()); } this.passwords.put(identifier.getApplicationAttemptId(), token.getPassword()); } /** * Retrieve the password for the given {@link AMRMTokenIdentifier}. * Used by RPC layer to validate a remote {@link AMRMTokenIdentifier}. */ @Override public synchronized byte[] retrievePassword( AMRMTokenIdentifier identifier) throws InvalidToken { ApplicationAttemptId applicationAttemptId = identifier.getApplicationAttemptId(); if (LOG.isDebugEnabled()) { LOG.debug("Trying to retrieve password for " + applicationAttemptId); } byte[] password = this.passwords.get(applicationAttemptId); if (password == null) { throw new InvalidToken("Password not found for ApplicationAttempt " + applicationAttemptId); } return password; } /** * Creates an empty TokenId to be used for de-serializing an * {@link AMRMTokenIdentifier} by the RPC layer. */ @Override public AMRMTokenIdentifier createIdentifier() { return new AMRMTokenIdentifier(); } }