/*
* 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.accumulo.server.security.delegation;
import java.util.List;
import org.apache.accumulo.core.util.Daemon;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
/**
* Service that handles generation of the secret key used to create delegation tokens.
*/
public class AuthenticationTokenKeyManager extends Daemon {
private static final Logger log = LoggerFactory.getLogger(AuthenticationTokenKeyManager.class);
private final AuthenticationTokenSecretManager secretManager;
private final ZooAuthenticationKeyDistributor keyDistributor;
private long lastKeyUpdate = 0;
private long keyUpdateInterval;
private long tokenMaxLifetime;
private int idSeq = 0;
private volatile boolean keepRunning = true, initialized = false;
/**
* Construct the key manager which will generate new AuthenticationKeys to generate and verify delegation tokens
*
* @param mgr
* The SecretManager in use
* @param dist
* The implementation to distribute AuthenticationKeys to ZooKeeper
* @param keyUpdateInterval
* The frequency, in milliseconds, that new AuthenticationKeys are created
* @param tokenMaxLifetime
* The lifetime, in milliseconds, of generated AuthenticationKeys (and subsequently delegation tokens).
*/
public AuthenticationTokenKeyManager(AuthenticationTokenSecretManager mgr, ZooAuthenticationKeyDistributor dist, long keyUpdateInterval, long tokenMaxLifetime) {
super("Delegation Token Key Manager");
this.secretManager = mgr;
this.keyDistributor = dist;
this.keyUpdateInterval = keyUpdateInterval;
this.tokenMaxLifetime = tokenMaxLifetime;
}
@VisibleForTesting
void setKeepRunning(boolean keepRunning) {
this.keepRunning = keepRunning;
}
public boolean isInitialized() {
return initialized;
}
public void gracefulStop() {
keepRunning = false;
}
@Override
public void run() {
// Make sure to initialize the secret manager with keys already in ZK
updateStateFromCurrentKeys();
initialized = true;
while (keepRunning) {
long now = System.currentTimeMillis();
_run(now);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
log.debug("Interrupted waiting for next update", ie);
}
}
}
@VisibleForTesting
void updateStateFromCurrentKeys() {
try {
List<AuthenticationKey> currentKeys = keyDistributor.getCurrentKeys();
if (!currentKeys.isEmpty()) {
for (AuthenticationKey key : currentKeys) {
// Ensure that we don't create new Keys with duplicate keyIds for keys that already exist
// It's not a big concern if we happen to duplicate keyIds for already expired keys.
if (key.getKeyId() > idSeq) {
idSeq = key.getKeyId();
}
secretManager.addKey(key);
}
log.info("Added {} existing AuthenticationKeys into the local cache from ZooKeeper", currentKeys.size());
// Try to use the last key instead of creating a new one right away. This will present more expected
// functionality if the active master happens to die for some reasonn
AuthenticationKey currentKey = secretManager.getCurrentKey();
if (null != currentKey) {
log.info("Updating last key update to {} from current secret manager key", currentKey.getCreationDate());
lastKeyUpdate = currentKey.getCreationDate();
}
}
} catch (KeeperException | InterruptedException e) {
log.warn("Failed to fetch existing AuthenticationKeys from ZooKeeper");
}
}
@VisibleForTesting
long getLastKeyUpdate() {
return lastKeyUpdate;
}
@VisibleForTesting
int getIdSeq() {
return idSeq;
}
/**
* Internal "run" method which performs the actual work.
*
* @param now
* The current time in millis since epoch.
*/
void _run(long now) {
// clear any expired keys
int removedKeys = secretManager.removeExpiredKeys(keyDistributor);
if (removedKeys > 0) {
log.debug("Removed {} expired keys from the local cache", removedKeys);
}
if (lastKeyUpdate + keyUpdateInterval < now) {
log.debug("Key update interval passed, creating new authentication key");
// Increment the idSeq and use the new value as the unique ID
AuthenticationKey newKey = new AuthenticationKey(++idSeq, now, now + tokenMaxLifetime, secretManager.generateSecret());
log.debug("Created new {}", newKey.toString());
// Will set to be the current key given the idSeq
secretManager.addKey(newKey);
// advertise it to tabletservers
try {
keyDistributor.advertise(newKey);
} catch (KeeperException | InterruptedException e) {
log.error("Failed to advertise AuthenticationKey in ZooKeeper. Exiting.", e);
throw new RuntimeException(e);
}
lastKeyUpdate = now;
}
}
}