blob: 2398ba4031ed9746fbd8b617ce7e156939b2f2ef [file] [log] [blame]
/*
* 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 java.io.IOException;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.log.HBaseMarkers;
import org.apache.hadoop.hbase.util.Writables;
import org.apache.hadoop.hbase.zookeeper.ZKListener;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Synchronizes token encryption keys across cluster nodes.
*/
@InterfaceAudience.Private
public class ZKSecretWatcher extends ZKListener {
private static final String DEFAULT_ROOT_NODE = "tokenauth";
private static final String DEFAULT_KEYS_PARENT = "keys";
private static final Logger LOG = LoggerFactory.getLogger(ZKSecretWatcher.class);
private AuthenticationTokenSecretManager secretManager;
private String baseKeyZNode;
private String keysParentZNode;
public ZKSecretWatcher(Configuration conf,
ZKWatcher watcher,
AuthenticationTokenSecretManager secretManager) {
super(watcher);
this.secretManager = secretManager;
String keyZNodeParent = conf.get("zookeeper.znode.tokenauth.parent", DEFAULT_ROOT_NODE);
this.baseKeyZNode = ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, keyZNodeParent);
this.keysParentZNode = ZNodePaths.joinZNode(baseKeyZNode, DEFAULT_KEYS_PARENT);
}
public void start() throws KeeperException {
watcher.registerListener(this);
// make sure the base node exists
ZKUtil.createWithParents(watcher, keysParentZNode);
if (ZKUtil.watchAndCheckExists(watcher, keysParentZNode)) {
List<ZKUtil.NodeAndData> nodes =
ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
refreshNodes(nodes);
}
}
@Override
public void nodeCreated(String path) {
if (path.equals(keysParentZNode)) {
try {
List<ZKUtil.NodeAndData> nodes =
ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
refreshNodes(nodes);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
watcher.abort("Error reading new key znode "+path, ke);
}
}
}
@Override
public void nodeDeleted(String path) {
if (keysParentZNode.equals(ZKUtil.getParent(path))) {
String keyId = ZKUtil.getNodeName(path);
try {
Integer id = Integer.valueOf(keyId);
secretManager.removeKey(id);
LOG.info("Node deleted id={}", id);
} catch (NumberFormatException nfe) {
LOG.error("Invalid znode name for key ID '"+keyId+"'", nfe);
}
}
}
@Override
public void nodeDataChanged(String path) {
if (keysParentZNode.equals(ZKUtil.getParent(path))) {
try {
byte[] data = ZKUtil.getDataAndWatch(watcher, path);
if (data == null || data.length == 0) {
LOG.debug("Ignoring empty node "+path);
return;
}
AuthenticationKey key = (AuthenticationKey)Writables.getWritable(data,
new AuthenticationKey());
secretManager.addKey(key);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
watcher.abort("Error reading updated key znode "+path, ke);
} catch (IOException ioe) {
LOG.error(HBaseMarkers.FATAL, "Error reading key writables", ioe);
watcher.abort("Error reading key writables from znode "+path, ioe);
}
}
}
@Override
public void nodeChildrenChanged(String path) {
if (path.equals(keysParentZNode)) {
// keys changed
try {
List<ZKUtil.NodeAndData> nodes =
ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
refreshNodes(nodes);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
watcher.abort("Error reading changed keys from zookeeper", ke);
}
}
}
public String getRootKeyZNode() {
return baseKeyZNode;
}
private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
for (ZKUtil.NodeAndData n : nodes) {
String path = n.getNode();
String keyId = ZKUtil.getNodeName(path);
try {
byte[] data = n.getData();
if (data == null || data.length == 0) {
LOG.debug("Ignoring empty node "+path);
continue;
}
AuthenticationKey key = (AuthenticationKey)Writables.getWritable(
data, new AuthenticationKey());
secretManager.addKey(key);
} catch (IOException ioe) {
LOG.error(HBaseMarkers.FATAL, "Failed reading new secret key for id '" +
keyId + "' from zk", ioe);
watcher.abort("Error deserializing key from znode "+path, ioe);
}
}
}
private String getKeyNode(int keyId) {
return ZNodePaths.joinZNode(keysParentZNode, Integer.toString(keyId));
}
public void removeKeyFromZK(AuthenticationKey key) {
String keyZNode = getKeyNode(key.getKeyId());
try {
ZKUtil.deleteNode(watcher, keyZNode);
} catch (KeeperException.NoNodeException nne) {
LOG.error("Non-existent znode "+keyZNode+" for key "+key.getKeyId(), nne);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Failed removing znode "+keyZNode+" for key "+
key.getKeyId(), ke);
watcher.abort("Unhandled zookeeper error removing znode "+keyZNode+
" for key "+key.getKeyId(), ke);
}
}
public void addKeyToZK(AuthenticationKey key) {
String keyZNode = getKeyNode(key.getKeyId());
try {
byte[] keyData = Writables.getBytes(key);
// TODO: is there any point in retrying beyond what ZK client does?
ZKUtil.createSetData(watcher, keyZNode, keyData);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Unable to synchronize master key "+key.getKeyId()+
" to znode "+keyZNode, ke);
watcher.abort("Unable to synchronize secret key "+
key.getKeyId()+" in zookeeper", ke);
} catch (IOException ioe) {
// this can only happen from an error serializing the key
watcher.abort("Failed serializing key "+key.getKeyId(), ioe);
}
}
public void updateKeyInZK(AuthenticationKey key) {
String keyZNode = getKeyNode(key.getKeyId());
try {
byte[] keyData = Writables.getBytes(key);
try {
ZKUtil.updateExistingNodeData(watcher, keyZNode, keyData, -1);
} catch (KeeperException.NoNodeException ne) {
// node was somehow removed, try adding it back
ZKUtil.createSetData(watcher, keyZNode, keyData);
}
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Unable to update master key "+key.getKeyId()+
" in znode "+keyZNode);
watcher.abort("Unable to synchronize secret key "+
key.getKeyId()+" in zookeeper", ke);
} catch (IOException ioe) {
// this can only happen from an error serializing the key
watcher.abort("Failed serializing key "+key.getKeyId(), ioe);
}
}
/**
* refresh keys
*/
synchronized void refreshKeys() {
try {
List<ZKUtil.NodeAndData> nodes =
ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
refreshNodes(nodes);
} catch (KeeperException ke) {
LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
watcher.abort("Error reading changed keys from zookeeper", ke);
}
}
/**
* get token keys parent node
* @return token keys parent node
*/
String getKeysParentZNode() {
return keysParentZNode;
}
}