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