View Javadoc

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