View Javadoc

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