View Javadoc

1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *     http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.apache.hadoop.hbase.security.access;
16  
17  import java.io.IOException;
18  import java.net.InetAddress;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.TreeSet;
27  
28  import com.google.protobuf.RpcCallback;
29  import com.google.protobuf.RpcController;
30  import com.google.protobuf.Service;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.hbase.Cell;
35  import org.apache.hadoop.hbase.CoprocessorEnvironment;
36  import org.apache.hadoop.hbase.HColumnDescriptor;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HTableDescriptor;
39  import org.apache.hadoop.hbase.KeyValue;
40  import org.apache.hadoop.hbase.KeyValueUtil;
41  import org.apache.hadoop.hbase.ServerName;
42  import org.apache.hadoop.hbase.client.Append;
43  import org.apache.hadoop.hbase.client.Delete;
44  import org.apache.hadoop.hbase.client.Get;
45  import org.apache.hadoop.hbase.client.Increment;
46  import org.apache.hadoop.hbase.client.Put;
47  import org.apache.hadoop.hbase.client.Result;
48  import org.apache.hadoop.hbase.client.Scan;
49  import org.apache.hadoop.hbase.coprocessor.*;
50  import org.apache.hadoop.hbase.exceptions.CoprocessorException;
51  import org.apache.hadoop.hbase.filter.CompareFilter;
52  import org.apache.hadoop.hbase.filter.FilterList;
53  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
54  import org.apache.hadoop.hbase.ipc.RequestContext;
55  import org.apache.hadoop.hbase.master.RegionPlan;
56  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
57  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
58  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
59  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
60  import org.apache.hadoop.hbase.regionserver.HRegion;
61  import org.apache.hadoop.hbase.regionserver.InternalScanner;
62  import org.apache.hadoop.hbase.regionserver.RegionScanner;
63  import org.apache.hadoop.hbase.regionserver.Store;
64  import org.apache.hadoop.hbase.regionserver.ScanType;
65  import org.apache.hadoop.hbase.regionserver.StoreFile;
66  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
67  import org.apache.hadoop.hbase.exceptions.AccessDeniedException;
68  import org.apache.hadoop.hbase.security.User;
69  import org.apache.hadoop.hbase.security.access.Permission.Action;
70  import org.apache.hadoop.hbase.util.Bytes;
71  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
72  import org.apache.hadoop.hbase.util.Pair;
73  
74  import com.google.common.collect.ImmutableSet;
75  import com.google.common.collect.ListMultimap;
76  import com.google.common.collect.Lists;
77  import com.google.common.collect.MapMaker;
78  import com.google.common.collect.Maps;
79  import com.google.common.collect.Sets;
80  
81  import static org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
82  
83  /**
84   * Provides basic authorization checks for data access and administrative
85   * operations.
86   *
87   * <p>
88   * {@code AccessController} performs authorization checks for HBase operations
89   * based on:
90   * <ul>
91   *   <li>the identity of the user performing the operation</li>
92   *   <li>the scope over which the operation is performed, in increasing
93   *   specificity: global, table, column family, or qualifier</li>
94   *   <li>the type of action being performed (as mapped to
95   *   {@link Permission.Action} values)</li>
96   * </ul>
97   * If the authorization check fails, an {@link AccessDeniedException}
98   * will be thrown for the operation.
99   * </p>
100  *
101  * <p>
102  * To perform authorization checks, {@code AccessController} relies on the
103  * {@link org.apache.hadoop.hbase.ipc.RpcServerEngine} being loaded to provide
104  * the user identities for remote requests.
105  * </p>
106  *
107  * <p>
108  * The access control lists used for authorization can be manipulated via the
109  * exposed {@link AccessControlService} Interface implementation, and the associated
110  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
111  * commands.
112  * </p>
113  */
114 public class AccessController extends BaseRegionObserver
115     implements MasterObserver, RegionServerObserver,
116       AccessControlService.Interface, CoprocessorService {
117 
118   public static final Log LOG = LogFactory.getLog(AccessController.class);
119 
120   private static final Log AUDITLOG =
121     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
122 
123   TableAuthManager authManager = null;
124 
125   // flags if we are running on a region of the _acl_ table
126   boolean aclRegion = false;
127 
128   // defined only for Endpoint implementation, so it can have way to
129   // access region services.
130   private RegionCoprocessorEnvironment regionEnv;
131 
132   /** Mapping of scanner instances to the user who created them */
133   private Map<InternalScanner,String> scannerOwners =
134       new MapMaker().weakKeys().makeMap();
135 
136   void initialize(RegionCoprocessorEnvironment e) throws IOException {
137     final HRegion region = e.getRegion();
138 
139     Map<byte[],ListMultimap<String,TablePermission>> tables =
140         AccessControlLists.loadAll(region);
141     // For each table, write out the table's permissions to the respective
142     // znode for that table.
143     for (Map.Entry<byte[],ListMultimap<String,TablePermission>> t:
144       tables.entrySet()) {
145       byte[] table = t.getKey();
146       ListMultimap<String,TablePermission> perms = t.getValue();
147       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, e.getConfiguration());
148       this.authManager.getZKPermissionWatcher().writeToZookeeper(table, serialized);
149     }
150   }
151 
152   /**
153    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
154    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
155    * table updates.
156    */
157   void updateACL(RegionCoprocessorEnvironment e,
158       final Map<byte[], List<? extends Cell>> familyMap) {
159     Set<byte[]> tableSet = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
160     for (Map.Entry<byte[], List<? extends Cell>> f : familyMap.entrySet()) {
161       List<? extends Cell> cells = f.getValue();
162       for (Cell cell: cells) {
163         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
164         if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
165             kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
166             AccessControlLists.ACL_LIST_FAMILY.length)) {
167           tableSet.add(kv.getRow());
168         }
169       }
170     }
171 
172     ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
173     Configuration conf = regionEnv.getConfiguration();
174     for (byte[] tableName: tableSet) {
175       try {
176         ListMultimap<String,TablePermission> perms =
177           AccessControlLists.getTablePermissions(conf, tableName);
178         byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
179         zkw.writeToZookeeper(tableName, serialized);
180       } catch (IOException ex) {
181         LOG.error("Failed updating permissions mirror for '" + Bytes.toString(tableName) + "'", ex);
182       }
183     }
184   }
185 
186   /**
187    * Check the current user for authorization to perform a specific action
188    * against the given set of row data.
189    *
190    * <p>Note: Ordering of the authorization checks
191    * has been carefully optimized to short-circuit the most common requests
192    * and minimize the amount of processing required.</p>
193    *
194    * @param permRequest the action being requested
195    * @param e the coprocessor environment
196    * @param families the map of column families to qualifiers present in
197    * the request
198    * @return
199    */
200   AuthResult permissionGranted(String request, User user, Permission.Action permRequest,
201       RegionCoprocessorEnvironment e,
202       Map<byte [], ? extends Collection<?>> families) {
203     HRegionInfo hri = e.getRegion().getRegionInfo();
204     byte[] tableName = hri.getTableName();
205 
206     // 1. All users need read access to .META. table.
207     // this is a very common operation, so deal with it quickly.
208     if (hri.isMetaRegion()) {
209       if (permRequest == Permission.Action.READ) {
210         return AuthResult.allow(request, "All users allowed", user,
211           permRequest, tableName, families);
212       }
213     }
214 
215     if (user == null) {
216       return AuthResult.deny(request, "No user associated with request!", null,
217         permRequest, tableName, families);
218     }
219 
220     // Users with CREATE/ADMIN rights need to modify .META. and _acl_ table
221     // e.g. When a new table is created a new entry in .META. is added,
222     // so the user need to be allowed to write on it.
223     // e.g. When a table is removed an entry is removed from .META. and _acl_
224     // and the user need to be allowed to write on both tables.
225     if (permRequest == Permission.Action.WRITE &&
226        (hri.isMetaRegion() ||
227         Bytes.equals(tableName, AccessControlLists.ACL_GLOBAL_NAME)) &&
228        (authManager.authorize(user, Permission.Action.CREATE) ||
229         authManager.authorize(user, Permission.Action.ADMIN)))
230     {
231        return AuthResult.allow(request, "Table permission granted", user,
232         permRequest, tableName, families);
233     }
234 
235     // 2. check for the table-level, if successful we can short-circuit
236     if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
237       return AuthResult.allow(request, "Table permission granted", user,
238         permRequest, tableName, families);
239     }
240 
241     // 3. check permissions against the requested families
242     if (families != null && families.size() > 0) {
243       // all families must pass
244       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
245         // a) check for family level access
246         if (authManager.authorize(user, tableName, family.getKey(),
247             permRequest)) {
248           continue;  // family-level permission overrides per-qualifier
249         }
250 
251         // b) qualifier level access can still succeed
252         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
253           if (family.getValue() instanceof Set) {
254             // for each qualifier of the family
255             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
256             for (byte[] qualifier : familySet) {
257               if (!authManager.authorize(user, tableName, family.getKey(),
258                                          qualifier, permRequest)) {
259                 return AuthResult.deny(request, "Failed qualifier check", user,
260                     permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
261               }
262             }
263           } else if (family.getValue() instanceof List) { // List<KeyValue>
264             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
265             for (KeyValue kv : kvList) {
266               if (!authManager.authorize(user, tableName, family.getKey(),
267                       kv.getQualifier(), permRequest)) {
268                 return AuthResult.deny(request, "Failed qualifier check", user,
269                     permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
270               }
271             }
272           }
273         } else {
274           // no qualifiers and family-level check already failed
275           return AuthResult.deny(request, "Failed family check", user, permRequest,
276               tableName, makeFamilyMap(family.getKey(), null));
277         }
278       }
279 
280       // all family checks passed
281       return AuthResult.allow(request, "All family checks passed", user, permRequest,
282           tableName, families);
283     }
284 
285     // 4. no families to check and table level access failed
286     return AuthResult.deny(request, "No families to check and table permission failed",
287         user, permRequest, tableName, families);
288   }
289 
290   private void logResult(AuthResult result) {
291     if (AUDITLOG.isTraceEnabled()) {
292       RequestContext ctx = RequestContext.get();
293       InetAddress remoteAddr = null;
294       if (ctx != null) {
295         remoteAddr = ctx.getRemoteAddress();
296       }
297       AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
298           " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
299           "; reason: " + result.getReason() +
300           "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
301           "; request: " + result.getRequest() +
302           "; context: " + result.toContextString());
303     }
304   }
305 
306   /**
307    * Returns the active user to which authorization checks should be applied.
308    * If we are in the context of an RPC call, the remote user is used,
309    * otherwise the currently logged in user is used.
310    */
311   private User getActiveUser() throws IOException {
312     User user = RequestContext.getRequestUser();
313     if (!RequestContext.isInRequestContext()) {
314       // for non-rpc handling, fallback to system user
315       user = User.getCurrent();
316     }
317     return user;
318   }
319 
320   /**
321    * Authorizes that the current user has any of the given permissions for the
322    * given table, column family and column qualifier.
323    * @param tableName Table requested
324    * @param family Column family requested
325    * @param qualifier Column qualifier requested
326    * @throws IOException if obtaining the current user fails
327    * @throws AccessDeniedException if user has no authorization
328    */
329   private void requirePermission(String request, byte[] tableName, byte[] family, byte[] qualifier,
330       Action... permissions) throws IOException {
331     User user = getActiveUser();
332     AuthResult result = null;
333 
334     for (Action permission : permissions) {
335       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
336         result = AuthResult.allow(request, "Table permission granted", user,
337                                   permission, tableName, family, qualifier);
338         break;
339       } else {
340         // rest of the world
341         result = AuthResult.deny(request, "Insufficient permissions", user,
342                                  permission, tableName, family, qualifier);
343       }
344     }
345     logResult(result);
346     if (!result.isAllowed()) {
347       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
348     }
349   }
350 
351   /**
352    * Authorizes that the current user has global privileges for the given action.
353    * @param perm The action being requested
354    * @throws IOException if obtaining the current user fails
355    * @throws AccessDeniedException if authorization is denied
356    */
357   private void requirePermission(String request, Permission.Action perm) throws IOException {
358     requireGlobalPermission(request, perm, null, null);
359   }
360 
361   /**
362    * Authorizes that the current user has permission to perform the given
363    * action on the set of table column families.
364    * @param perm Action that is required
365    * @param env The current coprocessor environment
366    * @param families The map of column families-qualifiers.
367    * @throws AccessDeniedException if the authorization check failed
368    */
369   private void requirePermission(String request, Permission.Action perm,
370         RegionCoprocessorEnvironment env,
371         Map<byte[], ? extends Collection<?>> families)
372       throws IOException {
373     User user = getActiveUser();
374     AuthResult result = permissionGranted(request, user, perm, env, families);
375     logResult(result);
376 
377     if (!result.isAllowed()) {
378       throw new AccessDeniedException("Insufficient permissions (table=" +
379         env.getRegion().getTableDesc().getNameAsString()+
380         ((families != null && families.size() > 0) ? ", family: " +
381         result.toFamilyString() : "") + ", action=" +
382         perm.toString() + ")");
383     }
384   }
385 
386   /**
387    * Checks that the user has the given global permission. The generated
388    * audit log message will contain context information for the operation
389    * being authorized, based on the given parameters.
390    * @param perm Action being requested
391    * @param tableName Affected table name.
392    * @param familiMap Affected column families.
393    */
394   private void requireGlobalPermission(String request, Permission.Action perm, byte[] tableName,
395       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
396     User user = getActiveUser();
397     if (authManager.authorize(user, perm)) {
398       logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap));
399     } else {
400       logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap));
401       throw new AccessDeniedException("Insufficient permissions for user '" +
402           (user != null ? user.getShortName() : "null") +"' (global, action=" +
403           perm.toString() + ")");
404     }
405   }
406 
407   /**
408    * Returns <code>true</code> if the current user is allowed the given action
409    * over at least one of the column qualifiers in the given column families.
410    */
411   private boolean hasFamilyQualifierPermission(User user,
412       Permission.Action perm,
413       RegionCoprocessorEnvironment env,
414       Map<byte[], ? extends Set<byte[]>> familyMap)
415     throws IOException {
416     HRegionInfo hri = env.getRegion().getRegionInfo();
417     byte[] tableName = hri.getTableName();
418 
419     if (user == null) {
420       return false;
421     }
422 
423     if (familyMap != null && familyMap.size() > 0) {
424       // at least one family must be allowed
425       for (Map.Entry<byte[], ? extends Set<byte[]>> family :
426           familyMap.entrySet()) {
427         if (family.getValue() != null && !family.getValue().isEmpty()) {
428           for (byte[] qualifier : family.getValue()) {
429             if (authManager.matchPermission(user, tableName,
430                 family.getKey(), qualifier, perm)) {
431               return true;
432             }
433           }
434         } else {
435           if (authManager.matchPermission(user, tableName, family.getKey(),
436               perm)) {
437             return true;
438           }
439         }
440       }
441     } else if (LOG.isDebugEnabled()) {
442       LOG.debug("Empty family map passed for permission check");
443     }
444 
445     return false;
446   }
447 
448   /* ---- MasterObserver implementation ---- */
449   public void start(CoprocessorEnvironment env) throws IOException {
450 
451     ZooKeeperWatcher zk = null;
452     if (env instanceof MasterCoprocessorEnvironment) {
453       // if running on HMaster
454       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
455       zk = mEnv.getMasterServices().getZooKeeper();      
456     } else if (env instanceof RegionServerCoprocessorEnvironment) {      
457       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
458       zk = rsEnv.getRegionServerServices().getZooKeeper();      
459     } else if (env instanceof RegionCoprocessorEnvironment) {
460       // if running at region
461       regionEnv = (RegionCoprocessorEnvironment) env;
462       zk = regionEnv.getRegionServerServices().getZooKeeper();
463     }
464 
465     // If zk is null or IOException while obtaining auth manager,
466     // throw RuntimeException so that the coprocessor is unloaded.
467     if (zk != null) {
468       try {
469         this.authManager = TableAuthManager.get(zk, env.getConfiguration());
470       } catch (IOException ioe) {
471         throw new RuntimeException("Error obtaining TableAuthManager", ioe);
472       }
473     } else {
474       throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
475     }
476   }
477 
478   public void stop(CoprocessorEnvironment env) {
479 
480   }
481 
482   @Override
483   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
484       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
485     Set<byte[]> families = desc.getFamiliesKeys();
486     Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
487     for (byte[] family: families) {
488       familyMap.put(family, null);
489     }
490     requireGlobalPermission("createTable", Permission.Action.CREATE, desc.getName(), familyMap);
491   }
492 
493   @Override
494   public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
495       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
496 
497   @Override
498   public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
499       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
500     if (!AccessControlLists.isAclTable(desc)) {
501       String owner = desc.getOwnerString();
502       // default the table owner to current user, if not specified.
503       if (owner == null) owner = getActiveUser().getShortName();
504       UserPermission userperm = new UserPermission(Bytes.toBytes(owner), desc.getName(), null,
505           Action.values());
506       AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
507     }
508   }
509 
510   @Override
511   public void postCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
512       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
513 
514   @Override
515   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
516       throws IOException {
517     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
518   }
519 
520   @Override
521   public void preDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
522       byte[] tableName) throws IOException {}
523   @Override
524   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
525       byte[] tableName) throws IOException {
526     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName);
527   }
528   @Override
529   public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
530       byte[] tableName) throws IOException {}
531 
532   @Override
533   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
534       HTableDescriptor htd) throws IOException {
535     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
536   }
537 
538   @Override
539   public void preModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
540       byte[] tableName, HTableDescriptor htd) throws IOException {}
541 
542   @Override
543   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
544       byte[] tableName, HTableDescriptor htd) throws IOException {
545     String owner = htd.getOwnerString();
546     // default the table owner to current user, if not specified.
547     if (owner == null) owner = getActiveUser().getShortName();
548     UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getName(), null,
549         Action.values());
550     AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
551   }
552 
553   @Override
554   public void postModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
555       byte[] tableName, HTableDescriptor htd) throws IOException {}
556 
557 
558   @Override
559   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
560       HColumnDescriptor column) throws IOException {
561     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
562   }
563 
564   @Override
565   public void preAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
566       byte[] tableName, HColumnDescriptor column) throws IOException {}
567   @Override
568   public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> c,
569       byte[] tableName, HColumnDescriptor column) throws IOException {}
570   @Override
571   public void postAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
572       byte[] tableName, HColumnDescriptor column) throws IOException {}
573 
574   @Override
575   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
576       HColumnDescriptor descriptor) throws IOException {
577     requirePermission("modifyColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
578   }
579 
580   @Override
581   public void preModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
582       byte[] tableName, HColumnDescriptor descriptor) throws IOException {}
583   @Override
584   public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c,
585       byte[] tableName, HColumnDescriptor descriptor) throws IOException {}
586   @Override
587   public void postModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
588       byte[] tableName, HColumnDescriptor descriptor) throws IOException {}
589 
590 
591   @Override
592   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
593       byte[] col) throws IOException {
594     requirePermission("deleteColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
595   }
596 
597   @Override
598   public void preDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
599       byte[] tableName, byte[] col) throws IOException {}
600   @Override
601   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
602       byte[] tableName, byte[] col) throws IOException {
603     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(),
604                                               tableName, col);
605   }
606   @Override
607   public void postDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
608       byte[] tableName, byte[] col) throws IOException {}
609 
610   @Override
611   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
612       throws IOException {
613     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
614   }
615 
616   @Override
617   public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
618       byte[] tableName) throws IOException {}
619   @Override
620   public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> c,
621       byte[] tableName) throws IOException {}
622   @Override
623   public void postEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
624       byte[] tableName) throws IOException {}
625 
626   @Override
627   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
628       throws IOException {
629     if (Bytes.equals(tableName, AccessControlLists.ACL_GLOBAL_NAME)) {
630       throw new AccessDeniedException("Not allowed to disable "
631           + AccessControlLists.ACL_TABLE_NAME_STR + " table.");
632     }
633     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
634   }
635 
636   @Override
637   public void preDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
638       byte[] tableName) throws IOException {}
639   @Override
640   public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> c,
641       byte[] tableName) throws IOException {}
642   @Override
643   public void postDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
644       byte[] tableName) throws IOException {}
645 
646   @Override
647   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
648       ServerName srcServer, ServerName destServer) throws IOException {
649     requirePermission("move", region.getTableName(), null, null, Action.ADMIN);
650   }
651 
652   @Override
653   public void postMove(ObserverContext<MasterCoprocessorEnvironment> c,
654       HRegionInfo region, ServerName srcServer, ServerName destServer)
655     throws IOException {}
656 
657   @Override
658   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
659       throws IOException {
660     requirePermission("assign", regionInfo.getTableName(), null, null, Action.ADMIN);
661   }
662 
663   @Override
664   public void postAssign(ObserverContext<MasterCoprocessorEnvironment> c,
665       HRegionInfo regionInfo) throws IOException {}
666 
667   @Override
668   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
669       boolean force) throws IOException {
670     requirePermission("unassign", regionInfo.getTableName(), null, null, Action.ADMIN);
671   }
672 
673   @Override
674   public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> c,
675       HRegionInfo regionInfo, boolean force) throws IOException {}
676 
677   @Override
678   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
679       HRegionInfo regionInfo) throws IOException {
680     requirePermission("regionOffline", regionInfo.getTableName(), null, null, Action.ADMIN);
681   }
682 
683   @Override
684   public void postRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
685       HRegionInfo regionInfo) throws IOException {
686   }
687 
688   @Override
689   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
690       throws IOException {
691     requirePermission("balance", Permission.Action.ADMIN);
692   }
693   @Override
694   public void postBalance(ObserverContext<MasterCoprocessorEnvironment> c, List<RegionPlan> plans)
695       throws IOException {}
696 
697   @Override
698   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
699       boolean newValue) throws IOException {
700     requirePermission("balanceSwitch", Permission.Action.ADMIN);
701     return newValue;
702   }
703   @Override
704   public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
705       boolean oldValue, boolean newValue) throws IOException {}
706 
707   @Override
708   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
709       throws IOException {
710     requirePermission("shutdown", Permission.Action.ADMIN);
711   }
712 
713   @Override
714   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
715       throws IOException {
716     requirePermission("stopMaster", Permission.Action.ADMIN);
717   }
718 
719   @Override
720   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
721       throws IOException {
722     // initialize the ACL storage table
723     AccessControlLists.init(ctx.getEnvironment().getMasterServices());
724   }
725 
726   @Override
727   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
728       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
729       throws IOException {
730     requirePermission("snapshot", Permission.Action.ADMIN);
731   }
732 
733   @Override
734   public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
735       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
736       throws IOException {
737   }
738 
739   @Override
740   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
741       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
742       throws IOException {
743     requirePermission("clone", Permission.Action.ADMIN);
744   }
745 
746   @Override
747   public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
748       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
749       throws IOException {
750   }
751 
752   @Override
753   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
754       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
755       throws IOException {
756     requirePermission("restore", Permission.Action.ADMIN);
757   }
758 
759   @Override
760   public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
761       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
762       throws IOException {
763   }
764 
765   @Override
766   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
767       final SnapshotDescription snapshot) throws IOException {
768     requirePermission("deleteSnapshot", Permission.Action.ADMIN);
769   }
770 
771   @Override
772   public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
773       final SnapshotDescription snapshot) throws IOException {
774   }
775 
776   /* ---- RegionObserver implementation ---- */
777 
778   @Override
779   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
780       throws IOException {
781     RegionCoprocessorEnvironment env = e.getEnvironment();
782     final HRegion region = env.getRegion();
783     if (region == null) {
784       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
785     } else {
786       HRegionInfo regionInfo = region.getRegionInfo();
787       if (isSpecialTable(regionInfo)) {
788         isSystemOrSuperUser(regionEnv.getConfiguration());
789       } else {
790         requirePermission("preOpen", Action.ADMIN);
791       }
792     }
793   }
794 
795   @Override
796   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
797     RegionCoprocessorEnvironment env = c.getEnvironment();
798     final HRegion region = env.getRegion();
799     if (region == null) {
800       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
801       return;
802     }
803     if (AccessControlLists.isAclRegion(region)) {
804       aclRegion = true;
805       try {
806         initialize(env);
807       } catch (IOException ex) {
808         // if we can't obtain permissions, it's better to fail
809         // than perform checks incorrectly
810         throw new RuntimeException("Failed to initialize permissions cache", ex);
811       }
812     }
813   }
814 
815   @Override
816   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
817     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
818   }
819 
820   @Override
821   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
822     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
823   }
824   
825   @Override
826   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
827       byte[] splitRow) throws IOException {
828     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
829   }
830 
831   @Override
832   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
833       final Store store, final InternalScanner scanner, final ScanType scanType)
834           throws IOException {
835     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
836     return scanner;
837   }
838 
839   @Override
840   public void preCompactSelection(final ObserverContext<RegionCoprocessorEnvironment> e,
841       final Store store, final List<StoreFile> candidates) throws IOException {
842     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
843   }
844 
845   @Override
846   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
847       final byte [] row, final byte [] family, final Result result)
848       throws IOException {
849     assert family != null;
850     //noinspection PrimitiveArrayArgumentToVariableArgMethod
851     requirePermission("getClosestRowBefore", Permission.Action.READ, c.getEnvironment(),
852         makeFamilyMap(family, null));
853   }
854 
855   @Override
856   public void preGet(final ObserverContext<RegionCoprocessorEnvironment> c,
857       final Get get, final List<KeyValue> result) throws IOException {
858     /*
859      if column family level checks fail, check for a qualifier level permission
860      in one of the families.  If it is present, then continue with the AccessControlFilter.
861       */
862     RegionCoprocessorEnvironment e = c.getEnvironment();
863     User requestUser = getActiveUser();
864     AuthResult authResult = permissionGranted("get", requestUser,
865         Permission.Action.READ, e, get.getFamilyMap());
866     if (!authResult.isAllowed()) {
867       if (hasFamilyQualifierPermission(requestUser,
868           Permission.Action.READ, e, get.getFamilyMap())) {
869         byte[] table = getTableName(e);
870         AccessControlFilter filter = new AccessControlFilter(authManager,
871             requestUser, table);
872 
873         // wrap any existing filter
874         if (get.getFilter() != null) {
875           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
876               Lists.newArrayList(filter, get.getFilter()));
877           get.setFilter(wrapper);
878         } else {
879           get.setFilter(filter);
880         }
881         logResult(AuthResult.allow("get", "Access allowed with filter", requestUser,
882             Permission.Action.READ, authResult.getTable(), get.getFamilyMap()));
883       } else {
884         logResult(authResult);
885         throw new AccessDeniedException("Insufficient permissions (table=" +
886           e.getRegion().getTableDesc().getNameAsString() + ", action=READ)");
887       }
888     } else {
889       // log auth success
890       logResult(authResult);
891     }
892   }
893 
894   @Override
895   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
896       final Get get, final boolean exists) throws IOException {
897     requirePermission("exists", Permission.Action.READ, c.getEnvironment(),
898         get.getFamilyMap());
899     return exists;
900   }
901 
902   @Override
903   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
904       final Put put, final WALEdit edit, final boolean writeToWAL)
905       throws IOException {
906     requirePermission("put", Permission.Action.WRITE, c.getEnvironment(),
907         put.getFamilyMap());
908   }
909 
910   @Override
911   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
912       final Put put, final WALEdit edit, final boolean writeToWAL) {
913     if (aclRegion) {
914       updateACL(c.getEnvironment(), put.getFamilyMap());
915     }
916   }
917 
918   @Override
919   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
920       final Delete delete, final WALEdit edit, final boolean writeToWAL)
921       throws IOException {
922     requirePermission("delete", Permission.Action.WRITE, c.getEnvironment(),
923         delete.getFamilyMap());
924   }
925 
926   @Override
927   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
928       final Delete delete, final WALEdit edit, final boolean writeToWAL)
929       throws IOException {
930     if (aclRegion) {
931       updateACL(c.getEnvironment(), delete.getFamilyMap());
932     }
933   }
934 
935   @Override
936   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
937       final byte [] row, final byte [] family, final byte [] qualifier,
938       final CompareFilter.CompareOp compareOp,
939       final ByteArrayComparable comparator, final Put put,
940       final boolean result) throws IOException {
941     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
942     requirePermission("checkAndPut", Permission.Action.READ, c.getEnvironment(), familyMap);
943     requirePermission("checkAndPut", Permission.Action.WRITE, c.getEnvironment(), familyMap);
944     return result;
945   }
946 
947   @Override
948   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
949       final byte [] row, final byte [] family, final byte [] qualifier,
950       final CompareFilter.CompareOp compareOp,
951       final ByteArrayComparable comparator, final Delete delete,
952       final boolean result) throws IOException {
953     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
954     requirePermission("checkAndDelete", Permission.Action.READ, c.getEnvironment(), familyMap);
955     requirePermission("checkAndDelete", Permission.Action.WRITE, c.getEnvironment(), familyMap);
956     return result;
957   }
958 
959   @Override
960   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
961       final byte [] row, final byte [] family, final byte [] qualifier,
962       final long amount, final boolean writeToWAL)
963       throws IOException {
964     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
965     requirePermission("incrementColumnValue", Permission.Action.WRITE, c.getEnvironment(), familyMap);
966     return -1;
967   }
968 
969   @Override
970   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
971       throws IOException {
972     requirePermission("append", Permission.Action.WRITE, c.getEnvironment(), append.getFamilyMap());
973     return null;
974   }
975 
976   @Override
977   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
978       final Increment increment)
979       throws IOException {
980     // Create a map of family to qualifiers.
981     Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
982     for (Map.Entry<byte [], List<? extends Cell>> entry: increment.getFamilyMap().entrySet()) {
983       Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
984       for (Cell cell: entry.getValue()) {
985         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
986         qualifiers.add(kv.getQualifier());
987       }
988       familyMap.put(entry.getKey(), qualifiers);
989     }
990     requirePermission("increment", Permission.Action.WRITE, c.getEnvironment(), familyMap);
991     return null;
992   }
993 
994   @Override
995   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
996       final Scan scan, final RegionScanner s) throws IOException {
997     /*
998      if column family level checks fail, check for a qualifier level permission
999      in one of the families.  If it is present, then continue with the AccessControlFilter.
1000       */
1001     RegionCoprocessorEnvironment e = c.getEnvironment();
1002     User user = getActiveUser();
1003     AuthResult authResult = permissionGranted("scannerOpen", user, Permission.Action.READ, e,
1004         scan.getFamilyMap());
1005     if (!authResult.isAllowed()) {
1006       if (hasFamilyQualifierPermission(user, Permission.Action.READ, e,
1007           scan.getFamilyMap())) {
1008         byte[] table = getTableName(e);
1009         AccessControlFilter filter = new AccessControlFilter(authManager,
1010             user, table);
1011 
1012         // wrap any existing filter
1013         if (scan.hasFilter()) {
1014           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1015               Lists.newArrayList(filter, scan.getFilter()));
1016           scan.setFilter(wrapper);
1017         } else {
1018           scan.setFilter(filter);
1019         }
1020         logResult(AuthResult.allow("scannerOpen", "Access allowed with filter", user,
1021             Permission.Action.READ, authResult.getTable(), scan.getFamilyMap()));
1022       } else {
1023         // no table/family level perms and no qualifier level perms, reject
1024         logResult(authResult);
1025         throw new AccessDeniedException("Insufficient permissions for user '"+
1026             (user != null ? user.getShortName() : "null")+"' "+
1027             "for scanner open on table " + Bytes.toString(getTableName(e)));
1028       }
1029     } else {
1030       // log success
1031       logResult(authResult);
1032     }
1033     return s;
1034   }
1035 
1036   @Override
1037   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1038       final Scan scan, final RegionScanner s) throws IOException {
1039     User user = getActiveUser();
1040     if (user != null && user.getShortName() != null) {      // store reference to scanner owner for later checks
1041       scannerOwners.put(s, user.getShortName());
1042     }
1043     return s;
1044   }
1045 
1046   @Override
1047   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1048       final InternalScanner s, final List<Result> result,
1049       final int limit, final boolean hasNext) throws IOException {
1050     requireScannerOwner(s);
1051     return hasNext;
1052   }
1053 
1054   @Override
1055   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1056       final InternalScanner s) throws IOException {
1057     requireScannerOwner(s);
1058   }
1059 
1060   @Override
1061   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1062       final InternalScanner s) throws IOException {
1063     // clean up any associated owner mapping
1064     scannerOwners.remove(s);
1065   }
1066 
1067   /**
1068    * Verify, when servicing an RPC, that the caller is the scanner owner.
1069    * If so, we assume that access control is correctly enforced based on
1070    * the checks performed in preScannerOpen()
1071    */
1072   private void requireScannerOwner(InternalScanner s)
1073       throws AccessDeniedException {
1074     if (RequestContext.isInRequestContext()) {
1075       String requestUserName = RequestContext.getRequestUserName();
1076       String owner = scannerOwners.get(s);
1077       if (owner != null && !owner.equals(requestUserName)) {
1078         throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
1079       }
1080     }
1081   }
1082 
1083   /**
1084    * Verifies user has WRITE privileges on
1085    * the Column Families involved in the bulkLoadHFile
1086    * request. Specific Column Write privileges are presently
1087    * ignored.
1088    */
1089   @Override
1090   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
1091       List<Pair<byte[], String>> familyPaths) throws IOException {
1092     List<byte[]> cfs = new LinkedList<byte[]>();
1093     for(Pair<byte[],String> el : familyPaths) {
1094       requirePermission("preBulkLoadHFile",
1095           ctx.getEnvironment().getRegion().getTableDesc().getName(),
1096           el.getFirst(),
1097           null,
1098           Permission.Action.WRITE);
1099     }
1100   }
1101 
1102   private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String method, Action action) throws IOException {
1103     User requestUser = getActiveUser();
1104     byte[] tableName = e.getRegion().getTableDesc().getName();
1105     AuthResult authResult = permissionGranted(method, requestUser,
1106         action, e, Collections.EMPTY_MAP);
1107     if (!authResult.isAllowed()) {
1108       for(UserPermission userPerm:
1109           AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), tableName)) {
1110         for(Permission.Action userAction: userPerm.getActions()) {
1111           if(userAction.equals(action)) {
1112             return AuthResult.allow(method, "Access allowed", requestUser,
1113                 action, tableName, null, null);
1114           }
1115         }
1116       }
1117     }
1118     return authResult;
1119   }
1120 
1121   /**
1122    * Authorization check for
1123    * SecureBulkLoadProtocol.prepareBulkLoad()
1124    * @param e
1125    * @throws IOException
1126    */
1127   //TODO this should end up as a coprocessor hook
1128   public void prePrepareBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1129     AuthResult authResult = hasSomeAccess(e, "prePrepareBulkLoad", Action.WRITE);
1130     logResult(authResult);
1131     if (!authResult.isAllowed()) {
1132       throw new AccessDeniedException("Insufficient permissions (table=" +
1133         e.getRegion().getTableDesc().getNameAsString() + ", action=WRITE)");
1134     }
1135   }
1136 
1137   /**
1138    * Authorization security check for
1139    * SecureBulkLoadProtocol.cleanupBulkLoad()
1140    * @param e
1141    * @throws IOException
1142    */
1143   //TODO this should end up as a coprocessor hook
1144   public void preCleanupBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1145     AuthResult authResult = hasSomeAccess(e, "preCleanupBulkLoad", Action.WRITE);
1146     logResult(authResult);
1147     if (!authResult.isAllowed()) {
1148       throw new AccessDeniedException("Insufficient permissions (table=" +
1149         e.getRegion().getTableDesc().getNameAsString() + ", action=WRITE)");
1150     }
1151   }
1152 
1153   /* ---- Protobuf AccessControlService implementation ---- */
1154   @Override
1155   public void grant(RpcController controller,
1156                     AccessControlProtos.GrantRequest request,
1157                     RpcCallback<AccessControlProtos.GrantResponse> done) {
1158     UserPermission perm = ProtobufUtil.toUserPermission(request.getPermission());
1159     AccessControlProtos.GrantResponse response = null;
1160     try {
1161       // verify it's only running at .acl.
1162       if (aclRegion) {
1163         if (LOG.isDebugEnabled()) {
1164           LOG.debug("Received request to grant access permission " + perm.toString());
1165         }
1166 
1167         requirePermission("grant", perm.getTable(), perm.getFamily(), perm.getQualifier(), Action.ADMIN);
1168 
1169         AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
1170         if (AUDITLOG.isTraceEnabled()) {
1171           // audit log should store permission changes in addition to auth results
1172           AUDITLOG.trace("Granted permission " + perm.toString());
1173         }
1174       } else {
1175         throw new CoprocessorException(AccessController.class, "This method "
1176             + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1177       }
1178       response = AccessControlProtos.GrantResponse.getDefaultInstance();
1179     } catch (IOException ioe) {
1180       // pass exception back up
1181       ResponseConverter.setControllerException(controller, ioe);
1182     }
1183     done.run(response);
1184   }
1185 
1186   @Override
1187   public void revoke(RpcController controller,
1188                      AccessControlProtos.RevokeRequest request,
1189                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
1190     UserPermission perm = ProtobufUtil.toUserPermission(request.getPermission());
1191     AccessControlProtos.RevokeResponse response = null;
1192     try {
1193       // only allowed to be called on _acl_ region
1194       if (aclRegion) {
1195         if (LOG.isDebugEnabled()) {
1196           LOG.debug("Received request to revoke access permission " + perm.toString());
1197         }
1198 
1199         requirePermission("revoke", perm.getTable(), perm.getFamily(),
1200                           perm.getQualifier(), Action.ADMIN);
1201 
1202         AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
1203         if (AUDITLOG.isTraceEnabled()) {
1204           // audit log should record all permission changes
1205           AUDITLOG.trace("Revoked permission " + perm.toString());
1206         }
1207       } else {
1208         throw new CoprocessorException(AccessController.class, "This method "
1209             + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1210       }
1211       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
1212     } catch (IOException ioe) {
1213       // pass exception back up
1214       ResponseConverter.setControllerException(controller, ioe);
1215     }
1216     done.run(response);
1217   }
1218 
1219   @Override
1220   public void getUserPermissions(RpcController controller,
1221                                  AccessControlProtos.UserPermissionsRequest request,
1222                                  RpcCallback<AccessControlProtos.UserPermissionsResponse> done) {
1223     AccessControlProtos.UserPermissionsResponse response = null;
1224     byte[] table = null;
1225     if (request.hasTable()) {
1226       table = request.getTable().toByteArray();
1227     }
1228     try {
1229       // only allowed to be called on _acl_ region
1230       if (aclRegion) {
1231         requirePermission("userPermissions", table, null, null, Action.ADMIN);
1232 
1233         List<UserPermission> perms = AccessControlLists.getUserPermissions(
1234           regionEnv.getConfiguration(), table);
1235         response = ResponseConverter.buildUserPermissionsResponse(perms);
1236       } else {
1237         throw new CoprocessorException(AccessController.class, "This method "
1238             + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1239       }
1240     } catch (IOException ioe) {
1241       // pass exception back up
1242       ResponseConverter.setControllerException(controller, ioe);
1243     }
1244     done.run(response);
1245   }
1246 
1247   @Override
1248   public void checkPermissions(RpcController controller,
1249                                AccessControlProtos.CheckPermissionsRequest request,
1250                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
1251     Permission[] permissions = new Permission[request.getPermissionCount()];
1252     for (int i=0; i < request.getPermissionCount(); i++) {
1253       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
1254     }
1255     AccessControlProtos.CheckPermissionsResponse response = null;
1256     try {
1257       byte[] tableName = regionEnv.getRegion().getTableDesc().getName();
1258       for (Permission permission : permissions) {
1259         if (permission instanceof TablePermission) {
1260           TablePermission tperm = (TablePermission) permission;
1261           for (Permission.Action action : permission.getActions()) {
1262             if (!Arrays.equals(tperm.getTable(), tableName)) {
1263               throw new CoprocessorException(AccessController.class, String.format("This method "
1264                   + "can only execute at the table specified in TablePermission. " +
1265                   "Table of the region:%s , requested table:%s", Bytes.toString(tableName),
1266                   Bytes.toString(tperm.getTable())));
1267             }
1268 
1269             Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
1270             if (tperm.getFamily() != null) {
1271               if (tperm.getQualifier() != null) {
1272                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
1273                 qualifiers.add(tperm.getQualifier());
1274                 familyMap.put(tperm.getFamily(), qualifiers);
1275               } else {
1276                 familyMap.put(tperm.getFamily(), null);
1277               }
1278             }
1279 
1280             requirePermission("checkPermissions", action, regionEnv, familyMap);
1281           }
1282 
1283         } else {
1284           for (Permission.Action action : permission.getActions()) {
1285             requirePermission("checkPermissions", action);
1286           }
1287         }
1288       }
1289       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
1290     } catch (IOException ioe) {
1291       ResponseConverter.setControllerException(controller, ioe);
1292     }
1293     done.run(response);
1294   }
1295 
1296   @Override
1297   public Service getService() {
1298     return AccessControlProtos.AccessControlService.newReflectiveService(this);
1299   }
1300 
1301   private byte[] getTableName(RegionCoprocessorEnvironment e) {
1302     HRegion region = e.getRegion();
1303     byte[] tableName = null;
1304 
1305     if (region != null) {
1306       HRegionInfo regionInfo = region.getRegionInfo();
1307       if (regionInfo != null) {
1308         tableName = regionInfo.getTableName();
1309       }
1310     }
1311     return tableName;
1312   }
1313 
1314 
1315   @Override
1316   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
1317       throws IOException {
1318     requirePermission("preClose", Action.ADMIN);
1319   }
1320 
1321   private void isSystemOrSuperUser(Configuration conf) throws IOException {
1322     User user = User.getCurrent();
1323     if (user == null) {
1324       throw new IOException("Unable to obtain the current user, " +
1325         "authorization checks for internal operations will not work correctly!");
1326     }
1327 
1328     String currentUser = user.getShortName();
1329     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
1330       AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
1331 
1332     User activeUser = getActiveUser();
1333     if (!(superusers.contains(activeUser.getShortName()))) {
1334       throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null") +
1335         "is not system or super user.");
1336     }
1337   }
1338 
1339   private boolean isSpecialTable(HRegionInfo regionInfo) {
1340     byte[] tableName = regionInfo.getTableName();
1341     return Arrays.equals(tableName, AccessControlLists.ACL_TABLE_NAME)
1342         || Arrays.equals(tableName, Bytes.toBytes("-ROOT-"))
1343         || Arrays.equals(tableName, Bytes.toBytes(".META."));
1344   }
1345 
1346   @Override
1347   public void preStopRegionServer(
1348       ObserverContext<RegionServerCoprocessorEnvironment> env)
1349       throws IOException {
1350     requirePermission("preStopRegionServer", Permission.Action.ADMIN);
1351   }
1352 
1353   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
1354       byte[] qualifier) {
1355     if (family == null) {
1356       return null;
1357     }
1358 
1359     Map<byte[], Collection<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
1360     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
1361     return familyMap;
1362   }
1363 }