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