| /* |
| * 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; |
| } |
| } |