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 com.google.common.collect.ArrayListMultimap;
22  import com.google.common.collect.ListMultimap;
23  import com.google.common.collect.Lists;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.TableName;
28  import org.apache.hadoop.hbase.exceptions.DeserializationException;
29  import org.apache.hadoop.hbase.KeyValue;
30  import org.apache.hadoop.hbase.security.User;
31  import org.apache.hadoop.hbase.util.Bytes;
32  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
33  import org.apache.zookeeper.KeeperException;
34  
35  import java.io.*;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.concurrent.ConcurrentSkipListMap;
40  
41  /**
42   * Performs authorization checks for a given user's assigned permissions
43   */
44  public class TableAuthManager {
45    private static class PermissionCache<T extends Permission> {
46      /** Cache of user permissions */
47      private ListMultimap<String,T> userCache = ArrayListMultimap.create();
48      /** Cache of group permissions */
49      private ListMultimap<String,T> groupCache = ArrayListMultimap.create();
50  
51      public List<T> getUser(String user) {
52        return userCache.get(user);
53      }
54  
55      public void putUser(String user, T perm) {
56        userCache.put(user, perm);
57      }
58  
59      public List<T> replaceUser(String user, Iterable<? extends T> perms) {
60        return userCache.replaceValues(user, perms);
61      }
62  
63      public List<T> getGroup(String group) {
64        return groupCache.get(group);
65      }
66  
67      public void putGroup(String group, T perm) {
68        groupCache.put(group, perm);
69      }
70  
71      public List<T> replaceGroup(String group, Iterable<? extends T> perms) {
72        return groupCache.replaceValues(group, perms);
73      }
74  
75      /**
76       * Returns a combined map of user and group permissions, with group names prefixed by
77       * {@link AccessControlLists#GROUP_PREFIX}.
78       */
79      public ListMultimap<String,T> getAllPermissions() {
80        ListMultimap<String,T> tmp = ArrayListMultimap.create();
81        tmp.putAll(userCache);
82        for (String group : groupCache.keySet()) {
83          tmp.putAll(AccessControlLists.GROUP_PREFIX + group, groupCache.get(group));
84        }
85        return tmp;
86      }
87    }
88  
89    private static Log LOG = LogFactory.getLog(TableAuthManager.class);
90  
91    private static TableAuthManager instance;
92  
93    /** Cache of global permissions */
94    private volatile PermissionCache<Permission> globalCache;
95  
96    private ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>> tableCache =
97        new ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>>();
98  
99    private ConcurrentSkipListMap<String, PermissionCache<TablePermission>> nsCache =
100     new ConcurrentSkipListMap<String, PermissionCache<TablePermission>>();
101 
102   private Configuration conf;
103   private ZKPermissionWatcher zkperms;
104 
105   private TableAuthManager(ZooKeeperWatcher watcher, Configuration conf)
106       throws IOException {
107     this.conf = conf;
108 
109     // initialize global permissions based on configuration
110     globalCache = initGlobal(conf);
111 
112     this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
113     try {
114       this.zkperms.start();
115     } catch (KeeperException ke) {
116       LOG.error("ZooKeeper initialization failed", ke);
117     }
118   }
119 
120   /**
121    * Returns a new {@code PermissionCache} initialized with permission assignments
122    * from the {@code hbase.superuser} configuration key.
123    */
124   private PermissionCache<Permission> initGlobal(Configuration conf) throws IOException {
125     User user = User.getCurrent();
126     if (user == null) {
127       throw new IOException("Unable to obtain the current user, " +
128           "authorization checks for internal operations will not work correctly!");
129     }
130     PermissionCache<Permission> newCache = new PermissionCache<Permission>();
131     String currentUser = user.getShortName();
132 
133     // the system user is always included
134     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
135         AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
136     if (superusers != null) {
137       for (String name : superusers) {
138         if (AccessControlLists.isGroupPrincipal(name)) {
139           newCache.putGroup(AccessControlLists.getGroupName(name),
140               new Permission(Permission.Action.values()));
141         } else {
142           newCache.putUser(name, new Permission(Permission.Action.values()));
143         }
144       }
145     }
146     return newCache;
147   }
148 
149   public ZKPermissionWatcher getZKPermissionWatcher() {
150     return this.zkperms;
151   }
152 
153   public void refreshTableCacheFromWritable(TableName table,
154                                        byte[] data) throws IOException {
155     if (data != null && data.length > 0) {
156       ListMultimap<String,TablePermission> perms;
157       try {
158         perms = AccessControlLists.readPermissions(data, conf);
159       } catch (DeserializationException e) {
160         throw new IOException(e);
161       }
162 
163       if (perms != null) {
164         if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
165           updateGlobalCache(perms);
166         } else {
167           updateTableCache(table, perms);
168         }
169       }
170     } else {
171       LOG.debug("Skipping permission cache refresh because writable data is empty");
172     }
173   }
174 
175   public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException {
176     if (data != null && data.length > 0) {
177       ListMultimap<String,TablePermission> perms;
178       try {
179         perms = AccessControlLists.readPermissions(data, conf);
180       } catch (DeserializationException e) {
181         throw new IOException(e);
182       }
183       if (perms != null) {
184         updateNsCache(namespace, perms);
185       }
186     } else {
187       LOG.debug("Skipping permission cache refresh because writable data is empty");
188     }
189   }
190 
191   /**
192    * Updates the internal global permissions cache
193    *
194    * @param userPerms
195    */
196   private void updateGlobalCache(ListMultimap<String,TablePermission> userPerms) {
197     PermissionCache<Permission> newCache = null;
198     try {
199       newCache = initGlobal(conf);
200       for (Map.Entry<String,TablePermission> entry : userPerms.entries()) {
201         if (AccessControlLists.isGroupPrincipal(entry.getKey())) {
202           newCache.putGroup(AccessControlLists.getGroupName(entry.getKey()),
203               new Permission(entry.getValue().getActions()));
204         } else {
205           newCache.putUser(entry.getKey(), new Permission(entry.getValue().getActions()));
206         }
207       }
208       globalCache = newCache;
209     } catch (IOException e) {
210       // Never happens
211       LOG.error("Error occured while updating the global cache", e);
212     }
213   }
214 
215   /**
216    * Updates the internal permissions cache for a single table, splitting
217    * the permissions listed into separate caches for users and groups to optimize
218    * group lookups.
219    * 
220    * @param table
221    * @param tablePerms
222    */
223   private void updateTableCache(TableName table,
224                                 ListMultimap<String,TablePermission> tablePerms) {
225     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
226 
227     for (Map.Entry<String,TablePermission> entry : tablePerms.entries()) {
228       if (AccessControlLists.isGroupPrincipal(entry.getKey())) {
229         newTablePerms.putGroup(AccessControlLists.getGroupName(entry.getKey()), entry.getValue());
230       } else {
231         newTablePerms.putUser(entry.getKey(), entry.getValue());
232       }
233     }
234 
235     tableCache.put(table, newTablePerms);
236   }
237 
238   /**
239    * Updates the internal permissions cache for a single table, splitting
240    * the permissions listed into separate caches for users and groups to optimize
241    * group lookups.
242    *
243    * @param namespace
244    * @param tablePerms
245    */
246   private void updateNsCache(String namespace,
247                              ListMultimap<String, TablePermission> tablePerms) {
248     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
249 
250     for (Map.Entry<String, TablePermission> entry : tablePerms.entries()) {
251       if (AccessControlLists.isGroupPrincipal(entry.getKey())) {
252         newTablePerms.putGroup(AccessControlLists.getGroupName(entry.getKey()), entry.getValue());
253       } else {
254         newTablePerms.putUser(entry.getKey(), entry.getValue());
255       }
256     }
257 
258     nsCache.put(namespace, newTablePerms);
259   }
260 
261   private PermissionCache<TablePermission> getTablePermissions(TableName table) {
262     if (!tableCache.containsKey(table)) {
263       tableCache.putIfAbsent(table, new PermissionCache<TablePermission>());
264     }
265     return tableCache.get(table);
266   }
267 
268   private PermissionCache<TablePermission> getNamespacePermissions(String namespace) {
269     if (!nsCache.containsKey(namespace)) {
270       nsCache.putIfAbsent(namespace, new PermissionCache<TablePermission>());
271     }
272     return nsCache.get(namespace);
273   }
274 
275   /**
276    * Authorizes a global permission
277    * @param perms
278    * @param action
279    * @return
280    */
281   private boolean authorize(List<Permission> perms, Permission.Action action) {
282     if (perms != null) {
283       for (Permission p : perms) {
284         if (p.implies(action)) {
285           return true;
286         }
287       }
288     } else if (LOG.isDebugEnabled()) {
289       LOG.debug("No permissions found");
290     }
291 
292     return false;
293   }
294 
295   /**
296    * Authorize a global permission based on ACLs for the given user and the
297    * user's groups.
298    * @param user
299    * @param action
300    * @return true if known and authorized, false otherwise
301    */
302   public boolean authorize(User user, Permission.Action action) {
303     if (user == null) {
304       return false;
305     }
306 
307     if (authorize(globalCache.getUser(user.getShortName()), action)) {
308       return true;
309     }
310 
311     String[] groups = user.getGroupNames();
312     if (groups != null) {
313       for (String group : groups) {
314         if (authorize(globalCache.getGroup(group), action)) {
315           return true;
316         }
317       }
318     }
319     return false;
320   }
321 
322   private boolean authorize(List<TablePermission> perms,
323                             TableName table, byte[] family,
324                             Permission.Action action) {
325     return authorize(perms, table, family, null, action);
326   }
327 
328   private boolean authorize(List<TablePermission> perms,
329                             TableName table, byte[] family,
330                             byte[] qualifier, Permission.Action action) {
331     if (perms != null) {
332       for (TablePermission p : perms) {
333         if (p.implies(table, family, qualifier, action)) {
334           return true;
335         }
336       }
337     } else if (LOG.isDebugEnabled()) {
338       LOG.debug("No permissions found for table="+table);
339     }
340     return false;
341   }
342 
343   public boolean authorize(User user, TableName table, KeyValue kv,
344       Permission.Action action) {
345     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
346     if (tablePerms != null) {
347       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
348       if (authorize(userPerms, table, kv, action)) {
349         return true;
350       }
351 
352       String[] groupNames = user.getGroupNames();
353       if (groupNames != null) {
354         for (String group : groupNames) {
355           List<TablePermission> groupPerms = tablePerms.getGroup(group);
356           if (authorize(groupPerms, table, kv, action)) {
357             return true;
358           }
359         }
360       }
361     }
362     return false;
363   }
364 
365   private boolean authorize(List<TablePermission> perms, TableName table, KeyValue kv,
366       Permission.Action action) {
367     if (perms != null) {
368       for (TablePermission p : perms) {
369         if (p.implies(table, kv, action)) {
370           return true;
371         }
372       }
373     } else if (LOG.isDebugEnabled()) {
374       LOG.debug("No permissions for authorize() check, table=" +
375           table);
376     }
377 
378     return false;
379   }
380 
381   public boolean authorize(User user, String namespace, Permission.Action action) {
382     if (authorizeUser(user.getShortName(), action)) {
383       return true;
384     }
385     PermissionCache<TablePermission> tablePerms = nsCache.get(namespace);
386     if (tablePerms != null) {
387       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
388       if (authorize(userPerms, namespace, action)) {
389         return true;
390       }
391 
392       String[] groupNames = user.getGroupNames();
393       if (groupNames != null) {
394         for (String group : groupNames) {
395           List<TablePermission> groupPerms = tablePerms.getGroup(group);
396           if (authorize(groupPerms, namespace, action)) {
397             return true;
398           }
399         }
400       }
401     }
402     return false;
403   }
404 
405   private boolean authorize(List<TablePermission> perms, String namespace,
406                             Permission.Action action) {
407     if (perms != null) {
408       for (TablePermission p : perms) {
409         if (p.implies(namespace, action)) {
410           return true;
411         }
412       }
413     } else if (LOG.isDebugEnabled()) {
414       LOG.debug("No permissions for authorize() check, table=" + namespace);
415     }
416 
417     return false;
418   }
419 
420   /**
421    * Checks global authorization for a specific action for a user, based on the
422    * stored user permissions.
423    */
424   public boolean authorizeUser(String username, Permission.Action action) {
425     return authorize(globalCache.getUser(username), action);
426   }
427 
428   /**
429    * Checks authorization to a given table and column family for a user, based on the
430    * stored user permissions.
431    *
432    * @param username
433    * @param table
434    * @param family
435    * @param action
436    * @return true if known and authorized, false otherwise
437    */
438   public boolean authorizeUser(String username, TableName table, byte[] family,
439       Permission.Action action) {
440     return authorizeUser(username, table, family, null, action);
441   }
442 
443   public boolean authorizeUser(String username, TableName table, byte[] family,
444       byte[] qualifier, Permission.Action action) {
445     // global authorization supercedes table level
446     if (authorizeUser(username, action)) {
447       return true;
448     }
449     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
450     return authorize(getTablePermissions(table).getUser(username), table, family,
451         qualifier, action);
452   }
453 
454 
455   /**
456    * Checks authorization for a given action for a group, based on the stored
457    * permissions.
458    */
459   public boolean authorizeGroup(String groupName, Permission.Action action) {
460     return authorize(globalCache.getGroup(groupName), action);
461   }
462 
463   /**
464    * Checks authorization to a given table and column family for a group, based
465    * on the stored permissions. 
466    * @param groupName
467    * @param table
468    * @param family
469    * @param action
470    * @return true if known and authorized, false otherwise
471    */
472   public boolean authorizeGroup(String groupName, TableName table, byte[] family,
473       Permission.Action action) {
474     // global authorization supercedes table level
475     if (authorizeGroup(groupName, action)) {
476       return true;
477     }
478     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
479     return authorize(getTablePermissions(table).getGroup(groupName), table, family, action);
480   }
481 
482   public boolean authorize(User user, TableName table, byte[] family,
483       byte[] qualifier, Permission.Action action) {
484     if (authorizeUser(user.getShortName(), table, family, qualifier, action)) {
485       return true;
486     }
487 
488     String[] groups = user.getGroupNames();
489     if (groups != null) {
490       for (String group : groups) {
491         if (authorizeGroup(group, table, family, action)) {
492           return true;
493         }
494       }
495     }
496     return false;
497   }
498 
499   public boolean authorize(User user, TableName table, byte[] family,
500       Permission.Action action) {
501     return authorize(user, table, family, null, action);
502   }
503 
504   /**
505    * Returns true if the given user has a {@link TablePermission} matching up
506    * to the column family portion of a permission.  Note that this permission
507    * may be scoped to a given column qualifier and does not guarantee that
508    * authorize() on the same column family would return true.
509    */
510   public boolean matchPermission(User user,
511       TableName table, byte[] family, Permission.Action action) {
512     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
513     if (tablePerms != null) {
514       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
515       if (userPerms != null) {
516         for (TablePermission p : userPerms) {
517           if (p.matchesFamily(table, family, action)) {
518             return true;
519           }
520         }
521       }
522 
523       String[] groups = user.getGroupNames();
524       if (groups != null) {
525         for (String group : groups) {
526           List<TablePermission> groupPerms = tablePerms.getGroup(group);
527           if (groupPerms != null) {
528             for (TablePermission p : groupPerms) {
529               if (p.matchesFamily(table, family, action)) {
530                 return true;
531               }
532             }
533           }
534         }
535       }
536     }
537 
538     return false;
539   }
540 
541   public boolean matchPermission(User user,
542       TableName table, byte[] family, byte[] qualifier,
543       Permission.Action action) {
544     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
545     if (tablePerms != null) {
546       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
547       if (userPerms != null) {
548         for (TablePermission p : userPerms) {
549           if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
550             return true;
551           }
552         }
553       }
554 
555       String[] groups = user.getGroupNames();
556       if (groups != null) {
557         for (String group : groups) {
558           List<TablePermission> groupPerms = tablePerms.getGroup(group);
559           if (groupPerms != null) {
560             for (TablePermission p : groupPerms) {
561               if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
562                 return true;
563               }
564             }
565           }
566         }
567       }
568     }
569     return false;
570   }
571 
572   public void removeNamespace(byte[] ns) {
573     nsCache.remove(ns);
574   }
575 
576   public void removeTable(TableName table) {
577     tableCache.remove(table);
578   }
579 
580   /**
581    * Overwrites the existing permission set for a given user for a table, and
582    * triggers an update for zookeeper synchronization.
583    * @param username
584    * @param table
585    * @param perms
586    */
587   public void setTableUserPermissions(String username, TableName table,
588       List<TablePermission> perms) {
589     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
590     tablePerms.replaceUser(username, perms);
591     writeTableToZooKeeper(table, tablePerms);
592   }
593 
594   /**
595    * Overwrites the existing permission set for a group and triggers an update
596    * for zookeeper synchronization.
597    * @param group
598    * @param table
599    * @param perms
600    */
601   public void setTableGroupPermissions(String group, TableName table,
602       List<TablePermission> perms) {
603     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
604     tablePerms.replaceGroup(group, perms);
605     writeTableToZooKeeper(table, tablePerms);
606   }
607 
608   /**
609    * Overwrites the existing permission set for a given user for a table, and
610    * triggers an update for zookeeper synchronization.
611    * @param username
612    * @param namespace
613    * @param perms
614    */
615   public void setNamespaceUserPermissions(String username, String namespace,
616       List<TablePermission> perms) {
617     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
618     tablePerms.replaceUser(username, perms);
619     writeNamespaceToZooKeeper(namespace, tablePerms);
620   }
621 
622   /**
623    * Overwrites the existing permission set for a group and triggers an update
624    * for zookeeper synchronization.
625    * @param group
626    * @param namespace
627    * @param perms
628    */
629   public void setNamespaceGroupPermissions(String group, String namespace,
630       List<TablePermission> perms) {
631     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
632     tablePerms.replaceGroup(group, perms);
633     writeNamespaceToZooKeeper(namespace, tablePerms);
634   }
635 
636   public void writeTableToZooKeeper(TableName table,
637       PermissionCache<TablePermission> tablePerms) {
638     byte[] serialized = new byte[0];
639     if (tablePerms != null) {
640       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
641     }
642     zkperms.writeToZookeeper(table.getName(), serialized);
643   }
644 
645   public void writeNamespaceToZooKeeper(String namespace,
646       PermissionCache<TablePermission> tablePerms) {
647     byte[] serialized = new byte[0];
648     if (tablePerms != null) {
649       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
650     }
651     zkperms.writeToZookeeper(Bytes.toBytes(AccessControlLists.toNamespaceEntry(namespace)),
652         serialized);
653   }
654 
655   static Map<ZooKeeperWatcher,TableAuthManager> managerMap =
656     new HashMap<ZooKeeperWatcher,TableAuthManager>();
657 
658   public synchronized static TableAuthManager get(
659       ZooKeeperWatcher watcher, Configuration conf) throws IOException {
660     instance = managerMap.get(watcher);
661     if (instance == null) {
662       instance = new TableAuthManager(watcher, conf);
663       managerMap.put(watcher, instance);
664     }
665     return instance;
666   }
667 }