View Javadoc

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