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.getOrCreate(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     if (this.authManager != null) {
986       TableAuthManager.release(authManager);
987     }
988   }
989 
990    @Override
991   public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
992       final TableName tableName) throws IOException {
993     requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE);
994     final Configuration conf = c.getEnvironment().getConfiguration();
995     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
996       @Override
997       public Void run() throws Exception {
998         List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName);
999         if (acls != null) {
1000           tableAcls.put(tableName, acls);
1001         }
1002         return null;
1003       }
1004     });
1005   }
1006 
1007   @Override
1008   public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
1009       final TableName tableName) throws IOException {
1010     final Configuration conf = ctx.getEnvironment().getConfiguration();
1011     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1012       @Override
1013       public Void run() throws Exception {
1014         List<UserPermission> perms = tableAcls.get(tableName);
1015         if (perms != null) {
1016           for (UserPermission perm : perms) {
1017             AccessControlLists.addUserPermission(conf, perm);
1018           }
1019         }
1020         tableAcls.remove(tableName);
1021         return null;
1022       }
1023     });
1024   }
1025 
1026   @Override
1027   public void preTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1028       TableName tableName) throws IOException {}
1029 
1030   @Override
1031   public void postTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1032       TableName tableName) throws IOException {}
1033 
1034   @Override
1035   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
1036       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1037     Set<byte[]> families = desc.getFamiliesKeys();
1038     Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
1039     for (byte[] family: families) {
1040       familyMap.put(family, null);
1041     }
1042     requireGlobalPermission("createTable", Action.CREATE, desc.getTableName(), familyMap);
1043   }
1044 
1045   @Override
1046   public void postCreateTableHandler(final ObserverContext<MasterCoprocessorEnvironment> c,
1047       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1048     // When AC is used, it should be configured as the 1st CP.
1049     // In Master, the table operations like create, are handled by a Thread pool but the max size
1050     // for this pool is 1. So if multiple CPs create tables on startup, these creations will happen
1051     // sequentially only.
1052     // Related code in HMaster#startServiceThreads
1053     // {code}
1054     //   // We depend on there being only one instance of this executor running
1055     //   // at a time. To do concurrency, would need fencing of enable/disable of
1056     //   // tables.
1057     //   this.service.startExecutorService(ExecutorType.MASTER_TABLE_OPERATIONS, 1);
1058     // {code}
1059     // In future if we change this pool to have more threads, then there is a chance for thread,
1060     // creating acl table, getting delayed and by that time another table creation got over and
1061     // this hook is getting called. In such a case, we will need a wait logic here which will
1062     // wait till the acl table is created.
1063     if (AccessControlLists.isAclTable(desc)) {
1064       this.aclTabAvailable = true;
1065     } else if (!(TableName.NAMESPACE_TABLE_NAME.equals(desc.getTableName()))) {
1066       if (!aclTabAvailable) {
1067         LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". "
1068             + AccessControlLists.ACL_TABLE_NAME + " is not yet created. "
1069             + getClass().getSimpleName() + " should be configured as the first Coprocessor");
1070       } else {
1071         String owner = desc.getOwnerString();
1072         // default the table owner to current user, if not specified.
1073         if (owner == null)
1074           owner = getActiveUser().getShortName();
1075         final UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1076             desc.getTableName(), null, Action.values());
1077         // switch to the real hbase master user for doing the RPC on the ACL table
1078         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1079           @Override
1080           public Void run() throws Exception {
1081             AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(),
1082                 userperm);
1083             return null;
1084           }
1085         });
1086       }
1087     }
1088   }
1089 
1090   @Override
1091   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1092       throws IOException {
1093     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1094   }
1095 
1096   @Override
1097   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
1098       final TableName tableName) throws IOException {
1099     final Configuration conf = c.getEnvironment().getConfiguration();
1100     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1101       @Override
1102       public Void run() throws Exception {
1103         AccessControlLists.removeTablePermissions(conf, tableName);
1104         return null;
1105       }
1106     });
1107     this.authManager.getZKPermissionWatcher().deleteTableACLNode(tableName);
1108   }
1109 
1110   @Override
1111   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1112       HTableDescriptor htd) throws IOException {
1113     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1114   }
1115 
1116   @Override
1117   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
1118       TableName tableName, final HTableDescriptor htd) throws IOException {
1119     final Configuration conf = c.getEnvironment().getConfiguration();
1120     // default the table owner to current user, if not specified.
1121     final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() :
1122       getActiveUser().getShortName();
1123     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1124       @Override
1125       public Void run() throws Exception {
1126         UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1127             htd.getTableName(), null, Action.values());
1128         AccessControlLists.addUserPermission(conf, userperm);
1129         return null;
1130       }
1131     });
1132   }
1133 
1134   @Override
1135   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1136       HColumnDescriptor column) throws IOException {
1137     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
1138   }
1139 
1140   @Override
1141   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1142       HColumnDescriptor descriptor) throws IOException {
1143     requirePermission("modifyColumn", tableName, descriptor.getName(), null, Action.ADMIN,
1144         Action.CREATE);
1145   }
1146 
1147   @Override
1148   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1149       byte[] col) throws IOException {
1150     requirePermission("deleteColumn", tableName, col, null, Action.ADMIN, Action.CREATE);
1151   }
1152 
1153   @Override
1154   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
1155       final TableName tableName, final byte[] col) throws IOException {
1156     final Configuration conf = c.getEnvironment().getConfiguration();
1157     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1158       @Override
1159       public Void run() throws Exception {
1160         AccessControlLists.removeTablePermissions(conf, tableName, col);
1161         return null;
1162       }
1163     });
1164   }
1165 
1166   @Override
1167   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1168       throws IOException {
1169     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1170   }
1171 
1172   @Override
1173   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1174       throws IOException {
1175     if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
1176       // We have to unconditionally disallow disable of the ACL table when we are installed,
1177       // even if not enforcing authorizations. We are still allowing grants and revocations,
1178       // checking permissions and logging audit messages, etc. If the ACL table is not
1179       // available we will fail random actions all over the place.
1180       throw new AccessDeniedException("Not allowed to disable "
1181           + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed");
1182     }
1183     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1184   }
1185 
1186   @Override
1187   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
1188       ServerName srcServer, ServerName destServer) throws IOException {
1189     requirePermission("move", region.getTable(), null, null, Action.ADMIN);
1190   }
1191 
1192   @Override
1193   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
1194       throws IOException {
1195     requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
1196   }
1197 
1198   @Override
1199   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
1200       boolean force) throws IOException {
1201     requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
1202   }
1203 
1204   @Override
1205   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
1206       HRegionInfo regionInfo) throws IOException {
1207     requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
1208   }
1209 
1210   @Override
1211   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
1212       throws IOException {
1213     requirePermission("balance", Action.ADMIN);
1214   }
1215 
1216   @Override
1217   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
1218       boolean newValue) throws IOException {
1219     requirePermission("balanceSwitch", Action.ADMIN);
1220     return newValue;
1221   }
1222 
1223   @Override
1224   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
1225       throws IOException {
1226     requirePermission("shutdown", Action.ADMIN);
1227   }
1228 
1229   @Override
1230   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
1231       throws IOException {
1232     requirePermission("stopMaster", Action.ADMIN);
1233   }
1234 
1235   @Override
1236   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
1237       throws IOException {
1238     if (!MetaReader.tableExists(ctx.getEnvironment().getMasterServices().getCatalogTracker(),
1239         AccessControlLists.ACL_TABLE_NAME)) {
1240       // initialize the ACL storage table
1241       AccessControlLists.createACLTable(ctx.getEnvironment().getMasterServices());
1242     } else {
1243       aclTabAvailable = true;
1244     }
1245   }
1246 
1247   @Override
1248   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1249       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1250       throws IOException {
1251     requirePermission("snapshot", Action.ADMIN);
1252   }
1253 
1254   @Override
1255   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1256       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1257       throws IOException {
1258     requirePermission("clone", Action.ADMIN);
1259   }
1260 
1261   @Override
1262   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1263       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1264       throws IOException {
1265     requirePermission("restore", Action.ADMIN);
1266   }
1267 
1268   @Override
1269   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1270       final SnapshotDescription snapshot) throws IOException {
1271     requirePermission("deleteSnapshot", Action.ADMIN);
1272   }
1273 
1274   @Override
1275   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1276       NamespaceDescriptor ns) throws IOException {
1277     requirePermission("createNamespace", Action.ADMIN);
1278   }
1279 
1280   @Override
1281   public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1282       throws IOException {
1283     requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
1284   }
1285 
1286   @Override
1287   public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1288       final String namespace) throws IOException {
1289     final Configuration conf = ctx.getEnvironment().getConfiguration();
1290     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1291       @Override
1292       public Void run() throws Exception {
1293         AccessControlLists.removeNamespacePermissions(conf, namespace);
1294         return null;
1295       }
1296     });
1297     this.authManager.getZKPermissionWatcher().deleteNamespaceACLNode(namespace);
1298     LOG.info(namespace + " entry deleted in " + AccessControlLists.ACL_TABLE_NAME + " table.");
1299   }
1300 
1301   @Override
1302   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1303       NamespaceDescriptor ns) throws IOException {
1304     requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
1305   }
1306 
1307   /* ---- RegionObserver implementation ---- */
1308 
1309   @Override
1310   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
1311       throws IOException {
1312     RegionCoprocessorEnvironment env = e.getEnvironment();
1313     final HRegion region = env.getRegion();
1314     if (region == null) {
1315       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1316     } else {
1317       HRegionInfo regionInfo = region.getRegionInfo();
1318       if (regionInfo.getTable().isSystemTable()) {
1319         checkSystemOrSuperUser();
1320       } else {
1321         requirePermission("preOpen", Action.ADMIN);
1322       }
1323     }
1324   }
1325 
1326   @Override
1327   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
1328     RegionCoprocessorEnvironment env = c.getEnvironment();
1329     final HRegion region = env.getRegion();
1330     if (region == null) {
1331       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1332       return;
1333     }
1334     if (AccessControlLists.isAclRegion(region)) {
1335       aclRegion = true;
1336       // When this region is under recovering state, initialize will be handled by postLogReplay
1337       if (!region.isRecovering()) {
1338         try {
1339           initialize(env);
1340         } catch (IOException ex) {
1341           // if we can't obtain permissions, it's better to fail
1342           // than perform checks incorrectly
1343           throw new RuntimeException("Failed to initialize permissions cache", ex);
1344         }
1345       }
1346     } else {
1347       initialized = true;
1348     }
1349   }
1350 
1351   @Override
1352   public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> c) {
1353     if (aclRegion) {
1354       try {
1355         initialize(c.getEnvironment());
1356       } catch (IOException ex) {
1357         // if we can't obtain permissions, it's better to fail
1358         // than perform checks incorrectly
1359         throw new RuntimeException("Failed to initialize permissions cache", ex);
1360       }
1361     }
1362   }
1363 
1364   @Override
1365   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1366     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1367         Action.CREATE);
1368   }
1369 
1370   @Override
1371   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1372     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1373   }
1374 
1375   @Override
1376   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
1377       byte[] splitRow) throws IOException {
1378     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1379   }
1380 
1381   @Override
1382   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
1383       final Store store, final InternalScanner scanner, final ScanType scanType)
1384           throws IOException {
1385     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1386         Action.CREATE);
1387     return scanner;
1388   }
1389 
1390   @Override
1391   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
1392       final byte [] row, final byte [] family, final Result result)
1393       throws IOException {
1394     assert family != null;
1395     RegionCoprocessorEnvironment env = c.getEnvironment();
1396     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, null);
1397     User user = getActiveUser();
1398     AuthResult authResult = permissionGranted(OpType.GET_CLOSEST_ROW_BEFORE, user, env, families,
1399       Action.READ);
1400     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1401       authResult.setAllowed(checkCoveringPermission(OpType.GET_CLOSEST_ROW_BEFORE, env, row,
1402         families, HConstants.LATEST_TIMESTAMP, Action.READ));
1403       authResult.setReason("Covering cell set");
1404     }
1405     logResult(authResult);
1406     if (authorizationEnabled && !authResult.isAllowed()) {
1407       throw new AccessDeniedException("Insufficient permissions " +
1408         authResult.toContextString());
1409     }
1410   }
1411 
1412   private void internalPreRead(final ObserverContext<RegionCoprocessorEnvironment> c,
1413       final Query query, OpType opType) throws IOException {
1414     Filter filter = query.getFilter();
1415     // Don't wrap an AccessControlFilter
1416     if (filter != null && filter instanceof AccessControlFilter) {
1417       return;
1418     }
1419     User user = getActiveUser();
1420     RegionCoprocessorEnvironment env = c.getEnvironment();
1421     Map<byte[],? extends Collection<byte[]>> families = null;
1422     switch (opType) {
1423     case GET:
1424     case EXISTS:
1425       families = ((Get)query).getFamilyMap();
1426       break;
1427     case SCAN:
1428       families = ((Scan)query).getFamilyMap();
1429       break;
1430     default:
1431       throw new RuntimeException("Unhandled operation " + opType);
1432     }
1433     AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ);
1434     HRegion region = getRegion(env);
1435     TableName table = getTableName(region);
1436     Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap();
1437     for (HColumnDescriptor hcd : region.getTableDesc().getFamilies()) {
1438       cfVsMaxVersions.put(new SimpleByteRange(hcd.getName()), hcd.getMaxVersions());
1439     }
1440     if (!authResult.isAllowed()) {
1441       if (!cellFeaturesEnabled || compatibleEarlyTermination) {
1442         // Old behavior: Scan with only qualifier checks if we have partial
1443         // permission. Backwards compatible behavior is to throw an
1444         // AccessDeniedException immediately if there are no grants for table
1445         // or CF or CF+qual. Only proceed with an injected filter if there are
1446         // grants for qualifiers. Otherwise we will fall through below and log
1447         // the result and throw an ADE. We may end up checking qualifier
1448         // grants three times (permissionGranted above, here, and in the
1449         // filter) but that's the price of backwards compatibility.
1450         if (hasFamilyQualifierPermission(user, Action.READ, env, families)) {
1451           authResult.setAllowed(true);
1452           authResult.setReason("Access allowed with filter");
1453           // Only wrap the filter if we are enforcing authorizations
1454           if (authorizationEnabled) {
1455             Filter ourFilter = new AccessControlFilter(authManager, user, table,
1456               AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY,
1457               cfVsMaxVersions);
1458             // wrap any existing filter
1459             if (filter != null) {
1460               ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1461                 Lists.newArrayList(ourFilter, filter));
1462             }
1463             switch (opType) {
1464               case GET:
1465               case EXISTS:
1466                 ((Get)query).setFilter(ourFilter);
1467                 break;
1468               case SCAN:
1469                 ((Scan)query).setFilter(ourFilter);
1470                 break;
1471               default:
1472                 throw new RuntimeException("Unhandled operation " + opType);
1473             }
1474           }
1475         }
1476       } else {
1477         // New behavior: Any access we might be granted is more fine-grained
1478         // than whole table or CF. Simply inject a filter and return what is
1479         // allowed. We will not throw an AccessDeniedException. This is a
1480         // behavioral change since 0.96.
1481         authResult.setAllowed(true);
1482         authResult.setReason("Access allowed with filter");
1483         // Only wrap the filter if we are enforcing authorizations
1484         if (authorizationEnabled) {
1485           Filter ourFilter = new AccessControlFilter(authManager, user, table,
1486             AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions);
1487           // wrap any existing filter
1488           if (filter != null) {
1489             ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1490               Lists.newArrayList(ourFilter, filter));
1491           }
1492           switch (opType) {
1493             case GET:
1494             case EXISTS:
1495               ((Get)query).setFilter(ourFilter);
1496               break;
1497             case SCAN:
1498               ((Scan)query).setFilter(ourFilter);
1499               break;
1500             default:
1501               throw new RuntimeException("Unhandled operation " + opType);
1502           }
1503         }
1504       }
1505     }
1506 
1507     logResult(authResult);
1508     if (authorizationEnabled && !authResult.isAllowed()) {
1509       throw new AccessDeniedException("Insufficient permissions for user '"
1510           + (user != null ? user.getShortName() : "null")
1511           + "' (table=" + table + ", action=READ)");
1512     }
1513   }
1514 
1515   @Override
1516   public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
1517       final Get get, final List<Cell> result) throws IOException {
1518     internalPreRead(c, get, OpType.GET);
1519   }
1520 
1521   @Override
1522   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
1523       final Get get, final boolean exists) throws IOException {
1524     internalPreRead(c, get, OpType.EXISTS);
1525     return exists;
1526   }
1527 
1528   @Override
1529   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1530       final Put put, final WALEdit edit, final Durability durability)
1531       throws IOException {
1532     User user = getActiveUser();
1533     checkForReservedTagPresence(user, put);
1534 
1535     // Require WRITE permission to the table, CF, or top visible value, if any.
1536     // NOTE: We don't need to check the permissions for any earlier Puts
1537     // because we treat the ACLs in each Put as timestamped like any other
1538     // HBase value. A new ACL in a new Put applies to that Put. It doesn't
1539     // change the ACL of any previous Put. This allows simple evolution of
1540     // security policy over time without requiring expensive updates.
1541     RegionCoprocessorEnvironment env = c.getEnvironment();
1542     Map<byte[],? extends Collection<Cell>> families = put.getFamilyCellMap();
1543     AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE);
1544     logResult(authResult);
1545     if (!authResult.isAllowed()) {
1546       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1547         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1548       } else if (authorizationEnabled) {
1549         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1550       }
1551     }
1552 
1553     // Add cell ACLs from the operation to the cells themselves
1554     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1555     if (bytes != null) {
1556       if (cellFeaturesEnabled) {
1557         addCellPermissions(bytes, put.getFamilyCellMap());
1558       } else {
1559         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1560       }
1561     }
1562   }
1563 
1564   @Override
1565   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1566       final Put put, final WALEdit edit, final Durability durability) {
1567     if (aclRegion) {
1568       updateACL(c.getEnvironment(), put.getFamilyCellMap());
1569     }
1570   }
1571 
1572   @Override
1573   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1574       final Delete delete, final WALEdit edit, final Durability durability)
1575       throws IOException {
1576     // An ACL on a delete is useless, we shouldn't allow it
1577     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1578       throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1579     }
1580     // Require WRITE permissions on all cells covered by the delete. Unlike
1581     // for Puts we need to check all visible prior versions, because a major
1582     // compaction could remove them. If the user doesn't have permission to
1583     // overwrite any of the visible versions ('visible' defined as not covered
1584     // by a tombstone already) then we have to disallow this operation.
1585     RegionCoprocessorEnvironment env = c.getEnvironment();
1586     Map<byte[],? extends Collection<Cell>> families = delete.getFamilyCellMap();
1587     User user = getActiveUser();
1588     AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE);
1589     logResult(authResult);
1590     if (!authResult.isAllowed()) {
1591       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1592         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1593       } else if (authorizationEnabled) {
1594         throw new AccessDeniedException("Insufficient permissions " +
1595           authResult.toContextString());
1596       }
1597     }
1598   }
1599 
1600   @Override
1601   public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
1602       MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1603     if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1604       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1605       for (int i = 0; i < miniBatchOp.size(); i++) {
1606         Mutation m = miniBatchOp.getOperation(i);
1607         if (m.getAttribute(CHECK_COVERING_PERM) != null) {
1608           // We have a failure with table, cf and q perm checks and now giving a chance for cell
1609           // perm check
1610           OpType opType;
1611           if (m instanceof Put) {
1612             checkForReservedTagPresence(getActiveUser(), m);
1613             opType = OpType.PUT;
1614           } else {
1615             opType = OpType.DELETE;
1616           }
1617           AuthResult authResult = null;
1618           if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(),
1619             m.getFamilyCellMap(), m.getTimeStamp(), Action.WRITE)) {
1620             authResult = AuthResult.allow(opType.toString(), "Covering cell set",
1621               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1622           } else {
1623             authResult = AuthResult.deny(opType.toString(), "Covering cell set",
1624               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1625           }
1626           logResult(authResult);
1627           if (authorizationEnabled && !authResult.isAllowed()) {
1628             throw new AccessDeniedException("Insufficient permissions "
1629               + authResult.toContextString());
1630           }
1631         }
1632       }
1633     }
1634   }
1635 
1636   @Override
1637   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1638       final Delete delete, final WALEdit edit, final Durability durability)
1639       throws IOException {
1640     if (aclRegion) {
1641       updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1642     }
1643   }
1644 
1645   @Override
1646   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1647       final byte [] row, final byte [] family, final byte [] qualifier,
1648       final CompareFilter.CompareOp compareOp,
1649       final ByteArrayComparable comparator, final Put put,
1650       final boolean result) throws IOException {
1651     User user = getActiveUser();
1652     checkForReservedTagPresence(user, put);
1653 
1654     // Require READ and WRITE permissions on the table, CF, and KV to update
1655     RegionCoprocessorEnvironment env = c.getEnvironment();
1656     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1657     AuthResult authResult = permissionGranted(OpType.CHECK_AND_PUT, user, env, families,
1658       Action.READ, Action.WRITE);
1659     logResult(authResult);
1660     if (!authResult.isAllowed()) {
1661       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1662         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1663       } else if (authorizationEnabled) {
1664         throw new AccessDeniedException("Insufficient permissions " +
1665           authResult.toContextString());
1666       }
1667     }
1668 
1669     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1670     if (bytes != null) {
1671       if (cellFeaturesEnabled) {
1672         addCellPermissions(bytes, put.getFamilyCellMap());
1673       } else {
1674         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1675       }
1676     }
1677     return result;
1678   }
1679 
1680   @Override
1681   public boolean preCheckAndPutAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1682       final byte[] row, final byte[] family, final byte[] qualifier,
1683       final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put,
1684       final boolean result) throws IOException {
1685     if (put.getAttribute(CHECK_COVERING_PERM) != null) {
1686       // We had failure with table, cf and q perm checks and now giving a chance for cell
1687       // perm check
1688       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1689       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1690       AuthResult authResult = null;
1691       if (checkCoveringPermission(OpType.CHECK_AND_PUT, c.getEnvironment(), row, families,
1692           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1693         authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1694             getActiveUser(), Action.READ, table, families);
1695       } else {
1696         authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1697             getActiveUser(), Action.READ, table, families);
1698       }
1699       logResult(authResult);
1700       if (authorizationEnabled && !authResult.isAllowed()) {
1701         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1702       }
1703     }
1704     return result;
1705   }
1706 
1707   @Override
1708   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1709       final byte [] row, final byte [] family, final byte [] qualifier,
1710       final CompareFilter.CompareOp compareOp,
1711       final ByteArrayComparable comparator, final Delete delete,
1712       final boolean result) throws IOException {
1713     // An ACL on a delete is useless, we shouldn't allow it
1714     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1715       throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " +
1716           delete.toString());
1717     }
1718     // Require READ and WRITE permissions on the table, CF, and the KV covered
1719     // by the delete
1720     RegionCoprocessorEnvironment env = c.getEnvironment();
1721     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1722     User user = getActiveUser();
1723     AuthResult authResult = permissionGranted(OpType.CHECK_AND_DELETE, user, env, families,
1724         Action.READ, Action.WRITE);
1725     logResult(authResult);
1726     if (!authResult.isAllowed()) {
1727       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1728         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1729       } else if (authorizationEnabled) {
1730         throw new AccessDeniedException("Insufficient permissions " +
1731           authResult.toContextString());
1732       }
1733     }
1734     return result;
1735   }
1736 
1737   @Override
1738   public boolean preCheckAndDeleteAfterRowLock(
1739       final ObserverContext<RegionCoprocessorEnvironment> c, final byte[] row, final byte[] family,
1740       final byte[] qualifier, final CompareFilter.CompareOp compareOp,
1741       final ByteArrayComparable comparator, final Delete delete, final boolean result)
1742       throws IOException {
1743     if (delete.getAttribute(CHECK_COVERING_PERM) != null) {
1744       // We had failure with table, cf and q perm checks and now giving a chance for cell
1745       // perm check
1746       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1747       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1748       AuthResult authResult = null;
1749       if (checkCoveringPermission(OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families,
1750           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1751         authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1752             getActiveUser(), Action.READ, table, families);
1753       } else {
1754         authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1755             getActiveUser(), Action.READ, table, families);
1756       }
1757       logResult(authResult);
1758       if (authorizationEnabled && !authResult.isAllowed()) {
1759         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1760       }
1761     }
1762     return result;
1763   }
1764 
1765   @Override
1766   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1767       final byte [] row, final byte [] family, final byte [] qualifier,
1768       final long amount, final boolean writeToWAL)
1769       throws IOException {
1770     // Require WRITE permission to the table, CF, and the KV to be replaced by the
1771     // incremented value
1772     RegionCoprocessorEnvironment env = c.getEnvironment();
1773     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1774     User user = getActiveUser();
1775     AuthResult authResult = permissionGranted(OpType.INCREMENT_COLUMN_VALUE, user, env, families,
1776         Action.WRITE);
1777     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1778       authResult.setAllowed(checkCoveringPermission(OpType.INCREMENT_COLUMN_VALUE, env, row,
1779         families, HConstants.LATEST_TIMESTAMP, Action.WRITE));
1780       authResult.setReason("Covering cell set");
1781     }
1782     logResult(authResult);
1783     if (authorizationEnabled && !authResult.isAllowed()) {
1784       throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1785     }
1786     return -1;
1787   }
1788 
1789   @Override
1790   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1791       throws IOException {
1792     User user = getActiveUser();
1793     checkForReservedTagPresence(user, append);
1794 
1795     // Require WRITE permission to the table, CF, and the KV to be appended
1796     RegionCoprocessorEnvironment env = c.getEnvironment();
1797     Map<byte[],? extends Collection<Cell>> families = append.getFamilyCellMap();
1798     AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE);
1799     logResult(authResult);
1800     if (!authResult.isAllowed()) {
1801       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1802         append.setAttribute(CHECK_COVERING_PERM, TRUE);
1803       } else if (authorizationEnabled)  {
1804         throw new AccessDeniedException("Insufficient permissions " +
1805           authResult.toContextString());
1806       }
1807     }
1808 
1809     byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1810     if (bytes != null) {
1811       if (cellFeaturesEnabled) {
1812         addCellPermissions(bytes, append.getFamilyCellMap());
1813       } else {
1814         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1815       }
1816     }
1817 
1818     return null;
1819   }
1820 
1821   @Override
1822   public Result preAppendAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1823       final Append append) throws IOException {
1824     if (append.getAttribute(CHECK_COVERING_PERM) != null) {
1825       // We had failure with table, cf and q perm checks and now giving a chance for cell
1826       // perm check
1827       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1828       AuthResult authResult = null;
1829       if (checkCoveringPermission(OpType.APPEND, c.getEnvironment(), append.getRow(),
1830           append.getFamilyCellMap(), HConstants.LATEST_TIMESTAMP, Action.WRITE)) {
1831         authResult = AuthResult.allow(OpType.APPEND.toString(), "Covering cell set",
1832             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1833       } else {
1834         authResult = AuthResult.deny(OpType.APPEND.toString(), "Covering cell set",
1835             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1836       }
1837       logResult(authResult);
1838       if (authorizationEnabled && !authResult.isAllowed()) {
1839         throw new AccessDeniedException("Insufficient permissions " +
1840           authResult.toContextString());
1841       }
1842     }
1843     return null;
1844   }
1845 
1846   @Override
1847   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1848       final Increment increment)
1849       throws IOException {
1850     User user = getActiveUser();
1851     checkForReservedTagPresence(user, increment);
1852 
1853     // Require WRITE permission to the table, CF, and the KV to be replaced by
1854     // the incremented value
1855     RegionCoprocessorEnvironment env = c.getEnvironment();
1856     Map<byte[],? extends Collection<Cell>> families = increment.getFamilyCellMap();
1857     AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families,
1858       Action.WRITE);
1859     logResult(authResult);
1860     if (!authResult.isAllowed()) {
1861       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1862         increment.setAttribute(CHECK_COVERING_PERM, TRUE);
1863       } else if (authorizationEnabled) {
1864         throw new AccessDeniedException("Insufficient permissions " +
1865           authResult.toContextString());
1866       }
1867     }
1868 
1869     byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1870     if (bytes != null) {
1871       if (cellFeaturesEnabled) {
1872         addCellPermissions(bytes, increment.getFamilyCellMap());
1873       } else {
1874         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1875       }
1876     }
1877 
1878     return null;
1879   }
1880 
1881   @Override
1882   public Result preIncrementAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1883       final Increment increment) throws IOException {
1884     if (increment.getAttribute(CHECK_COVERING_PERM) != null) {
1885       // We had failure with table, cf and q perm checks and now giving a chance for cell
1886       // perm check
1887       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1888       AuthResult authResult = null;
1889       if (checkCoveringPermission(OpType.INCREMENT, c.getEnvironment(), increment.getRow(),
1890           increment.getFamilyCellMap(), increment.getTimeRange().getMax(), Action.WRITE)) {
1891         authResult = AuthResult.allow(OpType.INCREMENT.toString(), "Covering cell set",
1892             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1893       } else {
1894         authResult = AuthResult.deny(OpType.INCREMENT.toString(), "Covering cell set",
1895             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1896       }
1897       logResult(authResult);
1898       if (authorizationEnabled && !authResult.isAllowed()) {
1899         throw new AccessDeniedException("Insufficient permissions " +
1900           authResult.toContextString());
1901       }
1902     }
1903     return null;
1904   }
1905 
1906   @Override
1907   public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
1908       MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
1909     // If the HFile version is insufficient to persist tags, we won't have any
1910     // work to do here
1911     if (!cellFeaturesEnabled) {
1912       return newCell;
1913     }
1914 
1915     // Collect any ACLs from the old cell
1916     List<Tag> tags = Lists.newArrayList();
1917     ListMultimap<String,Permission> perms = ArrayListMultimap.create();
1918     if (oldCell != null) {
1919       // Save an object allocation where we can
1920       if (oldCell.getTagsLengthUnsigned() > 0) {
1921         Iterator<Tag> tagIterator = CellUtil.tagsIterator(oldCell.getTagsArray(),
1922           oldCell.getTagsOffset(), oldCell.getTagsLengthUnsigned());
1923         while (tagIterator.hasNext()) {
1924           Tag tag = tagIterator.next();
1925           if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) {
1926             // Not an ACL tag, just carry it through
1927             if (LOG.isTraceEnabled()) {
1928               LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() +
1929                 " length " + tag.getTagLength());
1930             }
1931             tags.add(tag);
1932           } else {
1933             // Merge the perms from the older ACL into the current permission set
1934             // TODO: The efficiency of this can be improved. Don't build just to unpack
1935             // again, use the builder
1936             AccessControlProtos.UsersAndPermissions.Builder builder =
1937               AccessControlProtos.UsersAndPermissions.newBuilder();
1938             ProtobufUtil.mergeFrom(builder, tag.getBuffer(), tag.getTagOffset(), tag.getTagLength());
1939             ListMultimap<String,Permission> kvPerms =
1940               ProtobufUtil.toUsersAndPermissions(builder.build());
1941             perms.putAll(kvPerms);
1942           }
1943         }
1944       }
1945     }
1946 
1947     // Do we have an ACL on the operation?
1948     byte[] aclBytes = mutation.getACL();
1949     if (aclBytes != null) {
1950       // Yes, use it
1951       tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes));
1952     } else {
1953       // No, use what we carried forward
1954       if (perms != null) {
1955         // TODO: If we collected ACLs from more than one tag we may have a
1956         // List<Permission> of size > 1, this can be collapsed into a single
1957         // Permission
1958         if (LOG.isTraceEnabled()) {
1959           LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms);
1960         }
1961         tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE,
1962             ProtobufUtil.toUsersAndPermissions(perms).toByteArray()));
1963       }
1964     }
1965 
1966     // If we have no tags to add, just return
1967     if (tags.isEmpty()) {
1968       return newCell;
1969     }
1970 
1971     // We need to create another KV, unfortunately, because the current new KV
1972     // has no space for tags
1973     KeyValue newKv = KeyValueUtil.ensureKeyValue(newCell);
1974     byte[] bytes = newKv.getBuffer();
1975     KeyValue rewriteKv = new KeyValue(bytes, newKv.getRowOffset(), newKv.getRowLength(),
1976       bytes, newKv.getFamilyOffset(), newKv.getFamilyLength(),
1977       bytes, newKv.getQualifierOffset(), newKv.getQualifierLength(),
1978       newKv.getTimestamp(), KeyValue.Type.codeToType(newKv.getTypeByte()),
1979       bytes, newKv.getValueOffset(), newKv.getValueLength(),
1980       tags);
1981     // Preserve mvcc data
1982     rewriteKv.setMvccVersion(newKv.getMvccVersion());
1983     return rewriteKv;
1984   }
1985 
1986   @Override
1987   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1988       final Scan scan, final RegionScanner s) throws IOException {
1989     internalPreRead(c, scan, OpType.SCAN);
1990     return s;
1991   }
1992 
1993   @Override
1994   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1995       final Scan scan, final RegionScanner s) throws IOException {
1996     User user = getActiveUser();
1997     if (user != null && user.getShortName() != null) {
1998       // store reference to scanner owner for later checks
1999       scannerOwners.put(s, user.getShortName());
2000     }
2001     return s;
2002   }
2003 
2004   @Override
2005   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
2006       final InternalScanner s, final List<Result> result,
2007       final int limit, final boolean hasNext) throws IOException {
2008     requireScannerOwner(s);
2009     return hasNext;
2010   }
2011 
2012   @Override
2013   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2014       final InternalScanner s) throws IOException {
2015     requireScannerOwner(s);
2016   }
2017 
2018   @Override
2019   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2020       final InternalScanner s) throws IOException {
2021     // clean up any associated owner mapping
2022     scannerOwners.remove(s);
2023   }
2024 
2025   /**
2026    * Verify, when servicing an RPC, that the caller is the scanner owner.
2027    * If so, we assume that access control is correctly enforced based on
2028    * the checks performed in preScannerOpen()
2029    */
2030   private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
2031     if (!RpcServer.isInRpcCallContext())
2032       return;
2033     String requestUserName = RpcServer.getRequestUserName();
2034     String owner = scannerOwners.get(s);
2035     if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) {
2036       throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
2037     }
2038   }
2039 
2040   /**
2041    * Verifies user has CREATE privileges on
2042    * the Column Families involved in the bulkLoadHFile
2043    * request. Specific Column Write privileges are presently
2044    * ignored.
2045    */
2046   @Override
2047   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
2048       List<Pair<byte[], String>> familyPaths) throws IOException {
2049     for(Pair<byte[],String> el : familyPaths) {
2050       requirePermission("preBulkLoadHFile",
2051           ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
2052           el.getFirst(),
2053           null,
2054           Action.CREATE);
2055     }
2056   }
2057 
2058   /**
2059    * Authorization check for
2060    * SecureBulkLoadProtocol.prepareBulkLoad()
2061    * @param ctx the context
2062    * @param request the request
2063    * @throws IOException
2064    */
2065   @Override
2066   public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2067                                  PrepareBulkLoadRequest request) throws IOException {
2068     requireAccess("prePareBulkLoad",
2069         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2070   }
2071 
2072   /**
2073    * Authorization security check for
2074    * SecureBulkLoadProtocol.cleanupBulkLoad()
2075    * @param ctx the context
2076    * @param request the request
2077    * @throws IOException
2078    */
2079   @Override
2080   public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2081                                  CleanupBulkLoadRequest request) throws IOException {
2082     requireAccess("preCleanupBulkLoad",
2083         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2084   }
2085 
2086   /* ---- EndpointObserver implementation ---- */
2087 
2088   @Override
2089   public Message preEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2090       Service service, String methodName, Message request) throws IOException {
2091     // Don't intercept calls to our own AccessControlService, we check for
2092     // appropriate permissions in the service handlers
2093     if (shouldCheckExecPermission && !(service instanceof AccessControlService)) {
2094       requirePermission("invoke(" + service.getDescriptorForType().getName() + "." +
2095         methodName + ")",
2096         getTableName(ctx.getEnvironment()), null, null,
2097         Action.EXEC);
2098     }
2099     return request;
2100   }
2101 
2102   @Override
2103   public void postEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2104       Service service, String methodName, Message request, Message.Builder responseBuilder)
2105       throws IOException { }
2106 
2107   /* ---- Protobuf AccessControlService implementation ---- */
2108 
2109   @Override
2110   public void grant(RpcController controller,
2111                     AccessControlProtos.GrantRequest request,
2112                     RpcCallback<AccessControlProtos.GrantResponse> done) {
2113     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2114     AccessControlProtos.GrantResponse response = null;
2115     try {
2116       // verify it's only running at .acl.
2117       if (aclRegion) {
2118         if (!initialized) {
2119           throw new CoprocessorException("AccessController not yet initialized");
2120         }
2121         if (LOG.isDebugEnabled()) {
2122           LOG.debug("Received request to grant access permission " + perm.toString());
2123         }
2124 
2125         switch(request.getUserPermission().getPermission().getType()) {
2126           case Global :
2127           case Table :
2128             requirePermission("grant", perm.getTableName(), perm.getFamily(),
2129               perm.getQualifier(), Action.ADMIN);
2130             break;
2131           case Namespace :
2132             requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace());
2133            break;
2134         }
2135 
2136         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2137           @Override
2138           public Void run() throws Exception {
2139             AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
2140             return null;
2141           }
2142         });
2143 
2144         if (AUDITLOG.isTraceEnabled()) {
2145           // audit log should store permission changes in addition to auth results
2146           AUDITLOG.trace("Granted permission " + perm.toString());
2147         }
2148       } else {
2149         throw new CoprocessorException(AccessController.class, "This method "
2150             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2151       }
2152       response = AccessControlProtos.GrantResponse.getDefaultInstance();
2153     } catch (IOException ioe) {
2154       // pass exception back up
2155       ResponseConverter.setControllerException(controller, ioe);
2156     }
2157     done.run(response);
2158   }
2159 
2160   @Override
2161   public void revoke(RpcController controller,
2162                      AccessControlProtos.RevokeRequest request,
2163                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
2164     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2165     AccessControlProtos.RevokeResponse response = null;
2166     try {
2167       // only allowed to be called on _acl_ region
2168       if (aclRegion) {
2169         if (!initialized) {
2170           throw new CoprocessorException("AccessController not yet initialized");
2171         }
2172         if (LOG.isDebugEnabled()) {
2173           LOG.debug("Received request to revoke access permission " + perm.toString());
2174         }
2175 
2176         switch(request.getUserPermission().getPermission().getType()) {
2177           case Global :
2178           case Table :
2179             requirePermission("revoke", perm.getTableName(), perm.getFamily(),
2180               perm.getQualifier(), Action.ADMIN);
2181             break;
2182           case Namespace :
2183             requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace());
2184             break;
2185         }
2186 
2187         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2188           @Override
2189           public Void run() throws Exception {
2190             AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
2191             return null;
2192           }
2193         });
2194 
2195         if (AUDITLOG.isTraceEnabled()) {
2196           // audit log should record all permission changes
2197           AUDITLOG.trace("Revoked permission " + perm.toString());
2198         }
2199       } else {
2200         throw new CoprocessorException(AccessController.class, "This method "
2201             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2202       }
2203       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
2204     } catch (IOException ioe) {
2205       // pass exception back up
2206       ResponseConverter.setControllerException(controller, ioe);
2207     }
2208     done.run(response);
2209   }
2210 
2211   @Override
2212   public void getUserPermissions(RpcController controller,
2213                                  AccessControlProtos.GetUserPermissionsRequest request,
2214                                  RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
2215     AccessControlProtos.GetUserPermissionsResponse response = null;
2216     try {
2217       // only allowed to be called on _acl_ region
2218       if (aclRegion) {
2219         if (!initialized) {
2220           throw new CoprocessorException("AccessController not yet initialized");
2221         }
2222         List<UserPermission> perms = null;
2223         if (request.getType() == AccessControlProtos.Permission.Type.Table) {
2224           final TableName table = request.hasTableName() ?
2225             ProtobufUtil.toTableName(request.getTableName()) : null;
2226           requirePermission("userPermissions", table, null, null, Action.ADMIN);
2227           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2228             @Override
2229             public List<UserPermission> run() throws Exception {
2230               return AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), table);
2231             }
2232           });
2233         } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
2234           final String namespace = request.getNamespaceName().toStringUtf8();
2235           requireGlobalPermission("userPermissions", Action.ADMIN, namespace);
2236           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2237             @Override
2238             public List<UserPermission> run() throws Exception {
2239               return AccessControlLists.getUserNamespacePermissions(regionEnv.getConfiguration(),
2240                 namespace);
2241             }
2242           });
2243         } else {
2244           requirePermission("userPermissions", Action.ADMIN);
2245           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2246             @Override
2247             public List<UserPermission> run() throws Exception {
2248               return AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), null);
2249             }
2250           });
2251           // Adding superusers explicitly to the result set as AccessControlLists do not store them.
2252           // Also using acl as table name to be inline  with the results of global admin and will
2253           // help in avoiding any leakage of information about being superusers.
2254           for (String user: Superusers.getSuperUsers()) {
2255             perms.add(new UserPermission(user.getBytes(), AccessControlLists.ACL_TABLE_NAME, null,
2256                 Action.values()));
2257           }
2258         }
2259         response = ResponseConverter.buildGetUserPermissionsResponse(perms);
2260       } else {
2261         throw new CoprocessorException(AccessController.class, "This method "
2262             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2263       }
2264     } catch (IOException ioe) {
2265       // pass exception back up
2266       ResponseConverter.setControllerException(controller, ioe);
2267     }
2268     done.run(response);
2269   }
2270 
2271   @Override
2272   public void checkPermissions(RpcController controller,
2273                                AccessControlProtos.CheckPermissionsRequest request,
2274                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
2275     Permission[] permissions = new Permission[request.getPermissionCount()];
2276     for (int i=0; i < request.getPermissionCount(); i++) {
2277       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
2278     }
2279     AccessControlProtos.CheckPermissionsResponse response = null;
2280     try {
2281       User user = getActiveUser();
2282       TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
2283       for (Permission permission : permissions) {
2284         if (permission instanceof TablePermission) {
2285           // Check table permissions
2286 
2287           TablePermission tperm = (TablePermission) permission;
2288           for (Action action : permission.getActions()) {
2289             if (!tperm.getTableName().equals(tableName)) {
2290               throw new CoprocessorException(AccessController.class, String.format("This method "
2291                   + "can only execute at the table specified in TablePermission. " +
2292                   "Table of the region:%s , requested table:%s", tableName,
2293                   tperm.getTableName()));
2294             }
2295 
2296             Map<byte[], Set<byte[]>> familyMap =
2297                 new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
2298             if (tperm.getFamily() != null) {
2299               if (tperm.getQualifier() != null) {
2300                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
2301                 qualifiers.add(tperm.getQualifier());
2302                 familyMap.put(tperm.getFamily(), qualifiers);
2303               } else {
2304                 familyMap.put(tperm.getFamily(), null);
2305               }
2306             }
2307 
2308             AuthResult result = permissionGranted("checkPermissions", user, action, regionEnv,
2309               familyMap);
2310             logResult(result);
2311             if (!result.isAllowed()) {
2312               // Even if passive we need to throw an exception here, we support checking
2313               // effective permissions, so throw unconditionally
2314               throw new AccessDeniedException("Insufficient permissions (table=" + tableName +
2315                 (familyMap.size() > 0 ? ", family: " + result.toFamilyString() : "") +
2316                 ", action=" + action.toString() + ")");
2317             }
2318           }
2319 
2320         } else {
2321           // Check global permissions
2322 
2323           for (Action action : permission.getActions()) {
2324             AuthResult result;
2325             if (authManager.authorize(user, action)) {
2326               result = AuthResult.allow("checkPermissions", "Global action allowed", user,
2327                 action, null, null);
2328             } else {
2329               result = AuthResult.deny("checkPermissions", "Global action denied", user, action,
2330                 null, null);
2331             }
2332             logResult(result);
2333             if (!result.isAllowed()) {
2334               // Even if passive we need to throw an exception here, we support checking
2335               // effective permissions, so throw unconditionally
2336               throw new AccessDeniedException("Insufficient permissions (action=" +
2337                 action.toString() + ")");
2338             }
2339           }
2340         }
2341       }
2342       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
2343     } catch (IOException ioe) {
2344       ResponseConverter.setControllerException(controller, ioe);
2345     }
2346     done.run(response);
2347   }
2348 
2349   @Override
2350   public Service getService() {
2351     return AccessControlProtos.AccessControlService.newReflectiveService(this);
2352   }
2353 
2354   private HRegion getRegion(RegionCoprocessorEnvironment e) {
2355     return e.getRegion();
2356   }
2357 
2358   private TableName getTableName(RegionCoprocessorEnvironment e) {
2359     HRegion region = e.getRegion();
2360     if (region != null) {
2361       return getTableName(region);
2362     }
2363     return null;
2364   }
2365 
2366   private TableName getTableName(HRegion region) {
2367     HRegionInfo regionInfo = region.getRegionInfo();
2368     if (regionInfo != null) {
2369       return regionInfo.getTable();
2370     }
2371     return null;
2372   }
2373 
2374   @Override
2375   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
2376       throws IOException {
2377     requirePermission("preClose", Action.ADMIN);
2378   }
2379 
2380   private void checkSystemOrSuperUser() throws IOException {
2381     // No need to check if we're not going to throw
2382     if (!authorizationEnabled) {
2383       return;
2384     }
2385     User activeUser = getActiveUser();
2386     if (!Superusers.isSuperUser(activeUser)) {
2387       throw new AccessDeniedException("User '" + (activeUser != null ?
2388         activeUser.getShortName() : "null") + "is not system or super user.");
2389     }
2390   }
2391 
2392   @Override
2393   public void preStopRegionServer(
2394       ObserverContext<RegionServerCoprocessorEnvironment> env)
2395       throws IOException {
2396     requirePermission("preStopRegionServer", Action.ADMIN);
2397   }
2398 
2399   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
2400       byte[] qualifier) {
2401     if (family == null) {
2402       return null;
2403     }
2404 
2405     Map<byte[], Collection<byte[]>> familyMap = new TreeMap<byte[], Collection<byte[]>>(Bytes.BYTES_COMPARATOR);
2406     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
2407     return familyMap;
2408   }
2409 
2410   @Override
2411   public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2412       List<TableName> tableNamesList,
2413       List<HTableDescriptor> descriptors) throws IOException {
2414     // If the list is empty, this is a request for all table descriptors and requires GLOBAL
2415     // ADMIN privs.
2416     if (tableNamesList == null || tableNamesList.isEmpty()) {
2417       requireGlobalPermission("getTableDescriptors", Action.ADMIN, null, null);
2418     }
2419     // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
2420     // request can be granted.
2421     else {
2422       MasterServices masterServices = ctx.getEnvironment().getMasterServices();
2423       for (TableName tableName: tableNamesList) {
2424         // Skip checks for a table that does not exist
2425         if (masterServices.getTableDescriptors().get(tableName) == null) {
2426           continue;
2427         }
2428         requirePermission("getTableDescriptors", tableName, null, null,
2429           Action.ADMIN, Action.CREATE);
2430       }
2431     }
2432   }
2433 
2434   @Override
2435   public void preMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx, HRegion regionA,
2436       HRegion regionB) throws IOException {
2437     requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null,
2438       Action.ADMIN);
2439   }
2440 
2441   @Override
2442   public void postMerge(ObserverContext<RegionServerCoprocessorEnvironment> c, HRegion regionA,
2443       HRegion regionB, HRegion mergedRegion) throws IOException { }
2444 
2445   @Override
2446   public void preMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2447       HRegion regionA, HRegion regionB, List<Mutation> metaEntries) throws IOException { }
2448 
2449   @Override
2450   public void postMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2451       HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { }
2452 
2453   @Override
2454   public void preRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2455       HRegion regionA, HRegion regionB) throws IOException { }
2456 
2457   @Override
2458   public void postRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2459       HRegion regionA, HRegion regionB) throws IOException { }
2460 
2461   @Override
2462   public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2463       throws IOException {
2464     requirePermission("preRollLogWriterRequest", Permission.Action.ADMIN);
2465   }
2466 
2467   @Override
2468   public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2469       throws IOException { }
2470 
2471   @Override
2472   public ReplicationEndpoint postCreateReplicationEndPoint(
2473       ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
2474     return endpoint;
2475   }
2476 
2477   @Override
2478   public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2479       List<WALEntry> entries, CellScanner cells) throws IOException {
2480     requirePermission("replicateLogEntries", Action.WRITE);
2481   }
2482 
2483   @Override
2484   public void postReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2485       List<WALEntry> entries, CellScanner cells) throws IOException {
2486   }
2487 }