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.conf.Configuration;
24  import org.apache.hadoop.hbase.TableName;
25  import org.apache.hadoop.hbase.util.Bytes;
26  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
27  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
28  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
29  import org.apache.zookeeper.KeeperException;
30  
31  import java.io.IOException;
32  import java.util.List;
33  
34  /**
35   * Handles synchronization of access control list entries and updates
36   * throughout all nodes in the cluster.  The {@link AccessController} instance
37   * on the {@code _acl_} table regions, creates a znode for each table as
38   * {@code /hbase/acl/tablename}, with the znode data containing a serialized
39   * list of the permissions granted for the table.  The {@code AccessController}
40   * instances on all other cluster hosts watch the znodes for updates, which
41   * trigger updates in the {@link TableAuthManager} permission cache.
42   */
43  public class ZKPermissionWatcher extends ZooKeeperListener {
44    private static Log LOG = LogFactory.getLog(ZKPermissionWatcher.class);
45    // parent node for permissions lists
46    static final String ACL_NODE = "acl";
47    TableAuthManager authManager;
48    String aclZNode;
49  
50    public ZKPermissionWatcher(ZooKeeperWatcher watcher,
51        TableAuthManager authManager, Configuration conf) {
52      super(watcher);
53      this.authManager = authManager;
54      String aclZnodeParent = conf.get("zookeeper.znode.acl.parent", ACL_NODE);
55      this.aclZNode = ZKUtil.joinZNode(watcher.baseZNode, aclZnodeParent);
56    }
57  
58    public void start() throws KeeperException {
59      watcher.registerListener(this);
60      if (ZKUtil.watchAndCheckExists(watcher, aclZNode)) {
61        List<ZKUtil.NodeAndData> existing =
62            ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
63        if (existing != null) {
64          refreshNodes(existing);
65        }
66      }
67    }
68  
69    @Override
70    public void nodeCreated(String path) {
71      if (path.equals(aclZNode)) {
72        try {
73          List<ZKUtil.NodeAndData> nodes =
74              ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
75          refreshNodes(nodes);
76        } catch (KeeperException ke) {
77          LOG.error("Error reading data from zookeeper", ke);
78          // only option is to abort
79          watcher.abort("Zookeeper error obtaining acl node children", ke);
80        }
81      }
82    }
83  
84    @Override
85    public void nodeDeleted(String path) {
86      if (aclZNode.equals(ZKUtil.getParent(path))) {
87        String table = ZKUtil.getNodeName(path);
88        if(AccessControlLists.isNamespaceEntry(table)) {
89          authManager.removeNamespace(Bytes.toBytes(table));
90        } else {
91          authManager.removeTable(TableName.valueOf(table));
92        }
93      }
94    }
95  
96    @Override
97    public void nodeDataChanged(String path) {
98      if (aclZNode.equals(ZKUtil.getParent(path))) {
99        // update cache on an existing table node
100       String entry = ZKUtil.getNodeName(path);
101       try {
102         byte[] data = ZKUtil.getDataAndWatch(watcher, path);
103         refreshAuthManager(entry, data);
104       } catch (KeeperException ke) {
105         LOG.error("Error reading data from zookeeper for node " + entry, ke);
106         // only option is to abort
107         watcher.abort("Zookeeper error getting data for node " + entry, ke);
108       } catch (IOException ioe) {
109         LOG.error("Error reading permissions writables", ioe);
110       }
111     }
112   }
113 
114   @Override
115   public void nodeChildrenChanged(String path) {
116     if (path.equals(aclZNode)) {
117       // table permissions changed
118       try {
119         List<ZKUtil.NodeAndData> nodes =
120             ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
121         refreshNodes(nodes);
122       } catch (KeeperException ke) {
123         LOG.error("Error reading data from zookeeper for path "+path, ke);
124         watcher.abort("Zookeeper error get node children for path "+path, ke);
125       }
126     }
127   }
128 
129   private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
130     for (ZKUtil.NodeAndData n : nodes) {
131       if (n.isEmpty()) continue;
132       String path = n.getNode();
133       String entry = (ZKUtil.getNodeName(path));
134       try {
135         refreshAuthManager(entry, n.getData());
136       } catch (IOException ioe) {
137         LOG.error("Failed parsing permissions for table '" + entry +
138             "' from zk", ioe);
139       }
140     }
141   }
142 
143   private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
144     if (LOG.isDebugEnabled()) {
145       LOG.debug("Updating permissions cache from node "+entry+" with data: "+
146           Bytes.toStringBinary(nodeData));
147     }
148     if(AccessControlLists.isNamespaceEntry(entry)) {
149       authManager.refreshNamespaceCacheFromWritable(
150           AccessControlLists.fromNamespaceEntry(entry), nodeData);
151     } else {
152       authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
153     }
154   }
155 
156   /***
157    * Write a table's access controls to the permissions mirror in zookeeper
158    * @param entry
159    * @param permsData
160    */
161   public void writeToZookeeper(byte[] entry, byte[] permsData) {
162     String entryName = Bytes.toString(entry);
163     String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
164     zkNode = ZKUtil.joinZNode(zkNode, entryName);
165 
166     try {
167       ZKUtil.createWithParents(watcher, zkNode);
168       ZKUtil.updateExistingNodeData(watcher, zkNode, permsData, -1);
169     } catch (KeeperException e) {
170       LOG.error("Failed updating permissions for entry '" +
171           entryName + "'", e);
172       watcher.abort("Failed writing node "+zkNode+" to zookeeper", e);
173     }
174   }
175 }