View Javadoc

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