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