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