blob: e516b5b76a05f9df2b3970a85294d31207ae8992 [file] [log] [blame]
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.security.authentication.util;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract SignerSecretProvider that can be use used as the base for a
* rolling secret. The secret will roll over at the same interval as the token
* validity, so there are only ever a maximum of two valid secrets at any
* given time. This class handles storing and returning the secrets, as well
* as the rolling over. At a minimum, subclasses simply need to implement the
* generateNewSecret() method. More advanced implementations can override
* other methods to provide more advanced behavior, but should be careful when
* doing so.
*/
@InterfaceStability.Unstable
@InterfaceAudience.Private
public abstract class RolloverSignerSecretProvider
extends SignerSecretProvider {
@VisibleForTesting
static Logger LOG = LoggerFactory.getLogger(
RolloverSignerSecretProvider.class);
/**
* Stores the currently valid secrets. The current secret is the 0th element
* in the array.
*/
private volatile byte[][] secrets;
private ScheduledExecutorService scheduler;
private boolean schedulerRunning;
private boolean isDestroyed;
public RolloverSignerSecretProvider() {
schedulerRunning = false;
isDestroyed = false;
}
/**
* Initialize the SignerSecretProvider. It initializes the current secret
* and starts the scheduler for the rollover to run at an interval of
* tokenValidity.
* @param config configuration properties
* @param servletContext servlet context
* @param tokenValidity The amount of time a token is valid for
* @throws Exception thrown if an error occurred
*/
@Override
public void init(Properties config, ServletContext servletContext,
long tokenValidity) throws Exception {
initSecrets(generateNewSecret(), null);
startScheduler(tokenValidity, tokenValidity);
}
/**
* Initializes the secrets array. This should typically be called only once,
* during init but some implementations may wish to call it other times.
* previousSecret can be null if there isn't a previous secret, but
* currentSecret should never be null.
* @param currentSecret The current secret
* @param previousSecret The previous secret
*/
protected void initSecrets(byte[] currentSecret, byte[] previousSecret) {
secrets = new byte[][]{currentSecret, previousSecret};
}
/**
* Starts the scheduler for the rollover to run at an interval.
* @param initialDelay The initial delay in the rollover in milliseconds
* @param period The interval for the rollover in milliseconds
*/
protected synchronized void startScheduler(long initialDelay, long period) {
if (!schedulerRunning) {
schedulerRunning = true;
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
rollSecret();
}
}, initialDelay, period, TimeUnit.MILLISECONDS);
}
}
@Override
public synchronized void destroy() {
if (!isDestroyed) {
isDestroyed = true;
if (scheduler != null) {
scheduler.shutdown();
}
schedulerRunning = false;
super.destroy();
}
}
/**
* Rolls the secret. It is called automatically at the rollover interval.
*/
protected synchronized void rollSecret() {
if (!isDestroyed) {
LOG.debug("rolling secret");
byte[] newSecret = generateNewSecret();
secrets = new byte[][]{newSecret, secrets[0]};
}
}
/**
* Subclasses should implement this to return a new secret. It will be called
* automatically at the secret rollover interval. It should never return null.
* @return a new secret
*/
protected abstract byte[] generateNewSecret();
@Override
public byte[] getCurrentSecret() {
return secrets[0];
}
@Override
public byte[][] getAllSecrets() {
return secrets;
}
}