001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.security.token;
020
021import java.io.IOException;
022import java.util.List;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.hbase.log.HBaseMarkers;
025import org.apache.hadoop.hbase.util.Writables;
026import org.apache.hadoop.hbase.zookeeper.ZKListener;
027import org.apache.hadoop.hbase.zookeeper.ZKUtil;
028import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
029import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
030import org.apache.yetus.audience.InterfaceAudience;
031import org.apache.zookeeper.KeeperException;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Synchronizes token encryption keys across cluster nodes.
037 */
038@InterfaceAudience.Private
039public class ZKSecretWatcher extends ZKListener {
040  private static final String DEFAULT_ROOT_NODE = "tokenauth";
041  private static final String DEFAULT_KEYS_PARENT = "keys";
042  private static final Logger LOG = LoggerFactory.getLogger(ZKSecretWatcher.class);
043
044  private AuthenticationTokenSecretManager secretManager;
045  private String baseKeyZNode;
046  private String keysParentZNode;
047
048  public ZKSecretWatcher(Configuration conf,
049      ZKWatcher watcher,
050      AuthenticationTokenSecretManager secretManager) {
051    super(watcher);
052    this.secretManager = secretManager;
053    String keyZNodeParent = conf.get("zookeeper.znode.tokenauth.parent", DEFAULT_ROOT_NODE);
054    this.baseKeyZNode = ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, keyZNodeParent);
055    this.keysParentZNode = ZNodePaths.joinZNode(baseKeyZNode, DEFAULT_KEYS_PARENT);
056  }
057
058  public void start() throws KeeperException {
059    watcher.registerListener(this);
060    // make sure the base node exists
061    ZKUtil.createWithParents(watcher, keysParentZNode);
062
063    if (ZKUtil.watchAndCheckExists(watcher, keysParentZNode)) {
064      List<ZKUtil.NodeAndData> nodes =
065          ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
066      refreshNodes(nodes);
067    }
068  }
069
070  @Override
071  public void nodeCreated(String path) {
072    if (path.equals(keysParentZNode)) {
073      try {
074        List<ZKUtil.NodeAndData> nodes =
075            ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
076        refreshNodes(nodes);
077      } catch (KeeperException ke) {
078        LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
079        watcher.abort("Error reading new key znode "+path, ke);
080      }
081    }
082  }
083
084  @Override
085  public void nodeDeleted(String path) {
086    if (keysParentZNode.equals(ZKUtil.getParent(path))) {
087      String keyId = ZKUtil.getNodeName(path);
088      try {
089        Integer id = Integer.valueOf(keyId);
090        secretManager.removeKey(id);
091        LOG.info("Node deleted id={}", id);
092      } catch (NumberFormatException nfe) {
093        LOG.error("Invalid znode name for key ID '"+keyId+"'", nfe);
094      }
095    }
096  }
097
098  @Override
099  public void nodeDataChanged(String path) {
100    if (keysParentZNode.equals(ZKUtil.getParent(path))) {
101      try {
102        byte[] data = ZKUtil.getDataAndWatch(watcher, path);
103        if (data == null || data.length == 0) {
104          LOG.debug("Ignoring empty node "+path);
105          return;
106        }
107
108        AuthenticationKey key = (AuthenticationKey)Writables.getWritable(data,
109            new AuthenticationKey());
110        secretManager.addKey(key);
111      } catch (KeeperException ke) {
112        LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
113        watcher.abort("Error reading updated key znode "+path, ke);
114      } catch (IOException ioe) {
115        LOG.error(HBaseMarkers.FATAL, "Error reading key writables", ioe);
116        watcher.abort("Error reading key writables from znode "+path, ioe);
117      }
118    }
119  }
120
121  @Override
122  public void nodeChildrenChanged(String path) {
123    if (path.equals(keysParentZNode)) {
124      // keys changed
125      try {
126        List<ZKUtil.NodeAndData> nodes =
127            ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
128        refreshNodes(nodes);
129      } catch (KeeperException ke) {
130        LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
131        watcher.abort("Error reading changed keys from zookeeper", ke);
132      }
133    }
134  }
135
136  public String getRootKeyZNode() {
137    return baseKeyZNode;
138  }
139
140  private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
141    for (ZKUtil.NodeAndData n : nodes) {
142      String path = n.getNode();
143      String keyId = ZKUtil.getNodeName(path);
144      try {
145        byte[] data = n.getData();
146        if (data == null || data.length == 0) {
147          LOG.debug("Ignoring empty node "+path);
148          continue;
149        }
150        AuthenticationKey key = (AuthenticationKey)Writables.getWritable(
151            data, new AuthenticationKey());
152        secretManager.addKey(key);
153      } catch (IOException ioe) {
154        LOG.error(HBaseMarkers.FATAL, "Failed reading new secret key for id '" +
155            keyId + "' from zk", ioe);
156        watcher.abort("Error deserializing key from znode "+path, ioe);
157      }
158    }
159  }
160
161  private String getKeyNode(int keyId) {
162    return ZNodePaths.joinZNode(keysParentZNode, Integer.toString(keyId));
163  }
164
165  public void removeKeyFromZK(AuthenticationKey key) {
166    String keyZNode = getKeyNode(key.getKeyId());
167    try {
168      ZKUtil.deleteNode(watcher, keyZNode);
169    } catch (KeeperException.NoNodeException nne) {
170      LOG.error("Non-existent znode "+keyZNode+" for key "+key.getKeyId(), nne);
171    } catch (KeeperException ke) {
172      LOG.error(HBaseMarkers.FATAL, "Failed removing znode "+keyZNode+" for key "+
173          key.getKeyId(), ke);
174      watcher.abort("Unhandled zookeeper error removing znode "+keyZNode+
175          " for key "+key.getKeyId(), ke);
176    }
177  }
178
179  public void addKeyToZK(AuthenticationKey key) {
180    String keyZNode = getKeyNode(key.getKeyId());
181    try {
182      byte[] keyData = Writables.getBytes(key);
183      // TODO: is there any point in retrying beyond what ZK client does?
184      ZKUtil.createSetData(watcher, keyZNode, keyData);
185    } catch (KeeperException ke) {
186      LOG.error(HBaseMarkers.FATAL, "Unable to synchronize master key "+key.getKeyId()+
187          " to znode "+keyZNode, ke);
188      watcher.abort("Unable to synchronize secret key "+
189          key.getKeyId()+" in zookeeper", ke);
190    } catch (IOException ioe) {
191      // this can only happen from an error serializing the key
192      watcher.abort("Failed serializing key "+key.getKeyId(), ioe);
193    }
194  }
195
196  public void updateKeyInZK(AuthenticationKey key) {
197    String keyZNode = getKeyNode(key.getKeyId());
198    try {
199      byte[] keyData = Writables.getBytes(key);
200      try {
201        ZKUtil.updateExistingNodeData(watcher, keyZNode, keyData, -1);
202      } catch (KeeperException.NoNodeException ne) {
203        // node was somehow removed, try adding it back
204        ZKUtil.createSetData(watcher, keyZNode, keyData);
205      }
206    } catch (KeeperException ke) {
207      LOG.error(HBaseMarkers.FATAL, "Unable to update master key "+key.getKeyId()+
208          " in znode "+keyZNode);
209      watcher.abort("Unable to synchronize secret key "+
210          key.getKeyId()+" in zookeeper", ke);
211    } catch (IOException ioe) {
212      // this can only happen from an error serializing the key
213      watcher.abort("Failed serializing key "+key.getKeyId(), ioe);
214    }
215  }
216
217  /**
218   * refresh keys
219   */
220  synchronized void refreshKeys() {
221    try {
222      List<ZKUtil.NodeAndData> nodes =
223          ZKUtil.getChildDataAndWatchForNewChildren(watcher, keysParentZNode);
224      refreshNodes(nodes);
225    } catch (KeeperException ke) {
226      LOG.error(HBaseMarkers.FATAL, "Error reading data from zookeeper", ke);
227      watcher.abort("Error reading changed keys from zookeeper", ke);
228    }
229  }
230
231  /**
232   * get token keys parent node
233   * @return token keys parent node
234   */
235  String getKeysParentZNode() {
236    return keysParentZNode;
237  }
238}