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