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