View Javadoc

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