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.hbase.classification.InterfaceAudience;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.hbase.DaemonThreadFactory;
26  import org.apache.hadoop.hbase.TableName;
27  import org.apache.hadoop.hbase.util.Bytes;
28  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
29  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
30  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
31  import org.apache.zookeeper.KeeperException;
32  
33  import java.io.Closeable;
34  import java.io.IOException;
35  import java.util.List;
36  import java.util.concurrent.Callable;
37  import java.util.concurrent.CountDownLatch;
38  import java.util.concurrent.ExecutionException;
39  import java.util.concurrent.ExecutorService;
40  import java.util.concurrent.Executors;
41  import java.util.concurrent.Future;
42  import java.util.concurrent.RejectedExecutionException;
43  import java.util.concurrent.atomic.AtomicReference;
44  
45  /**
46   * Handles synchronization of access control list entries and updates
47   * throughout all nodes in the cluster.  The {@link AccessController} instance
48   * on the {@code _acl_} table regions, creates a znode for each table as
49   * {@code /hbase/acl/tablename}, with the znode data containing a serialized
50   * list of the permissions granted for the table.  The {@code AccessController}
51   * instances on all other cluster hosts watch the znodes for updates, which
52   * trigger updates in the {@link TableAuthManager} permission cache.
53   */
54  @InterfaceAudience.Private
55  public class ZKPermissionWatcher extends ZooKeeperListener implements Closeable {
56    private static final Log LOG = LogFactory.getLog(ZKPermissionWatcher.class);
57    // parent node for permissions lists
58    static final String ACL_NODE = "acl";
59    private final TableAuthManager authManager;
60    private final String aclZNode;
61    private final CountDownLatch initialized = new CountDownLatch(1);
62    private final ExecutorService executor;
63    private Future<?> childrenChangedFuture;
64  
65    public ZKPermissionWatcher(ZooKeeperWatcher watcher,
66        TableAuthManager authManager, Configuration conf) {
67      super(watcher);
68      this.authManager = authManager;
69      String aclZnodeParent = conf.get("zookeeper.znode.acl.parent", ACL_NODE);
70      this.aclZNode = ZKUtil.joinZNode(watcher.baseZNode, aclZnodeParent);
71      executor = Executors.newSingleThreadExecutor(
72        new DaemonThreadFactory("zk-permission-watcher"));
73    }
74  
75    public void start() throws KeeperException {
76      try {
77        watcher.registerListener(this);
78        if (ZKUtil.watchAndCheckExists(watcher, aclZNode)) {
79          try {
80            executor.submit(new Callable<Void>() {
81              @Override
82              public Void call() throws KeeperException {
83                List<ZKUtil.NodeAndData> existing =
84                    ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
85                if (existing != null) {
86                  refreshNodes(existing);
87                }
88                return null;
89              }
90            }).get();
91          } catch (ExecutionException ex) {
92            if (ex.getCause() instanceof KeeperException) {
93              throw (KeeperException)ex.getCause();
94            } else {
95              throw new RuntimeException(ex.getCause());
96            }
97          } catch (InterruptedException ex) {
98            Thread.currentThread().interrupt();
99          }
100       }
101     } finally {
102       initialized.countDown();
103     }
104   }
105 
106   @Override
107   public void close() {
108     executor.shutdown();
109   }
110 
111   private void waitUntilStarted() {
112     try {
113       initialized.await();
114     } catch (InterruptedException e) {
115       LOG.warn("Interrupted while waiting for start", e);
116       Thread.currentThread().interrupt();
117     }
118   }
119 
120   @Override
121   public void nodeCreated(String path) {
122     waitUntilStarted();
123     if (path.equals(aclZNode)) {
124       asyncProcessNodeUpdate(new Runnable() {
125         @Override
126         public void run() {
127           try {
128             List<ZKUtil.NodeAndData> nodes =
129                 ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
130             refreshNodes(nodes);
131           } catch (KeeperException ke) {
132             LOG.error("Error reading data from zookeeper", ke);
133             // only option is to abort
134             watcher.abort("Zookeeper error obtaining acl node children", ke);
135           }
136         }
137       });
138     }
139   }
140 
141   @Override
142   public void nodeDeleted(final String path) {
143     waitUntilStarted();
144     if (aclZNode.equals(ZKUtil.getParent(path))) {
145       asyncProcessNodeUpdate(new Runnable() {
146         @Override
147         public void run() {
148           String table = ZKUtil.getNodeName(path);
149           if(AccessControlLists.isNamespaceEntry(table)) {
150             authManager.removeNamespace(Bytes.toBytes(table));
151           } else {
152             authManager.removeTable(TableName.valueOf(table));
153           }
154         }
155       });
156     }
157   }
158 
159   @Override
160   public void nodeDataChanged(final String path) {
161     waitUntilStarted();
162     if (aclZNode.equals(ZKUtil.getParent(path))) {
163       asyncProcessNodeUpdate(new Runnable() {
164         @Override
165         public void run() {
166           // update cache on an existing table node
167           String entry = ZKUtil.getNodeName(path);
168           try {
169             byte[] data = ZKUtil.getDataAndWatch(watcher, path);
170             refreshAuthManager(entry, data);
171           } catch (KeeperException ke) {
172             LOG.error("Error reading data from zookeeper for node " + entry, ke);
173             // only option is to abort
174             watcher.abort("Zookeeper error getting data for node " + entry, ke);
175           } catch (IOException ioe) {
176             LOG.error("Error reading permissions writables", ioe);
177           }
178         }
179       });
180     }
181   }
182 
183   @Override
184   public void nodeChildrenChanged(final String path) {
185     waitUntilStarted();
186     if (path.equals(aclZNode)) {
187       // preempt any existing nodeChildrenChanged event processing
188       if (childrenChangedFuture != null && !childrenChangedFuture.isDone()) {
189         boolean cancelled = childrenChangedFuture.cancel(true);
190         if (!cancelled) {
191           // task may have finished between our check and attempted cancel, this is fine.
192           if (!childrenChangedFuture.isDone()) {
193             LOG.warn("Could not cancel processing node children changed event, "
194               + "please file a JIRA and attach logs if possible.");
195           }
196         }
197       }
198 
199       childrenChangedFuture = asyncProcessNodeUpdate(new Runnable() {
200         @Override public void run() {
201           try {
202             final List<ZKUtil.NodeAndData> nodeList =
203               ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode, false);
204             refreshNodes(nodeList);
205 
206           } catch (KeeperException ke) {
207             String msg = "ZooKeeper error while reading node children data for path " + path;
208             LOG.error(msg, ke);
209             watcher.abort(msg, ke);
210           }
211         }
212       });
213     }
214   }
215 
216   private Future<?> asyncProcessNodeUpdate(Runnable runnable) {
217     if (!executor.isShutdown()) {
218       try {
219         return executor.submit(runnable);
220       } catch (RejectedExecutionException e) {
221         if (executor.isShutdown()) {
222           LOG.warn("aclZNode changed after ZKPermissionWatcher was shutdown");
223         } else {
224           throw e;
225         }
226       }
227     }
228     return null; // No task launched so there will be nothing to cancel later
229   }
230 
231   private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
232     for (ZKUtil.NodeAndData n : nodes) {
233       if (Thread.interrupted()) {
234         // Use Thread.interrupted so that we clear interrupt status
235         break;
236       }
237       if (n.isEmpty()) continue;
238       String path = n.getNode();
239       String entry = (ZKUtil.getNodeName(path));
240       try {
241         refreshAuthManager(entry, n.getData());
242       } catch (IOException ioe) {
243         LOG.error("Failed parsing permissions for table '" + entry +
244             "' from zk", ioe);
245       }
246     }
247   }
248 
249   private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
250     if (LOG.isDebugEnabled()) {
251       LOG.debug("Updating permissions cache from node "+entry+" with data: "+
252           Bytes.toStringBinary(nodeData));
253     }
254     if(AccessControlLists.isNamespaceEntry(entry)) {
255       authManager.refreshNamespaceCacheFromWritable(
256           AccessControlLists.fromNamespaceEntry(entry), nodeData);
257     } else {
258       authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
259     }
260   }
261 
262   /***
263    * Write a table's access controls to the permissions mirror in zookeeper
264    * @param entry
265    * @param permsData
266    */
267   public void writeToZookeeper(byte[] entry, byte[] permsData) {
268     String entryName = Bytes.toString(entry);
269     String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
270     zkNode = ZKUtil.joinZNode(zkNode, entryName);
271 
272     try {
273       ZKUtil.createWithParents(watcher, zkNode);
274       ZKUtil.updateExistingNodeData(watcher, zkNode, permsData, -1);
275     } catch (KeeperException e) {
276       LOG.error("Failed updating permissions for entry '" +
277           entryName + "'", e);
278       watcher.abort("Failed writing node "+zkNode+" to zookeeper", e);
279     }
280   }
281 
282   /***
283    * Delete the acl notify node of table
284    * @param tableName
285    */
286   public void deleteTableACLNode(final TableName tableName) {
287     String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
288     zkNode = ZKUtil.joinZNode(zkNode, tableName.getNameAsString());
289 
290     try {
291       ZKUtil.deleteNode(watcher, zkNode);
292     } catch (KeeperException.NoNodeException e) {
293       LOG.warn("No acl notify node of table '" + tableName + "'");
294     } catch (KeeperException e) {
295       LOG.error("Failed deleting acl node of table '" + tableName + "'", e);
296       watcher.abort("Failed deleting node " + zkNode, e);
297     }
298   }
299 
300   /***
301    * Delete the acl notify node of namespace
302    */
303   public void deleteNamespaceACLNode(final String namespace) {
304     String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
305     zkNode = ZKUtil.joinZNode(zkNode, AccessControlLists.NAMESPACE_PREFIX + namespace);
306 
307     try {
308       ZKUtil.deleteNode(watcher, zkNode);
309     } catch (KeeperException.NoNodeException e) {
310       LOG.warn("No acl notify node of namespace '" + namespace + "'");
311     } catch (KeeperException e) {
312       LOG.error("Failed deleting acl node of namespace '" + namespace + "'", e);
313       watcher.abort("Failed deleting node " + zkNode, e);
314     }
315   }
316 }