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