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