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