View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security.access;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.classification.InterfaceAudience;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.hbase.TableName;
26  import org.apache.hadoop.hbase.util.Bytes;
27  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
28  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
29  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
30  import org.apache.zookeeper.KeeperException;
31  
32  import java.io.IOException;
33  import java.util.List;
34  
35  /**
36   * Handles synchronization of access control list entries and updates
37   * throughout all nodes in the cluster.  The {@link AccessController} instance
38   * on the {@code _acl_} table regions, creates a znode for each table as
39   * {@code /hbase/acl/tablename}, with the znode data containing a serialized
40   * list of the permissions granted for the table.  The {@code AccessController}
41   * instances on all other cluster hosts watch the znodes for updates, which
42   * trigger updates in the {@link TableAuthManager} permission cache.
43   */
44  @InterfaceAudience.Private
45  public class ZKPermissionWatcher extends ZooKeeperListener {
46    private static Log LOG = LogFactory.getLog(ZKPermissionWatcher.class);
47    // parent node for permissions lists
48    static final String ACL_NODE = "acl";
49    TableAuthManager authManager;
50    String aclZNode;
51  
52    public ZKPermissionWatcher(ZooKeeperWatcher watcher,
53        TableAuthManager authManager, Configuration conf) {
54      super(watcher);
55      this.authManager = authManager;
56      String aclZnodeParent = conf.get("zookeeper.znode.acl.parent", ACL_NODE);
57      this.aclZNode = ZKUtil.joinZNode(watcher.baseZNode, aclZnodeParent);
58    }
59  
60    public void start() throws KeeperException {
61      watcher.registerListener(this);
62      if (ZKUtil.watchAndCheckExists(watcher, aclZNode)) {
63        List<ZKUtil.NodeAndData> existing =
64            ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
65        if (existing != null) {
66          refreshNodes(existing);
67        }
68      }
69    }
70  
71    @Override
72    public void nodeCreated(String path) {
73      if (path.equals(aclZNode)) {
74        try {
75          List<ZKUtil.NodeAndData> nodes =
76              ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
77          refreshNodes(nodes);
78        } catch (KeeperException ke) {
79          LOG.error("Error reading data from zookeeper", ke);
80          // only option is to abort
81          watcher.abort("Zookeeper error obtaining acl node children", ke);
82        }
83      }
84    }
85  
86    @Override
87    public void nodeDeleted(String path) {
88      if (aclZNode.equals(ZKUtil.getParent(path))) {
89        String table = ZKUtil.getNodeName(path);
90        if(AccessControlLists.isNamespaceEntry(table)) {
91          authManager.removeNamespace(Bytes.toBytes(table));
92        } else {
93          authManager.removeTable(TableName.valueOf(table));
94        }
95      }
96    }
97  
98    @Override
99    public void nodeDataChanged(String path) {
100     if (aclZNode.equals(ZKUtil.getParent(path))) {
101       // update cache on an existing table node
102       String entry = ZKUtil.getNodeName(path);
103       try {
104         byte[] data = ZKUtil.getDataAndWatch(watcher, path);
105         refreshAuthManager(entry, data);
106       } catch (KeeperException ke) {
107         LOG.error("Error reading data from zookeeper for node " + entry, ke);
108         // only option is to abort
109         watcher.abort("Zookeeper error getting data for node " + entry, ke);
110       } catch (IOException ioe) {
111         LOG.error("Error reading permissions writables", ioe);
112       }
113     }
114   }
115 
116   @Override
117   public void nodeChildrenChanged(String path) {
118     if (path.equals(aclZNode)) {
119       // table permissions changed
120       try {
121         List<ZKUtil.NodeAndData> nodes =
122             ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
123         refreshNodes(nodes);
124       } catch (KeeperException ke) {
125         LOG.error("Error reading data from zookeeper for path "+path, ke);
126         watcher.abort("Zookeeper error get node children for path "+path, ke);
127       }
128     }
129   }
130 
131   private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
132     for (ZKUtil.NodeAndData n : nodes) {
133       if (n.isEmpty()) continue;
134       String path = n.getNode();
135       String entry = (ZKUtil.getNodeName(path));
136       try {
137         refreshAuthManager(entry, n.getData());
138       } catch (IOException ioe) {
139         LOG.error("Failed parsing permissions for table '" + entry +
140             "' from zk", ioe);
141       }
142     }
143   }
144 
145   private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
146     if (LOG.isDebugEnabled()) {
147       LOG.debug("Updating permissions cache from node "+entry+" with data: "+
148           Bytes.toStringBinary(nodeData));
149     }
150     if(AccessControlLists.isNamespaceEntry(entry)) {
151       authManager.refreshNamespaceCacheFromWritable(
152           AccessControlLists.fromNamespaceEntry(entry), nodeData);
153     } else {
154       authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
155     }
156   }
157 
158   /***
159    * Write a table's access controls to the permissions mirror in zookeeper
160    * @param entry
161    * @param permsData
162    */
163   public void writeToZookeeper(byte[] entry, byte[] permsData) {
164     String entryName = Bytes.toString(entry);
165     String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
166     zkNode = ZKUtil.joinZNode(zkNode, entryName);
167 
168     try {
169       ZKUtil.createWithParents(watcher, zkNode);
170       ZKUtil.updateExistingNodeData(watcher, zkNode, permsData, -1);
171     } catch (KeeperException e) {
172       LOG.error("Failed updating permissions for entry '" +
173           entryName + "'", e);
174       watcher.abort("Failed writing node "+zkNode+" to zookeeper", e);
175     }
176   }
177 }