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.security.PrivilegedExceptionAction;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Set;
27  import java.util.TreeMap;
28  import java.util.TreeSet;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.CellScanner;
35  import org.apache.hadoop.hbase.CellUtil;
36  import org.apache.hadoop.hbase.CompoundConfiguration;
37  import org.apache.hadoop.hbase.CoprocessorEnvironment;
38  import org.apache.hadoop.hbase.DoNotRetryIOException;
39  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
40  import org.apache.hadoop.hbase.HColumnDescriptor;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.KeyValue;
45  import org.apache.hadoop.hbase.KeyValue.Type;
46  import org.apache.hadoop.hbase.KeyValueUtil;
47  import org.apache.hadoop.hbase.NamespaceDescriptor;
48  import org.apache.hadoop.hbase.ServerName;
49  import org.apache.hadoop.hbase.TableName;
50  import org.apache.hadoop.hbase.Tag;
51  import org.apache.hadoop.hbase.catalog.MetaReader;
52  import org.apache.hadoop.hbase.classification.InterfaceAudience;
53  import org.apache.hadoop.hbase.client.Append;
54  import org.apache.hadoop.hbase.client.Delete;
55  import org.apache.hadoop.hbase.client.Durability;
56  import org.apache.hadoop.hbase.client.Get;
57  import org.apache.hadoop.hbase.client.Increment;
58  import org.apache.hadoop.hbase.client.Mutation;
59  import org.apache.hadoop.hbase.client.Put;
60  import org.apache.hadoop.hbase.client.Query;
61  import org.apache.hadoop.hbase.client.Result;
62  import org.apache.hadoop.hbase.client.Scan;
63  import org.apache.hadoop.hbase.coprocessor.BaseMasterAndRegionObserver;
64  import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver;
65  import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
66  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
67  import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
68  import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
69  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
70  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
71  import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
72  import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
73  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
74  import org.apache.hadoop.hbase.filter.CompareFilter;
75  import org.apache.hadoop.hbase.filter.Filter;
76  import org.apache.hadoop.hbase.filter.FilterList;
77  import org.apache.hadoop.hbase.io.hfile.HFile;
78  import org.apache.hadoop.hbase.ipc.RpcServer;
79  import org.apache.hadoop.hbase.master.MasterServices;
80  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
81  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
82  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
83  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
84  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
85  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
86  import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.CleanupBulkLoadRequest;
87  import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.PrepareBulkLoadRequest;
88  import org.apache.hadoop.hbase.regionserver.HRegion;
89  import org.apache.hadoop.hbase.regionserver.InternalScanner;
90  import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
91  import org.apache.hadoop.hbase.regionserver.RegionScanner;
92  import org.apache.hadoop.hbase.regionserver.ScanType;
93  import org.apache.hadoop.hbase.regionserver.Store;
94  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
95  import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
96  import org.apache.hadoop.hbase.security.AccessDeniedException;
97  import org.apache.hadoop.hbase.security.User;
98  import org.apache.hadoop.hbase.security.UserProvider;
99  import org.apache.hadoop.hbase.security.access.Permission.Action;
100 import org.apache.hadoop.hbase.util.ByteRange;
101 import org.apache.hadoop.hbase.util.Bytes;
102 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
103 import org.apache.hadoop.hbase.util.Pair;
104 import org.apache.hadoop.hbase.util.SimpleByteRange;
105 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
106 
107 import com.google.common.collect.ArrayListMultimap;
108 import com.google.common.collect.ImmutableSet;
109 import com.google.common.collect.ListMultimap;
110 import com.google.common.collect.Lists;
111 import com.google.common.collect.MapMaker;
112 import com.google.common.collect.Maps;
113 import com.google.common.collect.Sets;
114 import com.google.protobuf.Message;
115 import com.google.protobuf.RpcCallback;
116 import com.google.protobuf.RpcController;
117 import com.google.protobuf.Service;
118 
119 /**
120  * Provides basic authorization checks for data access and administrative
121  * operations.
122  *
123  * <p>
124  * {@code AccessController} performs authorization checks for HBase operations
125  * based on:
126  * <ul>
127  *   <li>the identity of the user performing the operation</li>
128  *   <li>the scope over which the operation is performed, in increasing
129  *   specificity: global, table, column family, or qualifier</li>
130  *   <li>the type of action being performed (as mapped to
131  *   {@link Permission.Action} values)</li>
132  * </ul>
133  * If the authorization check fails, an {@link AccessDeniedException}
134  * will be thrown for the operation.
135  * </p>
136  *
137  * <p>
138  * To perform authorization checks, {@code AccessController} relies on the
139  * {@link org.apache.hadoop.hbase.ipc.RpcServerEngine} being loaded to provide
140  * the user identities for remote requests.
141  * </p>
142  *
143  * <p>
144  * The access control lists used for authorization can be manipulated via the
145  * exposed {@link AccessControlService} Interface implementation, and the associated
146  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
147  * commands.
148  * </p>
149  */
150 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
151 public class AccessController extends BaseMasterAndRegionObserver
152     implements RegionServerObserver,
153       AccessControlService.Interface, CoprocessorService, EndpointObserver, BulkLoadObserver {
154 
155   public static final Log LOG = LogFactory.getLog(AccessController.class);
156 
157   private static final Log AUDITLOG =
158     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
159   private static final String CHECK_COVERING_PERM = "check_covering_perm";
160   private static final String TAG_CHECK_PASSED = "tag_check_passed";
161   private static final byte[] TRUE = Bytes.toBytes(true);
162 
163   TableAuthManager authManager = null;
164 
165   /** flags if we are running on a region of the _acl_ table */
166   boolean aclRegion = false;
167 
168   /** defined only for Endpoint implementation, so it can have way to
169    access region services */
170   private RegionCoprocessorEnvironment regionEnv;
171 
172   /** Mapping of scanner instances to the user who created them */
173   private Map<InternalScanner,String> scannerOwners =
174       new MapMaker().weakKeys().makeMap();
175 
176   private Map<TableName, List<UserPermission>> tableAcls;
177 
178   /** Provider for mapping principal names to Users */
179   private UserProvider userProvider;
180 
181   /** The list of users with superuser authority */
182   private List<String> superusers;
183 
184   /** if we are active, usually true, only not true if "hbase.security.authorization"
185    has been set to false in site configuration */
186   boolean authorizationEnabled;
187 
188   /** if we are able to support cell ACLs */
189   boolean cellFeaturesEnabled;
190 
191   /** if we should check EXEC permissions */
192   boolean shouldCheckExecPermission;
193 
194   /** if we should terminate access checks early as soon as table or CF grants
195     allow access; pre-0.98 compatible behavior */
196   boolean compatibleEarlyTermination;
197 
198   /** if we have been successfully initialized */
199   private volatile boolean initialized = false;
200 
201   /** if the ACL table is available, only relevant in the master */
202   private volatile boolean aclTabAvailable = false;
203 
204   public HRegion getRegion() {
205     return regionEnv != null ? regionEnv.getRegion() : null;
206   }
207 
208   public TableAuthManager getAuthManager() {
209     return authManager;
210   }
211 
212   void initialize(RegionCoprocessorEnvironment e) throws IOException {
213     final HRegion region = e.getRegion();
214     Configuration conf = e.getConfiguration();
215     Map<byte[], ListMultimap<String,TablePermission>> tables =
216         AccessControlLists.loadAll(region);
217     // For each table, write out the table's permissions to the respective
218     // znode for that table.
219     for (Map.Entry<byte[], ListMultimap<String,TablePermission>> t:
220       tables.entrySet()) {
221       byte[] entry = t.getKey();
222       ListMultimap<String,TablePermission> perms = t.getValue();
223       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
224       this.authManager.getZKPermissionWatcher().writeToZookeeper(entry, serialized);
225     }
226     initialized = true;
227   }
228 
229   /**
230    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
231    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
232    * table updates.
233    */
234   void updateACL(RegionCoprocessorEnvironment e,
235       final Map<byte[], List<Cell>> familyMap) {
236     Set<byte[]> entries =
237         new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
238     for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
239       List<Cell> cells = f.getValue();
240       for (Cell cell: cells) {
241         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
242         if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
243             kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
244             AccessControlLists.ACL_LIST_FAMILY.length)) {
245           entries.add(kv.getRow());
246         }
247       }
248     }
249     ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
250     Configuration conf = regionEnv.getConfiguration();
251     for (byte[] entry: entries) {
252       try {
253         ListMultimap<String,TablePermission> perms =
254           AccessControlLists.getPermissions(conf, entry);
255         byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
256         zkw.writeToZookeeper(entry, serialized);
257       } catch (IOException ex) {
258         LOG.error("Failed updating permissions mirror for '" + Bytes.toString(entry) + "'",
259             ex);
260       }
261     }
262   }
263 
264   /**
265    * Check the current user for authorization to perform a specific action
266    * against the given set of row data.
267    *
268    * <p>Note: Ordering of the authorization checks
269    * has been carefully optimized to short-circuit the most common requests
270    * and minimize the amount of processing required.</p>
271    *
272    * @param permRequest the action being requested
273    * @param e the coprocessor environment
274    * @param families the map of column families to qualifiers present in
275    * the request
276    * @return an authorization result
277    */
278   AuthResult permissionGranted(String request, User user, Action permRequest,
279       RegionCoprocessorEnvironment e,
280       Map<byte [], ? extends Collection<?>> families) {
281     HRegionInfo hri = e.getRegion().getRegionInfo();
282     TableName tableName = hri.getTable();
283 
284     // 1. All users need read access to hbase:meta table.
285     // this is a very common operation, so deal with it quickly.
286     if (hri.isMetaRegion()) {
287       if (permRequest == Action.READ) {
288         return AuthResult.allow(request, "All users allowed", user,
289           permRequest, tableName, families);
290       }
291     }
292 
293     if (user == null) {
294       return AuthResult.deny(request, "No user associated with request!", null,
295         permRequest, tableName, families);
296     }
297 
298     // 2. check for the table-level, if successful we can short-circuit
299     if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
300       return AuthResult.allow(request, "Table permission granted", user,
301         permRequest, tableName, families);
302     }
303 
304     // 3. check permissions against the requested families
305     if (families != null && families.size() > 0) {
306       // all families must pass
307       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
308         // a) check for family level access
309         if (authManager.authorize(user, tableName, family.getKey(),
310             permRequest)) {
311           continue;  // family-level permission overrides per-qualifier
312         }
313 
314         // b) qualifier level access can still succeed
315         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
316           if (family.getValue() instanceof Set) {
317             // for each qualifier of the family
318             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
319             for (byte[] qualifier : familySet) {
320               if (!authManager.authorize(user, tableName, family.getKey(),
321                                          qualifier, permRequest)) {
322                 return AuthResult.deny(request, "Failed qualifier check", user,
323                     permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
324               }
325             }
326           } else if (family.getValue() instanceof List) { // List<KeyValue>
327             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
328             for (KeyValue kv : kvList) {
329               if (!authManager.authorize(user, tableName, family.getKey(),
330                       kv.getQualifier(), permRequest)) {
331                 return AuthResult.deny(request, "Failed qualifier check", user,
332                     permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
333               }
334             }
335           }
336         } else {
337           // no qualifiers and family-level check already failed
338           return AuthResult.deny(request, "Failed family check", user, permRequest,
339               tableName, makeFamilyMap(family.getKey(), null));
340         }
341       }
342 
343       // all family checks passed
344       return AuthResult.allow(request, "All family checks passed", user, permRequest,
345           tableName, families);
346     }
347 
348     // 4. no families to check and table level access failed
349     return AuthResult.deny(request, "No families to check and table permission failed",
350         user, permRequest, tableName, families);
351   }
352 
353   /**
354    * Check the current user for authorization to perform a specific action
355    * against the given set of row data.
356    * @param opType the operation type
357    * @param user the user
358    * @param e the coprocessor environment
359    * @param families the map of column families to qualifiers present in
360    * the request
361    * @param actions the desired actions
362    * @return an authorization result
363    */
364   AuthResult permissionGranted(OpType opType, User user, RegionCoprocessorEnvironment e,
365       Map<byte [], ? extends Collection<?>> families, Action... actions) {
366     AuthResult result = null;
367     for (Action action: actions) {
368       result = permissionGranted(opType.toString(), user, action, e, families);
369       if (!result.isAllowed()) {
370         return result;
371       }
372     }
373     return result;
374   }
375 
376   private void logResult(AuthResult result) {
377     if (AUDITLOG.isTraceEnabled()) {
378       InetAddress remoteAddr = RpcServer.getRemoteAddress();
379       AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
380           " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
381           "; reason: " + result.getReason() +
382           "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
383           "; request: " + result.getRequest() +
384           "; context: " + result.toContextString());
385     }
386   }
387 
388   /**
389    * Returns the active user to which authorization checks should be applied.
390    * If we are in the context of an RPC call, the remote user is used,
391    * otherwise the currently logged in user is used.
392    */
393   private User getActiveUser() throws IOException {
394     User user = RpcServer.getRequestUser();
395     if (user == null) {
396       // for non-rpc handling, fallback to system user
397       user = userProvider.getCurrent();
398     }
399     return user;
400   }
401 
402   /**
403    * Authorizes that the current user has any of the given permissions for the
404    * given table, column family and column qualifier.
405    * @param tableName Table requested
406    * @param family Column family requested
407    * @param qualifier Column qualifier requested
408    * @throws IOException if obtaining the current user fails
409    * @throws AccessDeniedException if user has no authorization
410    */
411   private void requirePermission(String request, TableName tableName, byte[] family,
412       byte[] qualifier, Action... permissions) throws IOException {
413     User user = getActiveUser();
414     AuthResult result = null;
415 
416     for (Action permission : permissions) {
417       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
418         result = AuthResult.allow(request, "Table permission granted", user,
419                                   permission, tableName, family, qualifier);
420         break;
421       } else {
422         // rest of the world
423         result = AuthResult.deny(request, "Insufficient permissions", user,
424                                  permission, tableName, family, qualifier);
425       }
426     }
427     logResult(result);
428     if (authorizationEnabled && !result.isAllowed()) {
429       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
430     }
431   }
432 
433   /**
434    * Authorizes that the current user has any of the given permissions for the
435    * given table, column family and column qualifier.
436    * @param tableName Table requested
437    * @param family Column family param
438    * @param qualifier Column qualifier param
439    * @throws IOException if obtaining the current user fails
440    * @throws AccessDeniedException if user has no authorization
441    */
442   private void requireTablePermission(String request, TableName tableName, byte[] family,
443       byte[] qualifier, Action... permissions) throws IOException {
444     User user = getActiveUser();
445     AuthResult result = null;
446 
447     for (Action permission : permissions) {
448       if (authManager.authorize(user, tableName, null, null, permission)) {
449         result = AuthResult.allow(request, "Table permission granted", user,
450             permission, tableName, null, null);
451         break;
452       } else {
453         // rest of the world
454         result = AuthResult.deny(request, "Insufficient permissions", user,
455             permission, tableName, family, qualifier);
456       }
457     }
458     logResult(result);
459     if (authorizationEnabled && !result.isAllowed()) {
460       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
461     }
462   }
463 
464   /**
465    * Authorizes that the current user has any of the given permissions to access the table.
466    *
467    * @param tableName Table requested
468    * @param permissions Actions being requested
469    * @throws IOException if obtaining the current user fails
470    * @throws AccessDeniedException if user has no authorization
471    */
472   private void requireAccess(String request, TableName tableName,
473       Action... permissions) throws IOException {
474     User user = getActiveUser();
475     AuthResult result = null;
476 
477     for (Action permission : permissions) {
478       if (authManager.hasAccess(user, tableName, permission)) {
479         result = AuthResult.allow(request, "Table permission granted", user,
480                                   permission, tableName, null, null);
481         break;
482       } else {
483         // rest of the world
484         result = AuthResult.deny(request, "Insufficient permissions", user,
485                                  permission, tableName, null, null);
486       }
487     }
488     logResult(result);
489     if (authorizationEnabled && !result.isAllowed()) {
490       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
491     }
492   }
493 
494   /**
495    * Authorizes that the current user has global privileges for the given action.
496    * @param perm The action being requested
497    * @throws IOException if obtaining the current user fails
498    * @throws AccessDeniedException if authorization is denied
499    */
500   private void requirePermission(String request, Action perm) throws IOException {
501     requireGlobalPermission(request, perm, null, null);
502   }
503 
504   /**
505    * Checks that the user has the given global permission. The generated
506    * audit log message will contain context information for the operation
507    * being authorized, based on the given parameters.
508    * @param perm Action being requested
509    * @param tableName Affected table name.
510    * @param familyMap Affected column families.
511    */
512   private void requireGlobalPermission(String request, Action perm, TableName tableName,
513       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
514     User user = getActiveUser();
515     if (authManager.authorize(user, perm) || (tableName != null &&
516         authManager.authorize(user, tableName.getNamespaceAsString(), perm))) {
517       logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap));
518     } else {
519       logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap));
520       if (authorizationEnabled) {
521         throw new AccessDeniedException("Insufficient permissions for user '" +
522           (user != null ? user.getShortName() : "null") +"' (global, action=" +
523           perm.toString() + ")");
524       }
525     }
526   }
527 
528   /**
529    * Checks that the user has the given global permission. The generated
530    * audit log message will contain context information for the operation
531    * being authorized, based on the given parameters.
532    * @param perm Action being requested
533    * @param namespace
534    */
535   private void requireGlobalPermission(String request, Action perm,
536                                        String namespace) throws IOException {
537     User user = getActiveUser();
538     if (authManager.authorize(user, perm)
539         || (namespace != null && authManager.authorize(user, namespace, perm))) {
540       logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace));
541     } else {
542       logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace));
543       if (authorizationEnabled) {
544         throw new AccessDeniedException("Insufficient permissions for user '" +
545           (user != null ? user.getShortName() : "null") +"' (global, action=" +
546           perm.toString() + ")");
547       }
548     }
549   }
550 
551   /**
552    * Checks that the user has the given global or namespace permission.
553    * @param namespace
554    * @param permissions Actions being requested
555    */
556   public void requireNamespacePermission(String request, String namespace,
557       Action... permissions) throws IOException {
558     User user = getActiveUser();
559     AuthResult result = null;
560 
561     for (Action permission : permissions) {
562       if (authManager.authorize(user, namespace, permission)) {
563         result = AuthResult.allow(request, "Namespace permission granted",
564             user, permission, namespace);
565         break;
566       } else {
567         // rest of the world
568         result = AuthResult.deny(request, "Insufficient permissions", user,
569             permission, namespace);
570       }
571     }
572     logResult(result);
573     if (authorizationEnabled && !result.isAllowed()) {
574       throw new AccessDeniedException("Insufficient permissions "
575           + result.toContextString());
576     }
577   }
578 
579   /**
580    * Checks that the user has the given global or namespace permission.
581    * @param namespace
582    * @param permissions Actions being requested
583    */
584   public void requireNamespacePermission(String request, String namespace, TableName tableName,
585       Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
586       throws IOException {
587     User user = getActiveUser();
588     AuthResult result = null;
589 
590     for (Action permission : permissions) {
591       if (authManager.authorize(user, namespace, permission)) {
592         result = AuthResult.allow(request, "Namespace permission granted",
593             user, permission, namespace);
594         break;
595       } else {
596         // rest of the world
597         result = AuthResult.deny(request, "Insufficient permissions", user,
598             permission, namespace);
599       }
600     }
601     logResult(result);
602     if (authorizationEnabled && !result.isAllowed()) {
603       throw new AccessDeniedException("Insufficient permissions "
604           + result.toContextString());
605     }
606   }
607 
608   /**
609    * Returns <code>true</code> if the current user is allowed the given action
610    * over at least one of the column qualifiers in the given column families.
611    */
612   private boolean hasFamilyQualifierPermission(User user,
613       Action perm,
614       RegionCoprocessorEnvironment env,
615       Map<byte[], ? extends Collection<byte[]>> familyMap)
616     throws IOException {
617     HRegionInfo hri = env.getRegion().getRegionInfo();
618     TableName tableName = hri.getTable();
619 
620     if (user == null) {
621       return false;
622     }
623 
624     if (familyMap != null && familyMap.size() > 0) {
625       // at least one family must be allowed
626       for (Map.Entry<byte[], ? extends Collection<byte[]>> family :
627           familyMap.entrySet()) {
628         if (family.getValue() != null && !family.getValue().isEmpty()) {
629           for (byte[] qualifier : family.getValue()) {
630             if (authManager.matchPermission(user, tableName,
631                 family.getKey(), qualifier, perm)) {
632               return true;
633             }
634           }
635         } else {
636           if (authManager.matchPermission(user, tableName, family.getKey(),
637               perm)) {
638             return true;
639           }
640         }
641       }
642     } else if (LOG.isDebugEnabled()) {
643       LOG.debug("Empty family map passed for permission check");
644     }
645 
646     return false;
647   }
648 
649   private enum OpType {
650     GET_CLOSEST_ROW_BEFORE("getClosestRowBefore"),
651     GET("get"),
652     EXISTS("exists"),
653     SCAN("scan"),
654     PUT("put"),
655     DELETE("delete"),
656     CHECK_AND_PUT("checkAndPut"),
657     CHECK_AND_DELETE("checkAndDelete"),
658     INCREMENT_COLUMN_VALUE("incrementColumnValue"),
659     APPEND("append"),
660     INCREMENT("increment");
661 
662     private String type;
663 
664     private OpType(String type) {
665       this.type = type;
666     }
667 
668     @Override
669     public String toString() {
670       return type;
671     }
672   }
673 
674   /**
675    * Determine if cell ACLs covered by the operation grant access. This is expensive.
676    * @return false if cell ACLs failed to grant access, true otherwise
677    * @throws IOException
678    */
679   private boolean checkCoveringPermission(OpType request, RegionCoprocessorEnvironment e,
680       byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions)
681       throws IOException {
682     if (!cellFeaturesEnabled) {
683       return false;
684     }
685     long cellGrants = 0;
686     User user = getActiveUser();
687     long latestCellTs = 0;
688     Get get = new Get(row);
689     // Only in case of Put/Delete op, consider TS within cell (if set for individual cells).
690     // When every cell, within a Mutation, can be linked with diff TS we can not rely on only one
691     // version. We have to get every cell version and check its TS against the TS asked for in
692     // Mutation and skip those Cells which is outside this Mutation TS.In case of Put, we have to
693     // consider only one such passing cell. In case of Delete we have to consider all the cell
694     // versions under this passing version. When Delete Mutation contains columns which are a
695     // version delete just consider only one version for those column cells.
696     boolean considerCellTs  = (request == OpType.PUT || request == OpType.DELETE);
697     if (considerCellTs) {
698       get.setMaxVersions();
699     } else {
700       get.setMaxVersions(1);
701     }
702     boolean diffCellTsFromOpTs = false;
703     for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) {
704       byte[] col = entry.getKey();
705       // TODO: HBASE-7114 could possibly unify the collection type in family
706       // maps so we would not need to do this
707       if (entry.getValue() instanceof Set) {
708         Set<byte[]> set = (Set<byte[]>)entry.getValue();
709         if (set == null || set.isEmpty()) {
710           get.addFamily(col);
711         } else {
712           for (byte[] qual: set) {
713             get.addColumn(col, qual);
714           }
715         }
716       } else if (entry.getValue() instanceof List) {
717         List<Cell> list = (List<Cell>)entry.getValue();
718         if (list == null || list.isEmpty()) {
719           get.addFamily(col);
720         } else {
721           // In case of family delete, a Cell will be added into the list with Qualifier as null.
722           for (Cell cell : list) {
723             if (cell.getQualifierLength() == 0
724                 && (cell.getTypeByte() == Type.DeleteFamily.getCode()
725                 || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode())) {
726               get.addFamily(col);
727             } else {
728               get.addColumn(col, CellUtil.cloneQualifier(cell));
729             }
730             if (considerCellTs) {
731               long cellTs = cell.getTimestamp();
732               latestCellTs = Math.max(latestCellTs, cellTs);
733               diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs);
734             }
735           }
736         }
737       } else if (entry.getValue() == null) {
738         get.addFamily(col);        
739       } else {
740         throw new RuntimeException("Unhandled collection type " +
741           entry.getValue().getClass().getName());
742       }
743     }
744     // We want to avoid looking into the future. So, if the cells of the
745     // operation specify a timestamp, or the operation itself specifies a
746     // timestamp, then we use the maximum ts found. Otherwise, we bound
747     // the Get to the current server time. We add 1 to the timerange since
748     // the upper bound of a timerange is exclusive yet we need to examine
749     // any cells found there inclusively.
750     long latestTs = Math.max(opTs, latestCellTs);
751     if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) {
752       latestTs = EnvironmentEdgeManager.currentTimeMillis();
753     }
754     get.setTimeRange(0, latestTs + 1);
755     // In case of Put operation we set to read all versions. This was done to consider the case
756     // where columns are added with TS other than the Mutation TS. But normally this wont be the
757     // case with Put. There no need to get all versions but get latest version only.
758     if (!diffCellTsFromOpTs && request == OpType.PUT) {
759       get.setMaxVersions(1);
760     }
761     if (LOG.isTraceEnabled()) {
762       LOG.trace("Scanning for cells with " + get);
763     }
764     // This Map is identical to familyMap. The key is a BR rather than byte[].
765     // It will be easy to do gets over this new Map as we can create get keys over the Cell cf by
766     // new SimpleByteRange(cell.familyArray, cell.familyOffset, cell.familyLen)
767     Map<ByteRange, List<Cell>> familyMap1 = new HashMap<ByteRange, List<Cell>>();
768     for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
769       if (entry.getValue() instanceof List) {
770         familyMap1.put(new SimpleByteRange(entry.getKey()), (List<Cell>) entry.getValue());
771       }
772     }
773     RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
774     List<Cell> cells = Lists.newArrayList();
775     Cell prevCell = null;
776     ByteRange curFam = new SimpleByteRange();
777     boolean curColAllVersions = (request == OpType.DELETE);
778     long curColCheckTs = opTs;
779     boolean foundColumn = false;
780     try {
781       boolean more = false;
782       do {
783         cells.clear();
784         // scan with limit as 1 to hold down memory use on wide rows
785         more = scanner.next(cells, 1);
786         for (Cell cell: cells) {
787           if (LOG.isTraceEnabled()) {
788             LOG.trace("Found cell " + cell);
789           }
790           boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell);
791           if (colChange) foundColumn = false;
792           prevCell = cell;
793           if (!curColAllVersions && foundColumn) {
794             continue;
795           }
796           if (colChange && considerCellTs) {
797             curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
798             List<Cell> cols = familyMap1.get(curFam);
799             for (Cell col : cols) {
800               // null/empty qualifier is used to denote a Family delete. The TS and delete type
801               // associated with this is applicable for all columns within the family. That is
802               // why the below (col.getQualifierLength() == 0) check.
803               if ((col.getQualifierLength() == 0 && request == OpType.DELETE)
804                   || CellUtil.matchingQualifier(cell, col)) {
805                 byte type = col.getTypeByte();
806                 if (considerCellTs) {
807                   curColCheckTs = col.getTimestamp();
808                 }
809                 // For a Delete op we pass allVersions as true. When a Delete Mutation contains
810                 // a version delete for a column no need to check all the covering cells within
811                 // that column. Check all versions when Type is DeleteColumn or DeleteFamily
812                 // One version delete types are Delete/DeleteFamilyVersion
813                 curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type)
814                     || (KeyValue.Type.DeleteFamily.getCode() == type);
815                 break;
816               }
817             }
818           }
819           if (cell.getTimestamp() > curColCheckTs) {
820             // Just ignore this cell. This is not a covering cell.
821             continue;
822           }
823           foundColumn = true;
824           for (Action action: actions) {
825             // Are there permissions for this user for the cell?
826             if (!authManager.authorize(user, getTableName(e), cell, action)) {
827               // We can stop if the cell ACL denies access
828               return false;
829             }
830           }
831           cellGrants++;
832         }
833       } while (more);
834     } catch (AccessDeniedException ex) {
835       throw ex;
836     } catch (IOException ex) {
837       LOG.error("Exception while getting cells to calculate covering permission", ex);
838     } finally {
839       scanner.close();
840     }
841     // We should not authorize unless we have found one or more cell ACLs that
842     // grant access. This code is used to check for additional permissions
843     // after no table or CF grants are found.
844     return cellGrants > 0;
845   }
846 
847   private static void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) {
848     // Iterate over the entries in the familyMap, replacing the cells therein
849     // with new cells including the ACL data
850     for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) {
851       List<Cell> newCells = Lists.newArrayList();
852       for (Cell cell: e.getValue()) {
853         // Prepend the supplied perms in a new ACL tag to an update list of tags for the cell
854         List<Tag> tags = Lists.newArrayList(new Tag(AccessControlLists.ACL_TAG_TYPE, perms));
855         if (cell.getTagsLengthUnsigned() > 0) {
856           Iterator<Tag> tagIterator = CellUtil.tagsIterator(cell.getTagsArray(),
857             cell.getTagsOffset(), cell.getTagsLengthUnsigned());
858           while (tagIterator.hasNext()) {
859             tags.add(tagIterator.next());
860           }
861         }
862         // Ensure KeyValue so we can do a scatter gather copy. This is only a win if the
863         // incoming cell type is actually KeyValue.
864         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
865         byte[] bytes = kv.getBuffer();
866         newCells.add(
867           new KeyValue(bytes, kv.getRowOffset(), kv.getRowLength(),
868             bytes, kv.getFamilyOffset(), kv.getFamilyLength(),
869             bytes, kv.getQualifierOffset(), kv.getQualifierLength(),
870             kv.getTimestamp(), KeyValue.Type.codeToType(kv.getTypeByte()),
871             bytes, kv.getValueOffset(), kv.getValueLength(),
872             tags));
873       }
874       // This is supposed to be safe, won't CME
875       e.setValue(newCells);
876     }
877   }
878 
879   // Checks whether incoming cells contain any tag with type as ACL_TAG_TYPE. This tag
880   // type is reserved and should not be explicitly set by user.
881   private void checkForReservedTagPresence(User user, Mutation m) throws IOException {
882     // No need to check if we're not going to throw
883     if (!authorizationEnabled) {
884       m.setAttribute(TAG_CHECK_PASSED, TRUE);
885       return;
886     }
887     // Superusers are allowed to store cells unconditionally.
888     if (superusers.contains(user.getShortName())) {
889       m.setAttribute(TAG_CHECK_PASSED, TRUE);
890       return;
891     }
892     // We already checked (prePut vs preBatchMutation)
893     if (m.getAttribute(TAG_CHECK_PASSED) != null) {
894       return;
895     }
896     for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
897       Cell cell = cellScanner.current();
898       if (cell.getTagsLengthUnsigned() > 0) {
899         Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
900           cell.getTagsLengthUnsigned());
901         while (tagsItr.hasNext()) {
902           if (tagsItr.next().getType() == AccessControlLists.ACL_TAG_TYPE) {
903             throw new AccessDeniedException("Mutation contains cell with reserved type tag");
904           }
905         }
906       }
907     }
908     m.setAttribute(TAG_CHECK_PASSED, TRUE);
909   }
910 
911   /* ---- MasterObserver implementation ---- */
912 
913   public void start(CoprocessorEnvironment env) throws IOException {
914     CompoundConfiguration conf = new CompoundConfiguration();
915     conf.add(env.getConfiguration());
916 
917     authorizationEnabled = conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true);
918     if (!authorizationEnabled) {
919       LOG.warn("The AccessController has been loaded with authorization checks disabled.");
920     }
921 
922     shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY,
923       AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS);
924 
925     cellFeaturesEnabled = HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS;
926     if (!cellFeaturesEnabled) {
927       LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
928           + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY
929           + " accordingly.");
930     }
931 
932     ZooKeeperWatcher zk = null;
933     if (env instanceof MasterCoprocessorEnvironment) {
934       // if running on HMaster
935       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
936       zk = mEnv.getMasterServices().getZooKeeper();
937     } else if (env instanceof RegionServerCoprocessorEnvironment) {
938       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
939       zk = rsEnv.getRegionServerServices().getZooKeeper();
940     } else if (env instanceof RegionCoprocessorEnvironment) {
941       // if running at region
942       regionEnv = (RegionCoprocessorEnvironment) env;
943       conf.addStringMap(regionEnv.getRegion().getTableDesc().getConfiguration());
944       zk = regionEnv.getRegionServerServices().getZooKeeper();
945       compatibleEarlyTermination = conf.getBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT,
946         AccessControlConstants.DEFAULT_ATTRIBUTE_EARLY_OUT);
947     }
948 
949     // set the user-provider.
950     this.userProvider = UserProvider.instantiate(env.getConfiguration());
951 
952     // set up the list of users with superuser privilege
953     User user = userProvider.getCurrent();
954     superusers = Lists.asList(user.getShortName(),
955       conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
956 
957     // If zk is null or IOException while obtaining auth manager,
958     // throw RuntimeException so that the coprocessor is unloaded.
959     if (zk != null) {
960       try {
961         this.authManager = TableAuthManager.get(zk, env.getConfiguration());
962       } catch (IOException ioe) {
963         throw new RuntimeException("Error obtaining TableAuthManager", ioe);
964       }
965     } else {
966       throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
967     }
968 
969     if(!compatibleEarlyTermination && !cellFeaturesEnabled) {
970       LOG.warn("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
971           + " is required for " + AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT
972           + " to have an effect");
973     }
974 
975     tableAcls = new MapMaker().weakValues().makeMap();
976   }
977 
978   public void stop(CoprocessorEnvironment env) {
979 
980   }
981 
982    @Override
983   public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
984       final TableName tableName) throws IOException {
985     requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE);
986     final Configuration conf = c.getEnvironment().getConfiguration();
987     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
988       @Override
989       public Void run() throws Exception {
990         List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName);
991         if (acls != null) {
992           tableAcls.put(tableName, acls);
993         }
994         return null;
995       }
996     });
997   }
998 
999   @Override
1000   public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
1001       final TableName tableName) throws IOException {
1002     final Configuration conf = ctx.getEnvironment().getConfiguration();
1003     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1004       @Override
1005       public Void run() throws Exception {
1006         List<UserPermission> perms = tableAcls.get(tableName);
1007         if (perms != null) {
1008           for (UserPermission perm : perms) {
1009             AccessControlLists.addUserPermission(conf, perm);
1010           }
1011         }
1012         tableAcls.remove(tableName);
1013         return null;
1014       }
1015     });
1016   }
1017 
1018   @Override
1019   public void preTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1020       TableName tableName) throws IOException {}
1021 
1022   @Override
1023   public void postTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1024       TableName tableName) throws IOException {}
1025 
1026   @Override
1027   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
1028       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1029     Set<byte[]> families = desc.getFamiliesKeys();
1030     Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
1031     for (byte[] family: families) {
1032       familyMap.put(family, null);
1033     }
1034     requireGlobalPermission("createTable", Action.CREATE, desc.getTableName(), familyMap);
1035   }
1036 
1037   @Override
1038   public void postCreateTableHandler(final ObserverContext<MasterCoprocessorEnvironment> c,
1039       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1040     // When AC is used, it should be configured as the 1st CP.
1041     // In Master, the table operations like create, are handled by a Thread pool but the max size
1042     // for this pool is 1. So if multiple CPs create tables on startup, these creations will happen
1043     // sequentially only.
1044     // Related code in HMaster#startServiceThreads
1045     // {code}
1046     //   // We depend on there being only one instance of this executor running
1047     //   // at a time. To do concurrency, would need fencing of enable/disable of
1048     //   // tables.
1049     //   this.service.startExecutorService(ExecutorType.MASTER_TABLE_OPERATIONS, 1);
1050     // {code}
1051     // In future if we change this pool to have more threads, then there is a chance for thread,
1052     // creating acl table, getting delayed and by that time another table creation got over and
1053     // this hook is getting called. In such a case, we will need a wait logic here which will
1054     // wait till the acl table is created.
1055     if (AccessControlLists.isAclTable(desc)) {
1056       this.aclTabAvailable = true;
1057     } else if (!(TableName.NAMESPACE_TABLE_NAME.equals(desc.getTableName()))) {
1058       if (!aclTabAvailable) {
1059         LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". "
1060             + AccessControlLists.ACL_TABLE_NAME + " is not yet created. "
1061             + getClass().getSimpleName() + " should be configured as the first Coprocessor");
1062       } else {
1063         String owner = desc.getOwnerString();
1064         // default the table owner to current user, if not specified.
1065         if (owner == null)
1066           owner = getActiveUser().getShortName();
1067         final UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1068             desc.getTableName(), null, Action.values());
1069         // switch to the real hbase master user for doing the RPC on the ACL table
1070         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1071           @Override
1072           public Void run() throws Exception {
1073             AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(),
1074                 userperm);
1075             return null;
1076           }
1077         });
1078       }
1079     }
1080   }
1081 
1082   @Override
1083   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1084       throws IOException {
1085     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1086   }
1087 
1088   @Override
1089   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
1090       final TableName tableName) throws IOException {
1091     final Configuration conf = c.getEnvironment().getConfiguration();
1092     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1093       @Override
1094       public Void run() throws Exception {
1095         AccessControlLists.removeTablePermissions(conf, tableName);
1096         return null;
1097       }
1098     });
1099     this.authManager.getZKPermissionWatcher().deleteTableACLNode(tableName);
1100   }
1101 
1102   @Override
1103   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1104       HTableDescriptor htd) throws IOException {
1105     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1106   }
1107 
1108   @Override
1109   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
1110       TableName tableName, final HTableDescriptor htd) throws IOException {
1111     final Configuration conf = c.getEnvironment().getConfiguration();
1112     // default the table owner to current user, if not specified.
1113     final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() :
1114       getActiveUser().getShortName();
1115     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1116       @Override
1117       public Void run() throws Exception {
1118         UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1119           htd.getTableName(), null, Action.values());
1120         AccessControlLists.addUserPermission(conf, userperm);
1121         return null;
1122       }
1123     });
1124   }
1125 
1126   @Override
1127   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1128       HColumnDescriptor column) throws IOException {
1129     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
1130   }
1131 
1132   @Override
1133   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1134       HColumnDescriptor descriptor) throws IOException {
1135     requirePermission("modifyColumn", tableName, descriptor.getName(), null, Action.ADMIN,
1136       Action.CREATE);
1137   }
1138 
1139   @Override
1140   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1141       byte[] col) throws IOException {
1142     requirePermission("deleteColumn", tableName, col, null, Action.ADMIN, Action.CREATE);
1143   }
1144 
1145   @Override
1146   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
1147       final TableName tableName, final byte[] col) throws IOException {
1148     final Configuration conf = c.getEnvironment().getConfiguration();
1149     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1150       @Override
1151       public Void run() throws Exception {
1152         AccessControlLists.removeTablePermissions(conf, tableName, col);
1153         return null;
1154       }
1155     });
1156   }
1157 
1158   @Override
1159   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1160       throws IOException {
1161     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1162   }
1163 
1164   @Override
1165   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1166       throws IOException {
1167     if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
1168       // We have to unconditionally disallow disable of the ACL table when we are installed,
1169       // even if not enforcing authorizations. We are still allowing grants and revocations,
1170       // checking permissions and logging audit messages, etc. If the ACL table is not
1171       // available we will fail random actions all over the place.
1172       throw new AccessDeniedException("Not allowed to disable "
1173           + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed");
1174     }
1175     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1176   }
1177 
1178   @Override
1179   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
1180       ServerName srcServer, ServerName destServer) throws IOException {
1181     requirePermission("move", region.getTable(), null, null, Action.ADMIN);
1182   }
1183 
1184   @Override
1185   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
1186       throws IOException {
1187     requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
1188   }
1189 
1190   @Override
1191   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
1192       boolean force) throws IOException {
1193     requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
1194   }
1195 
1196   @Override
1197   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
1198       HRegionInfo regionInfo) throws IOException {
1199     requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
1200   }
1201 
1202   @Override
1203   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
1204       throws IOException {
1205     requirePermission("balance", Action.ADMIN);
1206   }
1207 
1208   @Override
1209   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
1210       boolean newValue) throws IOException {
1211     requirePermission("balanceSwitch", Action.ADMIN);
1212     return newValue;
1213   }
1214 
1215   @Override
1216   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
1217       throws IOException {
1218     requirePermission("shutdown", Action.ADMIN);
1219   }
1220 
1221   @Override
1222   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
1223       throws IOException {
1224     requirePermission("stopMaster", Action.ADMIN);
1225   }
1226 
1227   @Override
1228   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
1229       throws IOException {
1230     if (!MetaReader.tableExists(ctx.getEnvironment().getMasterServices().getCatalogTracker(),
1231         AccessControlLists.ACL_TABLE_NAME)) {
1232       // initialize the ACL storage table
1233       AccessControlLists.createACLTable(ctx.getEnvironment().getMasterServices());
1234     } else {
1235       aclTabAvailable = true;
1236     }
1237   }
1238 
1239   @Override
1240   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1241       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1242       throws IOException {
1243     requirePermission("snapshot", Action.ADMIN);
1244   }
1245 
1246   @Override
1247   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1248       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1249       throws IOException {
1250     requirePermission("clone", Action.ADMIN);
1251   }
1252 
1253   @Override
1254   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1255       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1256       throws IOException {
1257     requirePermission("restore", Action.ADMIN);
1258   }
1259 
1260   @Override
1261   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1262       final SnapshotDescription snapshot) throws IOException {
1263     requirePermission("deleteSnapshot", Action.ADMIN);
1264   }
1265 
1266   @Override
1267   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1268       NamespaceDescriptor ns) throws IOException {
1269     requirePermission("createNamespace", Action.ADMIN);
1270   }
1271 
1272   @Override
1273   public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1274       throws IOException {
1275     requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
1276   }
1277 
1278   @Override
1279   public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1280       final String namespace) throws IOException {
1281     final Configuration conf = ctx.getEnvironment().getConfiguration();
1282     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1283       @Override
1284       public Void run() throws Exception {
1285         AccessControlLists.removeNamespacePermissions(conf, namespace);
1286         return null;
1287       }
1288     });
1289     this.authManager.getZKPermissionWatcher().deleteNamespaceACLNode(namespace);
1290     LOG.info(namespace + " entry deleted in "+AccessControlLists.ACL_TABLE_NAME+" table.");
1291   }
1292 
1293   @Override
1294   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1295       NamespaceDescriptor ns) throws IOException {
1296     requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
1297   }
1298 
1299   /* ---- RegionObserver implementation ---- */
1300 
1301   @Override
1302   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
1303       throws IOException {
1304     RegionCoprocessorEnvironment env = e.getEnvironment();
1305     final HRegion region = env.getRegion();
1306     if (region == null) {
1307       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1308     } else {
1309       HRegionInfo regionInfo = region.getRegionInfo();
1310       if (regionInfo.getTable().isSystemTable()) {
1311         isSystemOrSuperUser(regionEnv.getConfiguration());
1312       } else {
1313         requirePermission("preOpen", Action.ADMIN);
1314       }
1315     }
1316   }
1317 
1318   @Override
1319   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
1320     RegionCoprocessorEnvironment env = c.getEnvironment();
1321     final HRegion region = env.getRegion();
1322     if (region == null) {
1323       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1324       return;
1325     }
1326     if (AccessControlLists.isAclRegion(region)) {
1327       aclRegion = true;
1328       // When this region is under recovering state, initialize will be handled by postLogReplay
1329       if (!region.isRecovering()) {
1330         try {
1331           initialize(env);
1332         } catch (IOException ex) {
1333           // if we can't obtain permissions, it's better to fail
1334           // than perform checks incorrectly
1335           throw new RuntimeException("Failed to initialize permissions cache", ex);
1336         }
1337       }
1338     } else {
1339       initialized = true;
1340     }
1341   }
1342 
1343   @Override
1344   public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> c) {
1345     if (aclRegion) {
1346       try {
1347         initialize(c.getEnvironment());
1348       } catch (IOException ex) {
1349         // if we can't obtain permissions, it's better to fail
1350         // than perform checks incorrectly
1351         throw new RuntimeException("Failed to initialize permissions cache", ex);
1352       }
1353     }
1354   }
1355 
1356   @Override
1357   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1358     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1359         Action.CREATE);
1360   }
1361 
1362   @Override
1363   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1364     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1365   }
1366 
1367   @Override
1368   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
1369       byte[] splitRow) throws IOException {
1370     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1371   }
1372 
1373   @Override
1374   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
1375       final Store store, final InternalScanner scanner, final ScanType scanType)
1376           throws IOException {
1377     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1378         Action.CREATE);
1379     return scanner;
1380   }
1381 
1382   @Override
1383   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
1384       final byte [] row, final byte [] family, final Result result)
1385       throws IOException {
1386     assert family != null;
1387     RegionCoprocessorEnvironment env = c.getEnvironment();
1388     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, null);
1389     User user = getActiveUser();
1390     AuthResult authResult = permissionGranted(OpType.GET_CLOSEST_ROW_BEFORE, user, env, families,
1391       Action.READ);
1392     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1393       authResult.setAllowed(checkCoveringPermission(OpType.GET_CLOSEST_ROW_BEFORE, env, row,
1394         families, HConstants.LATEST_TIMESTAMP, Action.READ));
1395       authResult.setReason("Covering cell set");
1396     }
1397     logResult(authResult);
1398     if (authorizationEnabled && !authResult.isAllowed()) {
1399       throw new AccessDeniedException("Insufficient permissions " +
1400         authResult.toContextString());
1401     }
1402   }
1403 
1404   private void internalPreRead(final ObserverContext<RegionCoprocessorEnvironment> c,
1405       final Query query, OpType opType) throws IOException {
1406     Filter filter = query.getFilter();
1407     // Don't wrap an AccessControlFilter
1408     if (filter != null && filter instanceof AccessControlFilter) {
1409       return;
1410     }
1411     User user = getActiveUser();
1412     RegionCoprocessorEnvironment env = c.getEnvironment();
1413     Map<byte[],? extends Collection<byte[]>> families = null;
1414     switch (opType) {
1415     case GET:
1416     case EXISTS:
1417       families = ((Get)query).getFamilyMap();
1418       break;
1419     case SCAN:
1420       families = ((Scan)query).getFamilyMap();
1421       break;
1422     default:
1423       throw new RuntimeException("Unhandled operation " + opType);
1424     }
1425     AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ);
1426     HRegion region = getRegion(env);
1427     TableName table = getTableName(region);
1428     Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap();
1429     for (HColumnDescriptor hcd : region.getTableDesc().getFamilies()) {
1430       cfVsMaxVersions.put(new SimpleByteRange(hcd.getName()), hcd.getMaxVersions());
1431     }
1432     if (!authResult.isAllowed()) {
1433       if (!cellFeaturesEnabled || compatibleEarlyTermination) {
1434         // Old behavior: Scan with only qualifier checks if we have partial
1435         // permission. Backwards compatible behavior is to throw an
1436         // AccessDeniedException immediately if there are no grants for table
1437         // or CF or CF+qual. Only proceed with an injected filter if there are
1438         // grants for qualifiers. Otherwise we will fall through below and log
1439         // the result and throw an ADE. We may end up checking qualifier
1440         // grants three times (permissionGranted above, here, and in the
1441         // filter) but that's the price of backwards compatibility.
1442         if (hasFamilyQualifierPermission(user, Action.READ, env, families)) {
1443           authResult.setAllowed(true);
1444           authResult.setReason("Access allowed with filter");
1445           // Only wrap the filter if we are enforcing authorizations
1446           if (authorizationEnabled) {
1447             Filter ourFilter = new AccessControlFilter(authManager, user, table,
1448               AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY,
1449               cfVsMaxVersions);
1450             // wrap any existing filter
1451             if (filter != null) {
1452               ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1453                 Lists.newArrayList(ourFilter, filter));
1454             }
1455             switch (opType) {
1456               case GET:
1457               case EXISTS:
1458                 ((Get)query).setFilter(ourFilter);
1459                 break;
1460               case SCAN:
1461                 ((Scan)query).setFilter(ourFilter);
1462                 break;
1463               default:
1464                 throw new RuntimeException("Unhandled operation " + opType);
1465             }
1466           }
1467         }
1468       } else {
1469         // New behavior: Any access we might be granted is more fine-grained
1470         // than whole table or CF. Simply inject a filter and return what is
1471         // allowed. We will not throw an AccessDeniedException. This is a
1472         // behavioral change since 0.96.
1473         authResult.setAllowed(true);
1474         authResult.setReason("Access allowed with filter");
1475         // Only wrap the filter if we are enforcing authorizations
1476         if (authorizationEnabled) {
1477           Filter ourFilter = new AccessControlFilter(authManager, user, table,
1478             AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions);
1479           // wrap any existing filter
1480           if (filter != null) {
1481             ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1482               Lists.newArrayList(ourFilter, filter));
1483           }
1484           switch (opType) {
1485             case GET:
1486             case EXISTS:
1487               ((Get)query).setFilter(ourFilter);
1488               break;
1489             case SCAN:
1490               ((Scan)query).setFilter(ourFilter);
1491               break;
1492             default:
1493               throw new RuntimeException("Unhandled operation " + opType);
1494           }
1495         }
1496       }
1497     }
1498 
1499     logResult(authResult);
1500     if (authorizationEnabled && !authResult.isAllowed()) {
1501       throw new AccessDeniedException("Insufficient permissions for user '"
1502           + (user != null ? user.getShortName() : "null")
1503           + "' (table=" + table + ", action=READ)");
1504     }
1505   }
1506 
1507   @Override
1508   public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
1509       final Get get, final List<Cell> result) throws IOException {
1510     internalPreRead(c, get, OpType.GET);
1511   }
1512 
1513   @Override
1514   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
1515       final Get get, final boolean exists) throws IOException {
1516     internalPreRead(c, get, OpType.EXISTS);
1517     return exists;
1518   }
1519 
1520   @Override
1521   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1522       final Put put, final WALEdit edit, final Durability durability)
1523       throws IOException {
1524     User user = getActiveUser();
1525     checkForReservedTagPresence(user, put);
1526 
1527     // Require WRITE permission to the table, CF, or top visible value, if any.
1528     // NOTE: We don't need to check the permissions for any earlier Puts
1529     // because we treat the ACLs in each Put as timestamped like any other
1530     // HBase value. A new ACL in a new Put applies to that Put. It doesn't
1531     // change the ACL of any previous Put. This allows simple evolution of
1532     // security policy over time without requiring expensive updates.
1533     RegionCoprocessorEnvironment env = c.getEnvironment();
1534     Map<byte[],? extends Collection<Cell>> families = put.getFamilyCellMap();
1535     AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE);
1536     logResult(authResult);
1537     if (!authResult.isAllowed()) {
1538       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1539         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1540       } else if (authorizationEnabled) {
1541         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1542       }
1543     }
1544 
1545     // Add cell ACLs from the operation to the cells themselves
1546     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1547     if (bytes != null) {
1548       if (cellFeaturesEnabled) {
1549         addCellPermissions(bytes, put.getFamilyCellMap());
1550       } else {
1551         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1552       }
1553     }
1554   }
1555 
1556   @Override
1557   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1558       final Put put, final WALEdit edit, final Durability durability) {
1559     if (aclRegion) {
1560       updateACL(c.getEnvironment(), put.getFamilyCellMap());
1561     }
1562   }
1563 
1564   @Override
1565   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1566       final Delete delete, final WALEdit edit, final Durability durability)
1567       throws IOException {
1568     // An ACL on a delete is useless, we shouldn't allow it
1569     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1570       throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1571     }
1572     // Require WRITE permissions on all cells covered by the delete. Unlike
1573     // for Puts we need to check all visible prior versions, because a major
1574     // compaction could remove them. If the user doesn't have permission to
1575     // overwrite any of the visible versions ('visible' defined as not covered
1576     // by a tombstone already) then we have to disallow this operation.
1577     RegionCoprocessorEnvironment env = c.getEnvironment();
1578     Map<byte[],? extends Collection<Cell>> families = delete.getFamilyCellMap();
1579     User user = getActiveUser();
1580     AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE);
1581     logResult(authResult);
1582     if (!authResult.isAllowed()) {
1583       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1584         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1585       } else if (authorizationEnabled) {
1586         throw new AccessDeniedException("Insufficient permissions " +
1587           authResult.toContextString());
1588       }
1589     }
1590   }
1591 
1592   @Override
1593   public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
1594       MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1595     if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1596       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1597       for (int i = 0; i < miniBatchOp.size(); i++) {
1598         Mutation m = miniBatchOp.getOperation(i);
1599         if (m.getAttribute(CHECK_COVERING_PERM) != null) {
1600           // We have a failure with table, cf and q perm checks and now giving a chance for cell
1601           // perm check
1602           OpType opType;
1603           if (m instanceof Put) {
1604             checkForReservedTagPresence(getActiveUser(), m);
1605             opType = OpType.PUT;
1606           } else {
1607             opType = OpType.DELETE;
1608           }
1609           AuthResult authResult = null;
1610           if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(),
1611             m.getFamilyCellMap(), m.getTimeStamp(), Action.WRITE)) {
1612             authResult = AuthResult.allow(opType.toString(), "Covering cell set",
1613               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1614           } else {
1615             authResult = AuthResult.deny(opType.toString(), "Covering cell set",
1616               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1617           }
1618           logResult(authResult);
1619           if (authorizationEnabled && !authResult.isAllowed()) {
1620             throw new AccessDeniedException("Insufficient permissions "
1621               + authResult.toContextString());
1622           }
1623         }
1624       }
1625     }
1626   }
1627 
1628   @Override
1629   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1630       final Delete delete, final WALEdit edit, final Durability durability)
1631       throws IOException {
1632     if (aclRegion) {
1633       updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1634     }
1635   }
1636 
1637   @Override
1638   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1639       final byte [] row, final byte [] family, final byte [] qualifier,
1640       final CompareFilter.CompareOp compareOp,
1641       final ByteArrayComparable comparator, final Put put,
1642       final boolean result) throws IOException {
1643     User user = getActiveUser();
1644     checkForReservedTagPresence(user, put);
1645 
1646     // Require READ and WRITE permissions on the table, CF, and KV to update
1647     RegionCoprocessorEnvironment env = c.getEnvironment();
1648     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1649     AuthResult authResult = permissionGranted(OpType.CHECK_AND_PUT, user, env, families,
1650       Action.READ, Action.WRITE);
1651     logResult(authResult);
1652     if (!authResult.isAllowed()) {
1653       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1654         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1655       } else if (authorizationEnabled) {
1656         throw new AccessDeniedException("Insufficient permissions " +
1657           authResult.toContextString());
1658       }
1659     }
1660 
1661     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1662     if (bytes != null) {
1663       if (cellFeaturesEnabled) {
1664         addCellPermissions(bytes, put.getFamilyCellMap());
1665       } else {
1666         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1667       }
1668     }
1669     return result;
1670   }
1671 
1672   @Override
1673   public boolean preCheckAndPutAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1674       final byte[] row, final byte[] family, final byte[] qualifier,
1675       final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put,
1676       final boolean result) throws IOException {
1677     if (put.getAttribute(CHECK_COVERING_PERM) != null) {
1678       // We had failure with table, cf and q perm checks and now giving a chance for cell
1679       // perm check
1680       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1681       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1682       AuthResult authResult = null;
1683       if (checkCoveringPermission(OpType.CHECK_AND_PUT, c.getEnvironment(), row, families,
1684           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1685         authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1686             getActiveUser(), Action.READ, table, families);
1687       } else {
1688         authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1689             getActiveUser(), Action.READ, table, families);
1690       }
1691       logResult(authResult);
1692       if (authorizationEnabled && !authResult.isAllowed()) {
1693         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1694       }
1695     }
1696     return result;
1697   }
1698 
1699   @Override
1700   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1701       final byte [] row, final byte [] family, final byte [] qualifier,
1702       final CompareFilter.CompareOp compareOp,
1703       final ByteArrayComparable comparator, final Delete delete,
1704       final boolean result) throws IOException {
1705     // An ACL on a delete is useless, we shouldn't allow it
1706     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1707       throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " +
1708           delete.toString());
1709     }
1710     // Require READ and WRITE permissions on the table, CF, and the KV covered
1711     // by the delete
1712     RegionCoprocessorEnvironment env = c.getEnvironment();
1713     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1714     User user = getActiveUser();
1715     AuthResult authResult = permissionGranted(OpType.CHECK_AND_DELETE, user, env, families,
1716       Action.READ, Action.WRITE);
1717     logResult(authResult);
1718     if (!authResult.isAllowed()) {
1719       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1720         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1721       } else if (authorizationEnabled) {
1722         throw new AccessDeniedException("Insufficient permissions " +
1723           authResult.toContextString());
1724       }
1725     }
1726     return result;
1727   }
1728 
1729   @Override
1730   public boolean preCheckAndDeleteAfterRowLock(
1731       final ObserverContext<RegionCoprocessorEnvironment> c, final byte[] row, final byte[] family,
1732       final byte[] qualifier, final CompareFilter.CompareOp compareOp,
1733       final ByteArrayComparable comparator, final Delete delete, final boolean result)
1734       throws IOException {
1735     if (delete.getAttribute(CHECK_COVERING_PERM) != null) {
1736       // We had failure with table, cf and q perm checks and now giving a chance for cell
1737       // perm check
1738       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1739       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1740       AuthResult authResult = null;
1741       if (checkCoveringPermission(OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families,
1742           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1743         authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1744             getActiveUser(), Action.READ, table, families);
1745       } else {
1746         authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1747             getActiveUser(), Action.READ, table, families);
1748       }
1749       logResult(authResult);
1750       if (authorizationEnabled && !authResult.isAllowed()) {
1751         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1752       }
1753     }
1754     return result;
1755   }
1756 
1757   @Override
1758   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1759       final byte [] row, final byte [] family, final byte [] qualifier,
1760       final long amount, final boolean writeToWAL)
1761       throws IOException {
1762     // Require WRITE permission to the table, CF, and the KV to be replaced by the
1763     // incremented value
1764     RegionCoprocessorEnvironment env = c.getEnvironment();
1765     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1766     User user = getActiveUser();
1767     AuthResult authResult = permissionGranted(OpType.INCREMENT_COLUMN_VALUE, user, env, families,
1768       Action.WRITE);
1769     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1770       authResult.setAllowed(checkCoveringPermission(OpType.INCREMENT_COLUMN_VALUE, env, row,
1771         families, HConstants.LATEST_TIMESTAMP, Action.WRITE));
1772       authResult.setReason("Covering cell set");
1773     }
1774     logResult(authResult);
1775     if (authorizationEnabled && !authResult.isAllowed()) {
1776       throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1777     }
1778     return -1;
1779   }
1780 
1781   @Override
1782   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1783       throws IOException {
1784     User user = getActiveUser();
1785     checkForReservedTagPresence(user, append);
1786 
1787     // Require WRITE permission to the table, CF, and the KV to be appended
1788     RegionCoprocessorEnvironment env = c.getEnvironment();
1789     Map<byte[],? extends Collection<Cell>> families = append.getFamilyCellMap();
1790     AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE);
1791     logResult(authResult);
1792     if (!authResult.isAllowed()) {
1793       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1794         append.setAttribute(CHECK_COVERING_PERM, TRUE);
1795       } else if (authorizationEnabled)  {
1796         throw new AccessDeniedException("Insufficient permissions " +
1797           authResult.toContextString());
1798       }
1799     }
1800 
1801     byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1802     if (bytes != null) {
1803       if (cellFeaturesEnabled) {
1804         addCellPermissions(bytes, append.getFamilyCellMap());
1805       } else {
1806         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1807       }
1808     }
1809 
1810     return null;
1811   }
1812 
1813   @Override
1814   public Result preAppendAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1815       final Append append) throws IOException {
1816     if (append.getAttribute(CHECK_COVERING_PERM) != null) {
1817       // We had failure with table, cf and q perm checks and now giving a chance for cell
1818       // perm check
1819       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1820       AuthResult authResult = null;
1821       if (checkCoveringPermission(OpType.APPEND, c.getEnvironment(), append.getRow(),
1822           append.getFamilyCellMap(), HConstants.LATEST_TIMESTAMP, Action.WRITE)) {
1823         authResult = AuthResult.allow(OpType.APPEND.toString(), "Covering cell set",
1824             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1825       } else {
1826         authResult = AuthResult.deny(OpType.APPEND.toString(), "Covering cell set",
1827             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1828       }
1829       logResult(authResult);
1830       if (authorizationEnabled && !authResult.isAllowed()) {
1831         throw new AccessDeniedException("Insufficient permissions " +
1832           authResult.toContextString());
1833       }
1834     }
1835     return null;
1836   }
1837 
1838   @Override
1839   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1840       final Increment increment)
1841       throws IOException {
1842     User user = getActiveUser();
1843     checkForReservedTagPresence(user, increment);
1844 
1845     // Require WRITE permission to the table, CF, and the KV to be replaced by
1846     // the incremented value
1847     RegionCoprocessorEnvironment env = c.getEnvironment();
1848     Map<byte[],? extends Collection<Cell>> families = increment.getFamilyCellMap();
1849     AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families,
1850       Action.WRITE);
1851     logResult(authResult);
1852     if (!authResult.isAllowed()) {
1853       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1854         increment.setAttribute(CHECK_COVERING_PERM, TRUE);
1855       } else if (authorizationEnabled) {
1856         throw new AccessDeniedException("Insufficient permissions " +
1857           authResult.toContextString());
1858       }
1859     }
1860 
1861     byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1862     if (bytes != null) {
1863       if (cellFeaturesEnabled) {
1864         addCellPermissions(bytes, increment.getFamilyCellMap());
1865       } else {
1866         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1867       }
1868     }
1869 
1870     return null;
1871   }
1872 
1873   @Override
1874   public Result preIncrementAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1875       final Increment increment) throws IOException {
1876     if (increment.getAttribute(CHECK_COVERING_PERM) != null) {
1877       // We had failure with table, cf and q perm checks and now giving a chance for cell
1878       // perm check
1879       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1880       AuthResult authResult = null;
1881       if (checkCoveringPermission(OpType.INCREMENT, c.getEnvironment(), increment.getRow(),
1882           increment.getFamilyCellMap(), increment.getTimeRange().getMax(), Action.WRITE)) {
1883         authResult = AuthResult.allow(OpType.INCREMENT.toString(), "Covering cell set",
1884             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1885       } else {
1886         authResult = AuthResult.deny(OpType.INCREMENT.toString(), "Covering cell set",
1887             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1888       }
1889       logResult(authResult);
1890       if (authorizationEnabled && !authResult.isAllowed()) {
1891         throw new AccessDeniedException("Insufficient permissions " +
1892           authResult.toContextString());
1893       }
1894     }
1895     return null;
1896   }
1897 
1898   @Override
1899   public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
1900       MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
1901     // If the HFile version is insufficient to persist tags, we won't have any
1902     // work to do here
1903     if (!cellFeaturesEnabled) {
1904       return newCell;
1905     }
1906 
1907     // Collect any ACLs from the old cell
1908     List<Tag> tags = Lists.newArrayList();
1909     ListMultimap<String,Permission> perms = ArrayListMultimap.create();
1910     if (oldCell != null) {
1911       // Save an object allocation where we can
1912       if (oldCell.getTagsLengthUnsigned() > 0) {
1913         Iterator<Tag> tagIterator = CellUtil.tagsIterator(oldCell.getTagsArray(),
1914           oldCell.getTagsOffset(), oldCell.getTagsLengthUnsigned());
1915         while (tagIterator.hasNext()) {
1916           Tag tag = tagIterator.next();
1917           if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) {
1918             // Not an ACL tag, just carry it through
1919             if (LOG.isTraceEnabled()) {
1920               LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() +
1921                 " length " + tag.getTagLength());
1922             }
1923             tags.add(tag);
1924           } else {
1925             // Merge the perms from the older ACL into the current permission set
1926             ListMultimap<String,Permission> kvPerms = ProtobufUtil.toUsersAndPermissions(
1927               AccessControlProtos.UsersAndPermissions.newBuilder().mergeFrom(
1928                 tag.getBuffer(), tag.getTagOffset(), tag.getTagLength()).build());
1929             perms.putAll(kvPerms);
1930           }
1931         }
1932       }
1933     }
1934 
1935     // Do we have an ACL on the operation?
1936     byte[] aclBytes = mutation.getACL();
1937     if (aclBytes != null) {
1938       // Yes, use it
1939       tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes));
1940     } else {
1941       // No, use what we carried forward
1942       if (perms != null) {
1943         // TODO: If we collected ACLs from more than one tag we may have a
1944         // List<Permission> of size > 1, this can be collapsed into a single
1945         // Permission
1946         if (LOG.isTraceEnabled()) {
1947           LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms);
1948         }
1949         tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE,
1950           ProtobufUtil.toUsersAndPermissions(perms).toByteArray()));
1951       }
1952     }
1953 
1954     // If we have no tags to add, just return
1955     if (tags.isEmpty()) {
1956       return newCell;
1957     }
1958 
1959     // We need to create another KV, unfortunately, because the current new KV
1960     // has no space for tags
1961     KeyValue newKv = KeyValueUtil.ensureKeyValue(newCell);
1962     byte[] bytes = newKv.getBuffer();
1963     KeyValue rewriteKv = new KeyValue(bytes, newKv.getRowOffset(), newKv.getRowLength(),
1964       bytes, newKv.getFamilyOffset(), newKv.getFamilyLength(),
1965       bytes, newKv.getQualifierOffset(), newKv.getQualifierLength(),
1966       newKv.getTimestamp(), KeyValue.Type.codeToType(newKv.getTypeByte()),
1967       bytes, newKv.getValueOffset(), newKv.getValueLength(),
1968       tags);
1969     // Preserve mvcc data
1970     rewriteKv.setMvccVersion(newKv.getMvccVersion());
1971     return rewriteKv;
1972   }
1973 
1974   @Override
1975   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1976       final Scan scan, final RegionScanner s) throws IOException {
1977     internalPreRead(c, scan, OpType.SCAN);
1978     return s;
1979   }
1980 
1981   @Override
1982   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1983       final Scan scan, final RegionScanner s) throws IOException {
1984     User user = getActiveUser();
1985     if (user != null && user.getShortName() != null) {
1986       // store reference to scanner owner for later checks
1987       scannerOwners.put(s, user.getShortName());
1988     }
1989     return s;
1990   }
1991 
1992   @Override
1993   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1994       final InternalScanner s, final List<Result> result,
1995       final int limit, final boolean hasNext) throws IOException {
1996     requireScannerOwner(s);
1997     return hasNext;
1998   }
1999 
2000   @Override
2001   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2002       final InternalScanner s) throws IOException {
2003     requireScannerOwner(s);
2004   }
2005 
2006   @Override
2007   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2008       final InternalScanner s) throws IOException {
2009     // clean up any associated owner mapping
2010     scannerOwners.remove(s);
2011   }
2012 
2013   /**
2014    * Verify, when servicing an RPC, that the caller is the scanner owner.
2015    * If so, we assume that access control is correctly enforced based on
2016    * the checks performed in preScannerOpen()
2017    */
2018   private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
2019     if (!RpcServer.isInRpcCallContext())
2020       return;
2021     String requestUserName = RpcServer.getRequestUserName();
2022     String owner = scannerOwners.get(s);
2023     if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) {
2024       throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
2025     }
2026   }
2027 
2028   /**
2029    * Verifies user has CREATE privileges on
2030    * the Column Families involved in the bulkLoadHFile
2031    * request. Specific Column Write privileges are presently
2032    * ignored.
2033    */
2034   @Override
2035   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
2036       List<Pair<byte[], String>> familyPaths) throws IOException {
2037     for(Pair<byte[],String> el : familyPaths) {
2038       requirePermission("preBulkLoadHFile",
2039           ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
2040           el.getFirst(),
2041           null,
2042           Action.CREATE);
2043     }
2044   }
2045 
2046   /**
2047    * Authorization check for
2048    * SecureBulkLoadProtocol.prepareBulkLoad()
2049    * @param ctx the context
2050    * @param request the request
2051    * @throws IOException
2052    */
2053   @Override
2054   public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2055                                  PrepareBulkLoadRequest request) throws IOException {
2056     requireAccess("prePareBulkLoad",
2057         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2058   }
2059 
2060   /**
2061    * Authorization security check for
2062    * SecureBulkLoadProtocol.cleanupBulkLoad()
2063    * @param ctx the context
2064    * @param request the request
2065    * @throws IOException
2066    */
2067   @Override
2068   public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2069                                  CleanupBulkLoadRequest request) throws IOException {
2070     requireAccess("preCleanupBulkLoad",
2071         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2072   }
2073 
2074   /* ---- EndpointObserver implementation ---- */
2075 
2076   @Override
2077   public Message preEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2078       Service service, String methodName, Message request) throws IOException {
2079     // Don't intercept calls to our own AccessControlService, we check for
2080     // appropriate permissions in the service handlers
2081     if (shouldCheckExecPermission && !(service instanceof AccessControlService)) {
2082       requirePermission("invoke(" + service.getDescriptorForType().getName() + "." +
2083         methodName + ")",
2084         getTableName(ctx.getEnvironment()), null, null,
2085         Action.EXEC);
2086     }
2087     return request;
2088   }
2089 
2090   @Override
2091   public void postEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2092       Service service, String methodName, Message request, Message.Builder responseBuilder)
2093       throws IOException { }
2094 
2095   /* ---- Protobuf AccessControlService implementation ---- */
2096 
2097   @Override
2098   public void grant(RpcController controller,
2099                     AccessControlProtos.GrantRequest request,
2100                     RpcCallback<AccessControlProtos.GrantResponse> done) {
2101     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2102     AccessControlProtos.GrantResponse response = null;
2103     try {
2104       // verify it's only running at .acl.
2105       if (aclRegion) {
2106         if (!initialized) {
2107           throw new CoprocessorException("AccessController not yet initialized");
2108         }
2109         if (LOG.isDebugEnabled()) {
2110           LOG.debug("Received request to grant access permission " + perm.toString());
2111         }
2112 
2113         switch(request.getUserPermission().getPermission().getType()) {
2114           case Global :
2115           case Table :
2116             requirePermission("grant", perm.getTableName(), perm.getFamily(),
2117               perm.getQualifier(), Action.ADMIN);
2118             break;
2119           case Namespace :
2120             requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace());
2121            break;
2122         }
2123 
2124         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2125           @Override
2126           public Void run() throws Exception {
2127             AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
2128             return null;
2129           }
2130         });
2131 
2132         if (AUDITLOG.isTraceEnabled()) {
2133           // audit log should store permission changes in addition to auth results
2134           AUDITLOG.trace("Granted permission " + perm.toString());
2135         }
2136       } else {
2137         throw new CoprocessorException(AccessController.class, "This method "
2138             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2139       }
2140       response = AccessControlProtos.GrantResponse.getDefaultInstance();
2141     } catch (IOException ioe) {
2142       // pass exception back up
2143       ResponseConverter.setControllerException(controller, ioe);
2144     }
2145     done.run(response);
2146   }
2147 
2148   @Override
2149   public void revoke(RpcController controller,
2150                      AccessControlProtos.RevokeRequest request,
2151                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
2152     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2153     AccessControlProtos.RevokeResponse response = null;
2154     try {
2155       // only allowed to be called on _acl_ region
2156       if (aclRegion) {
2157         if (!initialized) {
2158           throw new CoprocessorException("AccessController not yet initialized");
2159         }
2160         if (LOG.isDebugEnabled()) {
2161           LOG.debug("Received request to revoke access permission " + perm.toString());
2162         }
2163 
2164         switch(request.getUserPermission().getPermission().getType()) {
2165           case Global :
2166           case Table :
2167             requirePermission("revoke", perm.getTableName(), perm.getFamily(),
2168               perm.getQualifier(), Action.ADMIN);
2169             break;
2170           case Namespace :
2171             requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace());
2172             break;
2173         }
2174 
2175         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2176           @Override
2177           public Void run() throws Exception {
2178             AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
2179             return null;
2180           }
2181         });
2182 
2183         if (AUDITLOG.isTraceEnabled()) {
2184           // audit log should record all permission changes
2185           AUDITLOG.trace("Revoked permission " + perm.toString());
2186         }
2187       } else {
2188         throw new CoprocessorException(AccessController.class, "This method "
2189             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2190       }
2191       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
2192     } catch (IOException ioe) {
2193       // pass exception back up
2194       ResponseConverter.setControllerException(controller, ioe);
2195     }
2196     done.run(response);
2197   }
2198 
2199   @Override
2200   public void getUserPermissions(RpcController controller,
2201                                  AccessControlProtos.GetUserPermissionsRequest request,
2202                                  RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
2203     AccessControlProtos.GetUserPermissionsResponse response = null;
2204     try {
2205       // only allowed to be called on _acl_ region
2206       if (aclRegion) {
2207         if (!initialized) {
2208           throw new CoprocessorException("AccessController not yet initialized");
2209         }
2210         List<UserPermission> perms = null;
2211         if (request.getType() == AccessControlProtos.Permission.Type.Table) {
2212           final TableName table = request.hasTableName() ?
2213             ProtobufUtil.toTableName(request.getTableName()) : null;
2214           requirePermission("userPermissions", table, null, null, Action.ADMIN);
2215           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2216             @Override
2217             public List<UserPermission> run() throws Exception {
2218               return AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), table);
2219             }
2220           });
2221         } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
2222           final String namespace = request.getNamespaceName().toStringUtf8();
2223           requireGlobalPermission("userPermissions", Action.ADMIN, namespace);
2224           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2225             @Override
2226             public List<UserPermission> run() throws Exception {
2227               return AccessControlLists.getUserNamespacePermissions(regionEnv.getConfiguration(),
2228                 namespace);
2229             }
2230           });
2231         } else {
2232           requirePermission("userPermissions", Action.ADMIN);
2233           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2234             @Override
2235             public List<UserPermission> run() throws Exception {
2236               return AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), null);
2237             }
2238           });
2239         }
2240         response = ResponseConverter.buildGetUserPermissionsResponse(perms);
2241       } else {
2242         throw new CoprocessorException(AccessController.class, "This method "
2243             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2244       }
2245     } catch (IOException ioe) {
2246       // pass exception back up
2247       ResponseConverter.setControllerException(controller, ioe);
2248     }
2249     done.run(response);
2250   }
2251 
2252   @Override
2253   public void checkPermissions(RpcController controller,
2254                                AccessControlProtos.CheckPermissionsRequest request,
2255                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
2256     Permission[] permissions = new Permission[request.getPermissionCount()];
2257     for (int i=0; i < request.getPermissionCount(); i++) {
2258       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
2259     }
2260     AccessControlProtos.CheckPermissionsResponse response = null;
2261     try {
2262       User user = getActiveUser();
2263       TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
2264       for (Permission permission : permissions) {
2265         if (permission instanceof TablePermission) {
2266           // Check table permissions
2267 
2268           TablePermission tperm = (TablePermission) permission;
2269           for (Action action : permission.getActions()) {
2270             if (!tperm.getTableName().equals(tableName)) {
2271               throw new CoprocessorException(AccessController.class, String.format("This method "
2272                   + "can only execute at the table specified in TablePermission. " +
2273                   "Table of the region:%s , requested table:%s", tableName,
2274                   tperm.getTableName()));
2275             }
2276 
2277             Map<byte[], Set<byte[]>> familyMap =
2278                 new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
2279             if (tperm.getFamily() != null) {
2280               if (tperm.getQualifier() != null) {
2281                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
2282                 qualifiers.add(tperm.getQualifier());
2283                 familyMap.put(tperm.getFamily(), qualifiers);
2284               } else {
2285                 familyMap.put(tperm.getFamily(), null);
2286               }
2287             }
2288 
2289             AuthResult result = permissionGranted("checkPermissions", user, action, regionEnv,
2290               familyMap);
2291             logResult(result);
2292             if (!result.isAllowed()) {
2293               // Even if passive we need to throw an exception here, we support checking
2294               // effective permissions, so throw unconditionally
2295               throw new AccessDeniedException("Insufficient permissions (table=" + tableName +
2296                 (familyMap.size() > 0 ? ", family: " + result.toFamilyString() : "") +
2297                 ", action=" + action.toString() + ")");
2298             }
2299           }
2300 
2301         } else {
2302           // Check global permissions
2303 
2304           for (Action action : permission.getActions()) {
2305             AuthResult result;
2306             if (authManager.authorize(user, action)) {
2307               result = AuthResult.allow("checkPermissions", "Global action allowed", user,
2308                 action, null, null);
2309             } else {
2310               result = AuthResult.deny("checkPermissions", "Global action denied", user, action,
2311                 null, null);
2312             }
2313             logResult(result);
2314             if (!result.isAllowed()) {
2315               // Even if passive we need to throw an exception here, we support checking
2316               // effective permissions, so throw unconditionally
2317               throw new AccessDeniedException("Insufficient permissions (action=" +
2318                 action.toString() + ")");
2319             }
2320           }
2321         }
2322       }
2323       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
2324     } catch (IOException ioe) {
2325       ResponseConverter.setControllerException(controller, ioe);
2326     }
2327     done.run(response);
2328   }
2329 
2330   @Override
2331   public Service getService() {
2332     return AccessControlProtos.AccessControlService.newReflectiveService(this);
2333   }
2334 
2335   private HRegion getRegion(RegionCoprocessorEnvironment e) {
2336     return e.getRegion();
2337   }
2338 
2339   private TableName getTableName(RegionCoprocessorEnvironment e) {
2340     HRegion region = e.getRegion();
2341     if (region != null) {
2342       return getTableName(region);
2343     }
2344     return null;
2345   }
2346 
2347   private TableName getTableName(HRegion region) {
2348     HRegionInfo regionInfo = region.getRegionInfo();
2349     if (regionInfo != null) {
2350       return regionInfo.getTable();
2351     }
2352     return null;
2353   }
2354 
2355   @Override
2356   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
2357       throws IOException {
2358     requirePermission("preClose", Action.ADMIN);
2359   }
2360 
2361   private void isSystemOrSuperUser(Configuration conf) throws IOException {
2362     // No need to check if we're not going to throw
2363     if (!authorizationEnabled) {
2364       return;
2365     }
2366     User user = userProvider.getCurrent();
2367     if (user == null) {
2368       throw new IOException("Unable to obtain the current user, " +
2369         "authorization checks for internal operations will not work correctly!");
2370     }
2371     User activeUser = getActiveUser();
2372     if (!(superusers.contains(activeUser.getShortName()))) {
2373       throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null") +
2374         "is not system or super user.");
2375     }
2376   }
2377 
2378   @Override
2379   public void preStopRegionServer(
2380       ObserverContext<RegionServerCoprocessorEnvironment> env)
2381       throws IOException {
2382     requirePermission("preStopRegionServer", Action.ADMIN);
2383   }
2384 
2385   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
2386       byte[] qualifier) {
2387     if (family == null) {
2388       return null;
2389     }
2390 
2391     Map<byte[], Collection<byte[]>> familyMap = new TreeMap<byte[], Collection<byte[]>>(Bytes.BYTES_COMPARATOR);
2392     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
2393     return familyMap;
2394   }
2395 
2396   @Override
2397   public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2398       List<TableName> tableNamesList,
2399       List<HTableDescriptor> descriptors) throws IOException {
2400     // If the list is empty, this is a request for all table descriptors and requires GLOBAL
2401     // ADMIN privs.
2402     if (tableNamesList == null || tableNamesList.isEmpty()) {
2403       requireGlobalPermission("getTableDescriptors", Action.ADMIN, null, null);
2404     }
2405     // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
2406     // request can be granted.
2407     else {
2408       MasterServices masterServices = ctx.getEnvironment().getMasterServices();
2409       for (TableName tableName: tableNamesList) {
2410         // Skip checks for a table that does not exist
2411         if (masterServices.getTableDescriptors().get(tableName) == null) {
2412           continue;
2413         }
2414         requirePermission("getTableDescriptors", tableName, null, null,
2415           Action.ADMIN, Action.CREATE);
2416       }
2417     }
2418   }
2419 
2420   @Override
2421   public void preMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx, HRegion regionA,
2422       HRegion regionB) throws IOException {
2423     requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null,
2424       Action.ADMIN);
2425   }
2426 
2427   @Override
2428   public void postMerge(ObserverContext<RegionServerCoprocessorEnvironment> c, HRegion regionA,
2429       HRegion regionB, HRegion mergedRegion) throws IOException { }
2430 
2431   @Override
2432   public void preMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2433       HRegion regionA, HRegion regionB, List<Mutation> metaEntries) throws IOException { }
2434 
2435   @Override
2436   public void postMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2437       HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { }
2438 
2439   @Override
2440   public void preRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2441       HRegion regionA, HRegion regionB) throws IOException { }
2442 
2443   @Override
2444   public void postRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2445       HRegion regionA, HRegion regionB) throws IOException { }
2446 
2447   @Override
2448   public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2449       throws IOException {
2450     requirePermission("preRollLogWriterRequest", Permission.Action.ADMIN);
2451   }
2452 
2453   @Override
2454   public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2455       throws IOException { }
2456 
2457   @Override
2458   public ReplicationEndpoint postCreateReplicationEndPoint(
2459       ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
2460     return endpoint;
2461   }
2462 
2463   @Override
2464   public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2465       List<WALEntry> entries, CellScanner cells) throws IOException {
2466     requirePermission("replicateLogEntries", Action.WRITE);
2467   }
2468 
2469   @Override
2470   public void postReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2471       List<WALEntry> entries, CellScanner cells) throws IOException {
2472   }
2473 }