All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager Maven / Gradle / Ivy

The newest version!
/*
 * 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.hbase.security.token;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Stoppable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.zookeeper.ZKClusterId;
import org.apache.hadoop.hbase.zookeeper.ZKLeaderManager;
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.token.SecretManager;
import org.apache.hadoop.security.token.Token;
import org.apache.zookeeper.KeeperException;

/**
 * Manages an internal list of secret keys used to sign new authentication
 * tokens as they are generated, and to valid existing tokens used for
 * authentication.
 *
 * 

* A single instance of {@code AuthenticationTokenSecretManager} will be * running as the "leader" in a given HBase cluster. The leader is responsible * for periodically generating new secret keys, which are then distributed to * followers via ZooKeeper, and for expiring previously used secret keys that * are no longer needed (as any tokens using them have expired). *

*/ @InterfaceAudience.Private @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC", justification="Complaint is about lastKeyUpdate... afraid to change it.") public class AuthenticationTokenSecretManager extends SecretManager { static final String NAME_PREFIX = "SecretManager-"; private static final Log LOG = LogFactory.getLog( AuthenticationTokenSecretManager.class); private long lastKeyUpdate; // FindBugs: IS2_INCONSISTENT_SYNC FIX!! private long keyUpdateInterval; private long tokenMaxLifetime; private ZKSecretWatcher zkWatcher; private LeaderElector leaderElector; private ZKClusterId clusterId; private Map allKeys = new ConcurrentHashMap(); private AuthenticationKey currentKey; private int idSeq; private AtomicLong tokenSeq = new AtomicLong(); private String name; /** * Create a new secret manager instance for generating keys. * @param conf Configuration to use * @param zk Connection to zookeeper for handling leader elections * @param keyUpdateInterval Time (in milliseconds) between rolling a new master key for token signing * @param tokenMaxLifetime Maximum age (in milliseconds) before a token expires and is no longer valid */ /* TODO: Restrict access to this constructor to make rogues instances more difficult. * For the moment this class is instantiated from * org.apache.hadoop.hbase.ipc.SecureServer so public access is needed. */ public AuthenticationTokenSecretManager(Configuration conf, ZooKeeperWatcher zk, String serverName, long keyUpdateInterval, long tokenMaxLifetime) { this.zkWatcher = new ZKSecretWatcher(conf, zk, this); this.keyUpdateInterval = keyUpdateInterval; this.tokenMaxLifetime = tokenMaxLifetime; this.leaderElector = new LeaderElector(zk, serverName); this.name = NAME_PREFIX+serverName; this.clusterId = new ZKClusterId(zk, zk); } public void start() { try { // populate any existing keys this.zkWatcher.start(); // try to become leader this.leaderElector.start(); } catch (KeeperException ke) { LOG.error("Zookeeper initialization failed", ke); } } public void stop() { this.leaderElector.stop("SecretManager stopping"); } public boolean isMaster() { return leaderElector.isMaster(); } public String getName() { return name; } @Override protected synchronized byte[] createPassword(AuthenticationTokenIdentifier identifier) { long now = EnvironmentEdgeManager.currentTime(); AuthenticationKey secretKey = currentKey; identifier.setKeyId(secretKey.getKeyId()); identifier.setIssueDate(now); identifier.setExpirationDate(now + tokenMaxLifetime); identifier.setSequenceNumber(tokenSeq.getAndIncrement()); return createPassword(identifier.getBytes(), secretKey.getKey()); } @Override public byte[] retrievePassword(AuthenticationTokenIdentifier identifier) throws InvalidToken { long now = EnvironmentEdgeManager.currentTime(); if (identifier.getExpirationDate() < now) { throw new InvalidToken("Token has expired"); } AuthenticationKey masterKey = allKeys.get(identifier.getKeyId()); if(masterKey == null) { if(zkWatcher.getWatcher().isAborted()) { LOG.error("ZookeeperWatcher is abort"); throw new InvalidToken("Token keys could not be sync from zookeeper" + " because of ZookeeperWatcher abort"); } synchronized (this) { if (!leaderElector.isAlive() || leaderElector.isStopped()) { LOG.warn("Thread leaderElector[" + leaderElector.getName() + ":" + leaderElector.getId() + "] is stopped or not alive"); leaderElector.start(); LOG.info("Thread leaderElector [" + leaderElector.getName() + ":" + leaderElector.getId() + "] is started"); } } zkWatcher.refreshKeys(); if (LOG.isDebugEnabled()) { LOG.debug("Sync token keys from zookeeper"); } masterKey = allKeys.get(identifier.getKeyId()); } if (masterKey == null) { throw new InvalidToken("Unknown master key for token (id="+ identifier.getKeyId()+")"); } // regenerate the password return createPassword(identifier.getBytes(), masterKey.getKey()); } @Override public AuthenticationTokenIdentifier createIdentifier() { return new AuthenticationTokenIdentifier(); } public Token generateToken(String username) { AuthenticationTokenIdentifier ident = new AuthenticationTokenIdentifier(username); Token token = new Token(ident, this); if (clusterId.hasId()) { token.setService(new Text(clusterId.getId())); } return token; } public synchronized void addKey(AuthenticationKey key) throws IOException { // ignore zk changes when running as master if (leaderElector.isMaster()) { if (LOG.isDebugEnabled()) { LOG.debug("Running as master, ignoring new key "+key.getKeyId()); } return; } if (LOG.isDebugEnabled()) { LOG.debug("Adding key "+key.getKeyId()); } allKeys.put(key.getKeyId(), key); if (currentKey == null || key.getKeyId() > currentKey.getKeyId()) { currentKey = key; } // update current sequence if (key.getKeyId() > idSeq) { idSeq = key.getKeyId(); } } synchronized boolean removeKey(Integer keyId) { // ignore zk changes when running as master if (leaderElector.isMaster()) { if (LOG.isDebugEnabled()) { LOG.debug("Running as master, ignoring removed key "+keyId); } return false; } if (LOG.isDebugEnabled()) { LOG.debug("Removing key "+keyId); } allKeys.remove(keyId); return true; } synchronized AuthenticationKey getCurrentKey() { return currentKey; } AuthenticationKey getKey(int keyId) { return allKeys.get(keyId); } synchronized void removeExpiredKeys() { if (!leaderElector.isMaster()) { LOG.info("Skipping removeExpiredKeys() because not running as master."); return; } long now = EnvironmentEdgeManager.currentTime(); Iterator iter = allKeys.values().iterator(); while (iter.hasNext()) { AuthenticationKey key = iter.next(); if (key.getExpiration() < now) { if (LOG.isDebugEnabled()) { LOG.debug("Removing expired key "+key.getKeyId()); } iter.remove(); zkWatcher.removeKeyFromZK(key); } } } synchronized boolean isCurrentKeyRolled() { return currentKey != null; } synchronized void rollCurrentKey() { if (!leaderElector.isMaster()) { LOG.info("Skipping rollCurrentKey() because not running as master."); return; } long now = EnvironmentEdgeManager.currentTime(); AuthenticationKey prev = currentKey; AuthenticationKey newKey = new AuthenticationKey(++idSeq, Long.MAX_VALUE, // don't allow to expire until it's replaced by a new key generateSecret()); allKeys.put(newKey.getKeyId(), newKey); currentKey = newKey; zkWatcher.addKeyToZK(newKey); lastKeyUpdate = now; if (prev != null) { // make sure previous key is still stored prev.setExpiration(now + tokenMaxLifetime); allKeys.put(prev.getKeyId(), prev); zkWatcher.updateKeyInZK(prev); } } synchronized long getLastKeyUpdate() { return lastKeyUpdate; } public static SecretKey createSecretKey(byte[] raw) { return SecretManager.createSecretKey(raw); } private class LeaderElector extends Thread implements Stoppable { private boolean stopped = false; /** Flag indicating whether we're in charge of rolling/expiring keys */ private boolean isMaster = false; private ZKLeaderManager zkLeader; public LeaderElector(ZooKeeperWatcher watcher, String serverName) { setDaemon(true); setName("ZKSecretWatcher-leaderElector"); zkLeader = new ZKLeaderManager(watcher, ZKUtil.joinZNode(zkWatcher.getRootKeyZNode(), "keymaster"), Bytes.toBytes(serverName), this); } public boolean isMaster() { return isMaster; } @Override public boolean isStopped() { return stopped; } @Override public void stop(String reason) { if (stopped) { return; } stopped = true; // prevent further key generation when stopping if (isMaster) { zkLeader.stepDownAsLeader(); } isMaster = false; LOG.info("Stopping leader election, because: "+reason); interrupt(); } @Override public void run() { zkLeader.start(); zkLeader.waitToBecomeLeader(); isMaster = true; while (!stopped) { long now = EnvironmentEdgeManager.currentTime(); // clear any expired removeExpiredKeys(); long localLastKeyUpdate = getLastKeyUpdate(); if (localLastKeyUpdate + keyUpdateInterval < now) { // roll a new master key rollCurrentKey(); } try { Thread.sleep(5000); } catch (InterruptedException ie) { if (LOG.isDebugEnabled()) { LOG.debug("Interrupted waiting for next update", ie); } } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy