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.List;
22  import java.util.LinkedList;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.TreeSet;
26  
27  import com.google.protobuf.RpcCallback;
28  import com.google.protobuf.RpcController;
29  import com.google.protobuf.Service;
30  
31  import org.apache.commons.lang.ArrayUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.Cell;
36  import org.apache.hadoop.hbase.CoprocessorEnvironment;
37  import org.apache.hadoop.hbase.TableName;
38  import org.apache.hadoop.hbase.HColumnDescriptor;
39  import org.apache.hadoop.hbase.HRegionInfo;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.KeyValue;
42  import org.apache.hadoop.hbase.KeyValueUtil;
43  import org.apache.hadoop.hbase.NamespaceDescriptor;
44  import org.apache.hadoop.hbase.ServerName;
45  import org.apache.hadoop.hbase.TableNotDisabledException;
46  import org.apache.hadoop.hbase.TableNotFoundException;
47  import org.apache.hadoop.hbase.client.Append;
48  import org.apache.hadoop.hbase.client.Delete;
49  import org.apache.hadoop.hbase.client.Get;
50  import org.apache.hadoop.hbase.client.Increment;
51  import org.apache.hadoop.hbase.client.Put;
52  import org.apache.hadoop.hbase.client.Result;
53  import org.apache.hadoop.hbase.client.Scan;
54  import org.apache.hadoop.hbase.client.Durability;
55  import org.apache.hadoop.hbase.coprocessor.*;
56  import org.apache.hadoop.hbase.filter.CompareFilter;
57  import org.apache.hadoop.hbase.filter.FilterList;
58  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
59  import org.apache.hadoop.hbase.ipc.RequestContext;
60  import org.apache.hadoop.hbase.master.MasterServices;
61  import org.apache.hadoop.hbase.master.RegionPlan;
62  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
63  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
64  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
65  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
66  import org.apache.hadoop.hbase.regionserver.HRegion;
67  import org.apache.hadoop.hbase.regionserver.InternalScanner;
68  import org.apache.hadoop.hbase.regionserver.RegionScanner;
69  import org.apache.hadoop.hbase.regionserver.Store;
70  import org.apache.hadoop.hbase.regionserver.ScanType;
71  import org.apache.hadoop.hbase.regionserver.StoreFile;
72  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
73  import org.apache.hadoop.hbase.security.AccessDeniedException;
74  import org.apache.hadoop.hbase.security.User;
75  import org.apache.hadoop.hbase.security.UserProvider;
76  import org.apache.hadoop.hbase.security.access.Permission.Action;
77  import org.apache.hadoop.hbase.util.Bytes;
78  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
79  import org.apache.hadoop.hbase.util.Pair;
80  
81  import com.google.common.collect.ImmutableSet;
82  import com.google.common.collect.ListMultimap;
83  import com.google.common.collect.Lists;
84  import com.google.common.collect.MapMaker;
85  import com.google.common.collect.Maps;
86  import com.google.common.collect.Sets;
87  
88  import static org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
89  
90  /**
91   * Provides basic authorization checks for data access and administrative
92   * operations.
93   *
94   * <p>
95   * {@code AccessController} performs authorization checks for HBase operations
96   * based on:
97   * <ul>
98   *   <li>the identity of the user performing the operation</li>
99   *   <li>the scope over which the operation is performed, in increasing
100  *   specificity: global, table, column family, or qualifier</li>
101  *   <li>the type of action being performed (as mapped to
102  *   {@link Permission.Action} values)</li>
103  * </ul>
104  * If the authorization check fails, an {@link AccessDeniedException}
105  * will be thrown for the operation.
106  * </p>
107  *
108  * <p>
109  * To perform authorization checks, {@code AccessController} relies on the
110  * RpcServer being loaded to provide
111  * the user identities for remote requests.
112  * </p>
113  *
114  * <p>
115  * The access control lists used for authorization can be manipulated via the
116  * exposed {@link AccessControlService} Interface implementation, and the associated
117  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
118  * commands.
119  * </p>
120  */
121 public class AccessController extends BaseRegionObserver
122     implements MasterObserver, RegionServerObserver,
123       AccessControlService.Interface, CoprocessorService {
124 
125   public static final Log LOG = LogFactory.getLog(AccessController.class);
126 
127   private static final Log AUDITLOG =
128     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
129 
130   TableAuthManager authManager = null;
131 
132   // flags if we are running on a region of the _acl_ table
133   boolean aclRegion = false;
134 
135   // defined only for Endpoint implementation, so it can have way to
136   // access region services.
137   private RegionCoprocessorEnvironment regionEnv;
138 
139   /** Mapping of scanner instances to the user who created them */
140   private Map<InternalScanner,String> scannerOwners =
141       new MapMaker().weakKeys().makeMap();
142 
143   private UserProvider userProvider;
144 
145   void initialize(RegionCoprocessorEnvironment e) throws IOException {
146     final HRegion region = e.getRegion();
147     Map<byte[], ListMultimap<String,TablePermission>> tables =
148         AccessControlLists.loadAll(region);
149     // For each table, write out the table's permissions to the respective
150     // znode for that table.
151     for (Map.Entry<byte[], ListMultimap<String,TablePermission>> t:
152       tables.entrySet()) {
153       byte[] entry = t.getKey();
154       ListMultimap<String,TablePermission> perms = t.getValue();
155       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, e.getConfiguration());
156       this.authManager.getZKPermissionWatcher().writeToZookeeper(entry, serialized);
157     }
158   }
159 
160   /**
161    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
162    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
163    * table updates.
164    */
165   void updateACL(RegionCoprocessorEnvironment e,
166       final Map<byte[], List<Cell>> familyMap) {
167     Set<byte[]> entries =
168         new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
169     for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
170       List<Cell> cells = f.getValue();
171       for (Cell cell: cells) {
172         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
173         if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
174             kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
175             AccessControlLists.ACL_LIST_FAMILY.length)) {
176           entries.add(kv.getRow());
177         }
178       }
179     }
180     ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
181     Configuration conf = regionEnv.getConfiguration();
182     for (byte[] entry: entries) {
183       try {
184         ListMultimap<String,TablePermission> perms =
185           AccessControlLists.getPermissions(conf, entry);
186         byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
187         zkw.writeToZookeeper(entry, serialized);
188       } catch (IOException ex) {
189         LOG.error("Failed updating permissions mirror for '" + Bytes.toString(entry) + "'",
190             ex);
191       }
192     }
193   }
194 
195   /**
196    * Check the current user for authorization to perform a specific action
197    * against the given set of row data.
198    *
199    * <p>Note: Ordering of the authorization checks
200    * has been carefully optimized to short-circuit the most common requests
201    * and minimize the amount of processing required.</p>
202    *
203    * @param permRequest the action being requested
204    * @param e the coprocessor environment
205    * @param families the map of column families to qualifiers present in
206    * the request
207    * @return an authorization result
208    */
209   AuthResult permissionGranted(String request, User user, Permission.Action permRequest,
210       RegionCoprocessorEnvironment e,
211       Map<byte [], ? extends Collection<?>> families) {
212     HRegionInfo hri = e.getRegion().getRegionInfo();
213     TableName tableName = hri.getTable();
214 
215     // 1. All users need read access to hbase:meta table.
216     // this is a very common operation, so deal with it quickly.
217     if (hri.isMetaRegion()) {
218       if (permRequest == Permission.Action.READ) {
219         return AuthResult.allow(request, "All users allowed", user,
220           permRequest, tableName, families);
221       }
222     }
223 
224     if (user == null) {
225       return AuthResult.deny(request, "No user associated with request!", null,
226         permRequest, tableName, families);
227     }
228 
229     // Users with CREATE/ADMIN rights need to modify hbase:meta and _acl_ table
230     // e.g. When a new table is created a new entry in hbase:meta is added,
231     // so the user need to be allowed to write on it.
232     // e.g. When a table is removed an entry is removed from hbase:meta and _acl_
233     // and the user need to be allowed to write on both tables.
234     if (permRequest == Permission.Action.WRITE &&
235        (hri.isMetaRegion() ||
236         Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) &&
237        (authManager.authorize(user, Permission.Action.CREATE) ||
238         authManager.authorize(user, Permission.Action.ADMIN)))
239     {
240        return AuthResult.allow(request, "Table permission granted", user,
241         permRequest, tableName, families);
242     }
243 
244     // 2. check for the table-level, if successful we can short-circuit
245     if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
246       return AuthResult.allow(request, "Table permission granted", user,
247         permRequest, tableName, families);
248     }
249 
250     // 3. check permissions against the requested families
251     if (families != null && families.size() > 0) {
252       // all families must pass
253       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
254         // a) check for family level access
255         if (authManager.authorize(user, tableName, family.getKey(),
256             permRequest)) {
257           continue;  // family-level permission overrides per-qualifier
258         }
259 
260         // b) qualifier level access can still succeed
261         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
262           if (family.getValue() instanceof Set) {
263             // for each qualifier of the family
264             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
265             for (byte[] qualifier : familySet) {
266               if (!authManager.authorize(user, tableName, family.getKey(),
267                                          qualifier, permRequest)) {
268                 return AuthResult.deny(request, "Failed qualifier check", user,
269                     permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
270               }
271             }
272           } else if (family.getValue() instanceof List) { // List<KeyValue>
273             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
274             for (KeyValue kv : kvList) {
275               if (!authManager.authorize(user, tableName, family.getKey(),
276                       kv.getQualifier(), permRequest)) {
277                 return AuthResult.deny(request, "Failed qualifier check", user,
278                     permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
279               }
280             }
281           }
282         } else {
283           // no qualifiers and family-level check already failed
284           return AuthResult.deny(request, "Failed family check", user, permRequest,
285               tableName, makeFamilyMap(family.getKey(), null));
286         }
287       }
288 
289       // all family checks passed
290       return AuthResult.allow(request, "All family checks passed", user, permRequest,
291           tableName, families);
292     }
293 
294     // 4. no families to check and table level access failed
295     return AuthResult.deny(request, "No families to check and table permission failed",
296         user, permRequest, tableName, families);
297   }
298 
299   private void logResult(AuthResult result) {
300     if (AUDITLOG.isTraceEnabled()) {
301       RequestContext ctx = RequestContext.get();
302       InetAddress remoteAddr = null;
303       if (ctx != null) {
304         remoteAddr = ctx.getRemoteAddress();
305       }
306       AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
307           " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
308           "; reason: " + result.getReason() +
309           "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
310           "; request: " + result.getRequest() +
311           "; context: " + result.toContextString());
312     }
313   }
314 
315   /**
316    * Returns the active user to which authorization checks should be applied.
317    * If we are in the context of an RPC call, the remote user is used,
318    * otherwise the currently logged in user is used.
319    */
320   private User getActiveUser() throws IOException {
321     User user = RequestContext.getRequestUser();
322     if (!RequestContext.isInRequestContext()) {
323       // for non-rpc handling, fallback to system user
324       user = userProvider.getCurrent();
325     }
326     return user;
327   }
328 
329   /**
330    * Authorizes that the current user has any of the given permissions for the
331    * given table, column family and column qualifier.
332    * @param tableName Table requested
333    * @param family Column family requested
334    * @param qualifier Column qualifier requested
335    * @throws IOException if obtaining the current user fails
336    * @throws AccessDeniedException if user has no authorization
337    */
338   private void requirePermission(String request, TableName tableName, byte[] family, byte[] qualifier,
339       Action... permissions) throws IOException {
340     User user = getActiveUser();
341     AuthResult result = null;
342 
343     for (Action permission : permissions) {
344       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
345         result = AuthResult.allow(request, "Table permission granted", user,
346                                   permission, tableName, family, qualifier);
347         break;
348       } else {
349         // rest of the world
350         result = AuthResult.deny(request, "Insufficient permissions", user,
351                                  permission, tableName, family, qualifier);
352       }
353     }
354     logResult(result);
355     if (!result.isAllowed()) {
356       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
357     }
358   }
359 
360   /**
361    * Authorizes that the current user has any of the given permissions for the
362    * given table, column family and column qualifier.
363    * @param namespace
364    * @throws IOException if obtaining the current user fails
365    * @throws AccessDeniedException if user has no authorization
366    */
367   private void requirePermission(String request, String namespace,
368       Action... permissions) throws IOException {
369     User user = getActiveUser();
370     AuthResult result = null;
371 
372     for (Action permission : permissions) {
373       if (authManager.authorize(user, namespace, permission)) {
374         result = AuthResult.allow(request, "Table permission granted", user,
375                                   permission, namespace);
376         break;
377       } else {
378         // rest of the world
379         result = AuthResult.deny(request, "Insufficient permissions", user,
380                                  permission, namespace);
381       }
382     }
383     logResult(result);
384     if (!result.isAllowed()) {
385       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
386     }
387   }
388 
389   /**
390    * Authorizes that the current user has global privileges for the given action.
391    * @param perm The action being requested
392    * @throws IOException if obtaining the current user fails
393    * @throws AccessDeniedException if authorization is denied
394    */
395   private void requirePermission(String request, Permission.Action perm) throws IOException {
396     requireGlobalPermission(request, perm, null, null);
397   }
398 
399   /**
400    * Authorizes that the current user has permission to perform the given
401    * action on the set of table column families.
402    * @param perm Action that is required
403    * @param env The current coprocessor environment
404    * @param families The map of column families-qualifiers.
405    * @throws AccessDeniedException if the authorization check failed
406    */
407   private void requirePermission(String request, Permission.Action perm,
408         RegionCoprocessorEnvironment env,
409         Map<byte[], ? extends Collection<?>> families)
410       throws IOException {
411     User user = getActiveUser();
412     AuthResult result = permissionGranted(request, user, perm, env, families);
413     logResult(result);
414 
415     if (!result.isAllowed()) {
416       throw new AccessDeniedException("Insufficient permissions (table=" +
417         env.getRegion().getTableDesc().getTableName()+
418         ((families != null && families.size() > 0) ? ", family: " +
419         result.toFamilyString() : "") + ", action=" +
420         perm.toString() + ")");
421     }
422   }
423 
424   /**
425    * Checks that the user has the given global permission. The generated
426    * audit log message will contain context information for the operation
427    * being authorized, based on the given parameters.
428    * @param perm Action being requested
429    * @param tableName Affected table name.
430    * @param familyMap Affected column families.
431    */
432   private void requireGlobalPermission(String request, Permission.Action perm, TableName tableName,
433       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
434     User user = getActiveUser();
435     if (authManager.authorize(user, perm)) {
436       logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap));
437     } else {
438       logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap));
439       throw new AccessDeniedException("Insufficient permissions for user '" +
440           (user != null ? user.getShortName() : "null") +"' (global, action=" +
441           perm.toString() + ")");
442     }
443   }
444 
445   /**
446    * Checks that the user has the given global permission. The generated
447    * audit log message will contain context information for the operation
448    * being authorized, based on the given parameters.
449    * @param perm Action being requested
450    * @param namespace
451    */
452   private void requireGlobalPermission(String request, Permission.Action perm,
453                                        String namespace) throws IOException {
454     User user = getActiveUser();
455     if (authManager.authorize(user, perm)) {
456       logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace));
457     } else {
458       logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace));
459       throw new AccessDeniedException("Insufficient permissions for user '" +
460           (user != null ? user.getShortName() : "null") +"' (global, action=" +
461           perm.toString() + ")");
462     }
463   }
464 
465   /**
466    * Returns <code>true</code> if the current user is allowed the given action
467    * over at least one of the column qualifiers in the given column families.
468    */
469   private boolean hasFamilyQualifierPermission(User user,
470       Permission.Action perm,
471       RegionCoprocessorEnvironment env,
472       Map<byte[], ? extends Set<byte[]>> familyMap)
473     throws IOException {
474     HRegionInfo hri = env.getRegion().getRegionInfo();
475     TableName tableName = hri.getTable();
476 
477     if (user == null) {
478       return false;
479     }
480 
481     if (familyMap != null && familyMap.size() > 0) {
482       // at least one family must be allowed
483       for (Map.Entry<byte[], ? extends Set<byte[]>> family :
484           familyMap.entrySet()) {
485         if (family.getValue() != null && !family.getValue().isEmpty()) {
486           for (byte[] qualifier : family.getValue()) {
487             if (authManager.matchPermission(user, tableName,
488                 family.getKey(), qualifier, perm)) {
489               return true;
490             }
491           }
492         } else {
493           if (authManager.matchPermission(user, tableName, family.getKey(),
494               perm)) {
495             return true;
496           }
497         }
498       }
499     } else if (LOG.isDebugEnabled()) {
500       LOG.debug("Empty family map passed for permission check");
501     }
502 
503     return false;
504   }
505 
506   /* ---- MasterObserver implementation ---- */
507   public void start(CoprocessorEnvironment env) throws IOException {
508 
509     ZooKeeperWatcher zk = null;
510     if (env instanceof MasterCoprocessorEnvironment) {
511       // if running on HMaster
512       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
513       zk = mEnv.getMasterServices().getZooKeeper();
514     } else if (env instanceof RegionServerCoprocessorEnvironment) {
515       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
516       zk = rsEnv.getRegionServerServices().getZooKeeper();
517     } else if (env instanceof RegionCoprocessorEnvironment) {
518       // if running at region
519       regionEnv = (RegionCoprocessorEnvironment) env;
520       zk = regionEnv.getRegionServerServices().getZooKeeper();
521     }
522 
523     //set the user provider
524     this.userProvider = UserProvider.instantiate(env.getConfiguration());
525 
526     // If zk is null or IOException while obtaining auth manager,
527     // throw RuntimeException so that the coprocessor is unloaded.
528     if (zk != null) {
529       try {
530         this.authManager = TableAuthManager.get(zk, env.getConfiguration());
531       } catch (IOException ioe) {
532         throw new RuntimeException("Error obtaining TableAuthManager", ioe);
533       }
534     } else {
535       throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
536     }
537   }
538 
539   public void stop(CoprocessorEnvironment env) {
540 
541   }
542 
543   @Override
544   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
545       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
546     Set<byte[]> families = desc.getFamiliesKeys();
547     Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
548     for (byte[] family: families) {
549       familyMap.put(family, null);
550     }
551     requireGlobalPermission("createTable", Permission.Action.CREATE, desc.getTableName(), familyMap);
552   }
553 
554   @Override
555   public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
556       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
557 
558   @Override
559   public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
560       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
561     if (!AccessControlLists.isAclTable(desc)) {
562       String owner = desc.getOwnerString();
563       // default the table owner to current user, if not specified.
564       if (owner == null) owner = getActiveUser().getShortName();
565       UserPermission userperm = new UserPermission(Bytes.toBytes(owner), desc.getTableName(), null,
566           Action.values());
567       AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
568     }
569   }
570 
571   @Override
572   public void postCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
573       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
574 
575   @Override
576   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
577       throws IOException {
578     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
579   }
580 
581   @Override
582   public void preDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
583       TableName tableName) throws IOException {}
584   @Override
585   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
586       TableName tableName) throws IOException {
587     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName);
588   }
589   @Override
590   public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
591       TableName tableName) throws IOException {}
592 
593   @Override
594   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
595       HTableDescriptor htd) throws IOException {
596     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
597   }
598 
599   @Override
600   public void preModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
601       TableName tableName, HTableDescriptor htd) throws IOException {}
602 
603   @Override
604   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
605       TableName tableName, HTableDescriptor htd) throws IOException {
606     String owner = htd.getOwnerString();
607     // default the table owner to current user, if not specified.
608     if (owner == null) owner = getActiveUser().getShortName();
609     UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getTableName(), null,
610         Action.values());
611     AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
612   }
613 
614   @Override
615   public void postModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
616       TableName tableName, HTableDescriptor htd) throws IOException {}
617 
618 
619   @Override
620   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
621       HColumnDescriptor column) throws IOException {
622     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
623   }
624 
625   @Override
626   public void preAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
627       TableName tableName, HColumnDescriptor column) throws IOException {}
628   @Override
629   public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> c,
630       TableName tableName, HColumnDescriptor column) throws IOException {}
631   @Override
632   public void postAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
633       TableName tableName, HColumnDescriptor column) throws IOException {}
634 
635   @Override
636   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
637       HColumnDescriptor descriptor) throws IOException {
638     requirePermission("modifyColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
639   }
640 
641   @Override
642   public void preModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
643       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
644   @Override
645   public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c,
646       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
647   @Override
648   public void postModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
649       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
650 
651 
652   @Override
653   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
654       byte[] col) throws IOException {
655     requirePermission("deleteColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
656   }
657 
658   @Override
659   public void preDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
660       TableName tableName, byte[] col) throws IOException {}
661   @Override
662   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
663       TableName tableName, byte[] col) throws IOException {
664     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(),
665                                               tableName, col);
666   }
667   @Override
668   public void postDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
669       TableName tableName, byte[] col) throws IOException {}
670 
671   @Override
672   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
673       throws IOException {
674     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
675   }
676 
677   @Override
678   public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
679       TableName tableName) throws IOException {}
680   @Override
681   public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> c,
682       TableName tableName) throws IOException {}
683   @Override
684   public void postEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
685       TableName tableName) throws IOException {}
686 
687   @Override
688   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
689       throws IOException {
690     if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
691       throw new AccessDeniedException("Not allowed to disable "
692           + AccessControlLists.ACL_TABLE_NAME + " table.");
693     }
694     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
695   }
696 
697   @Override
698   public void preDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
699       TableName tableName) throws IOException {}
700   @Override
701   public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> c,
702       TableName tableName) throws IOException {}
703   @Override
704   public void postDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
705       TableName tableName) throws IOException {}
706 
707   @Override
708   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
709       ServerName srcServer, ServerName destServer) throws IOException {
710     requirePermission("move", region.getTable(), null, null, Action.ADMIN);
711   }
712 
713   @Override
714   public void postMove(ObserverContext<MasterCoprocessorEnvironment> c,
715       HRegionInfo region, ServerName srcServer, ServerName destServer)
716     throws IOException {}
717 
718   @Override
719   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
720       throws IOException {
721     requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
722   }
723 
724   @Override
725   public void postAssign(ObserverContext<MasterCoprocessorEnvironment> c,
726       HRegionInfo regionInfo) throws IOException {}
727 
728   @Override
729   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
730       boolean force) throws IOException {
731     requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
732   }
733 
734   @Override
735   public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> c,
736       HRegionInfo regionInfo, boolean force) throws IOException {}
737 
738   @Override
739   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
740       HRegionInfo regionInfo) throws IOException {
741     requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
742   }
743 
744   @Override
745   public void postRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
746       HRegionInfo regionInfo) throws IOException {
747   }
748 
749   @Override
750   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
751       throws IOException {
752     requirePermission("balance", Permission.Action.ADMIN);
753   }
754   @Override
755   public void postBalance(ObserverContext<MasterCoprocessorEnvironment> c, List<RegionPlan> plans)
756       throws IOException {}
757 
758   @Override
759   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
760       boolean newValue) throws IOException {
761     requirePermission("balanceSwitch", Permission.Action.ADMIN);
762     return newValue;
763   }
764   @Override
765   public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
766       boolean oldValue, boolean newValue) throws IOException {}
767 
768   @Override
769   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
770       throws IOException {
771     requirePermission("shutdown", Permission.Action.ADMIN);
772   }
773 
774   @Override
775   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
776       throws IOException {
777     requirePermission("stopMaster", Permission.Action.ADMIN);
778   }
779 
780   @Override
781   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
782       throws IOException {
783     // initialize the ACL storage table
784     AccessControlLists.init(ctx.getEnvironment().getMasterServices());
785   }
786 
787   @Override
788   public void preMasterInitialization(
789       ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
790   }
791 
792   @Override
793   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
794       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
795       throws IOException {
796     requirePermission("snapshot", Permission.Action.ADMIN);
797   }
798 
799   @Override
800   public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
801       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
802       throws IOException {
803   }
804 
805   @Override
806   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
807       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
808       throws IOException {
809     requirePermission("clone", Permission.Action.ADMIN);
810   }
811 
812   @Override
813   public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
814       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
815       throws IOException {
816   }
817 
818   @Override
819   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
820       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
821       throws IOException {
822     requirePermission("restore", Permission.Action.ADMIN);
823   }
824 
825   @Override
826   public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
827       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
828       throws IOException {
829   }
830 
831   @Override
832   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
833       final SnapshotDescription snapshot) throws IOException {
834     requirePermission("deleteSnapshot", Permission.Action.ADMIN);
835   }
836 
837   @Override
838   public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
839       final SnapshotDescription snapshot) throws IOException {
840   }
841 
842   @Override
843   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
844       NamespaceDescriptor ns) throws IOException {
845     requireGlobalPermission("createNamespace", Action.ADMIN, ns.getName());
846   }
847 
848   @Override
849   public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
850       NamespaceDescriptor ns) throws IOException {
851   }
852 
853   @Override
854   public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
855       throws IOException {
856     requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
857   }
858 
859   @Override
860   public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
861                                   String namespace) throws IOException {
862     AccessControlLists.removeNamespacePermissions(ctx.getEnvironment().getConfiguration(),
863         namespace);
864     LOG.info(namespace + "entry deleted in "+AccessControlLists.ACL_TABLE_NAME+" table.");
865   }
866 
867   @Override
868   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
869       NamespaceDescriptor ns) throws IOException {
870     requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
871   }
872 
873   @Override
874   public void postModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
875                                   NamespaceDescriptor ns) throws IOException {
876   }
877 
878   /* ---- RegionObserver implementation ---- */
879 
880   @Override
881   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
882       throws IOException {
883     RegionCoprocessorEnvironment env = e.getEnvironment();
884     final HRegion region = env.getRegion();
885     if (region == null) {
886       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
887     } else {
888       HRegionInfo regionInfo = region.getRegionInfo();
889       if (isSpecialTable(regionInfo)) {
890         isSystemOrSuperUser(regionEnv.getConfiguration());
891       } else {
892         requirePermission("preOpen", Action.ADMIN);
893       }
894     }
895   }
896 
897   @Override
898   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
899     RegionCoprocessorEnvironment env = c.getEnvironment();
900     final HRegion region = env.getRegion();
901     if (region == null) {
902       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
903       return;
904     }
905     if (AccessControlLists.isAclRegion(region)) {
906       aclRegion = true;
907       try {
908         initialize(env);
909       } catch (IOException ex) {
910         // if we can't obtain permissions, it's better to fail
911         // than perform checks incorrectly
912         throw new RuntimeException("Failed to initialize permissions cache", ex);
913       }
914     }
915   }
916 
917   @Override
918   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
919     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
920   }
921 
922   @Override
923   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
924     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
925   }
926 
927   @Override
928   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
929       byte[] splitRow) throws IOException {
930     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
931   }
932 
933   @Override
934   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
935       final Store store, final InternalScanner scanner, final ScanType scanType)
936           throws IOException {
937     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
938     return scanner;
939   }
940 
941   @Override
942   public void preCompactSelection(final ObserverContext<RegionCoprocessorEnvironment> e,
943       final Store store, final List<StoreFile> candidates) throws IOException {
944     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
945   }
946 
947   @Override
948   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
949       final byte [] row, final byte [] family, final Result result)
950       throws IOException {
951     assert family != null;
952     //noinspection PrimitiveArrayArgumentToVariableArgMethod
953     requirePermission("getClosestRowBefore", Permission.Action.READ, c.getEnvironment(),
954         makeFamilyMap(family, null));
955   }
956 
957   @Override
958   public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
959       final Get get, final List<Cell> result) throws IOException {
960     /*
961      if column family level checks fail, check for a qualifier level permission
962      in one of the families.  If it is present, then continue with the AccessControlFilter.
963       */
964     RegionCoprocessorEnvironment e = c.getEnvironment();
965     User requestUser = getActiveUser();
966     AuthResult authResult = permissionGranted("get", requestUser,
967         Permission.Action.READ, e, get.getFamilyMap());
968     if (!authResult.isAllowed()) {
969       if (hasFamilyQualifierPermission(requestUser,
970           Permission.Action.READ, e, get.getFamilyMap())) {
971         TableName table = getTableName(e);
972         AccessControlFilter filter = new AccessControlFilter(authManager,
973             requestUser, table);
974 
975         // wrap any existing filter
976         if (get.getFilter() != null) {
977           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
978               Lists.newArrayList(filter, get.getFilter()));
979           get.setFilter(wrapper);
980         } else {
981           get.setFilter(filter);
982         }
983         logResult(AuthResult.allow("get", "Access allowed with filter", requestUser,
984             Permission.Action.READ, authResult.getTableName(), get.getFamilyMap()));
985       } else {
986         logResult(authResult);
987         throw new AccessDeniedException("Insufficient permissions (table=" +
988           e.getRegion().getTableDesc().getTableName() + ", action=READ)");
989       }
990     } else {
991       // log auth success
992       logResult(authResult);
993     }
994   }
995 
996   @Override
997   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
998       final Get get, final boolean exists) throws IOException {
999     requirePermission("exists", Permission.Action.READ, c.getEnvironment(),
1000         get.getFamilyMap());
1001     return exists;
1002   }
1003 
1004   @Override
1005   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1006       final Put put, final WALEdit edit, final Durability durability)
1007       throws IOException {
1008     requirePermission("put", Permission.Action.WRITE, c.getEnvironment(),
1009         put.getFamilyCellMap());
1010   }
1011 
1012   @Override
1013   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1014       final Put put, final WALEdit edit, final Durability durability) {
1015     if (aclRegion) {
1016       updateACL(c.getEnvironment(), put.getFamilyCellMap());
1017     }
1018   }
1019 
1020   @Override
1021   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1022       final Delete delete, final WALEdit edit, final Durability durability)
1023       throws IOException {
1024     requirePermission("delete", Permission.Action.WRITE, c.getEnvironment(),
1025         delete.getFamilyCellMap());
1026   }
1027 
1028   @Override
1029   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1030       final Delete delete, final WALEdit edit, final Durability durability)
1031       throws IOException {
1032     if (aclRegion) {
1033       updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1034     }
1035   }
1036 
1037   @Override
1038   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1039       final byte [] row, final byte [] family, final byte [] qualifier,
1040       final CompareFilter.CompareOp compareOp,
1041       final ByteArrayComparable comparator, final Put put,
1042       final boolean result) throws IOException {
1043     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
1044     requirePermission("checkAndPut", Permission.Action.READ, c.getEnvironment(), familyMap);
1045     requirePermission("checkAndPut", Permission.Action.WRITE, c.getEnvironment(), familyMap);
1046     return result;
1047   }
1048 
1049   @Override
1050   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1051       final byte [] row, final byte [] family, final byte [] qualifier,
1052       final CompareFilter.CompareOp compareOp,
1053       final ByteArrayComparable comparator, final Delete delete,
1054       final boolean result) throws IOException {
1055     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
1056     requirePermission("checkAndDelete", Permission.Action.READ, c.getEnvironment(), familyMap);
1057     requirePermission("checkAndDelete", Permission.Action.WRITE, c.getEnvironment(), familyMap);
1058     return result;
1059   }
1060 
1061   @Override
1062   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1063       final byte [] row, final byte [] family, final byte [] qualifier,
1064       final long amount, final boolean writeToWAL)
1065       throws IOException {
1066     Map<byte[], ? extends Collection<byte[]>> familyMap = makeFamilyMap(family, qualifier);
1067     requirePermission("incrementColumnValue", Permission.Action.WRITE, c.getEnvironment(), familyMap);
1068     return -1;
1069   }
1070 
1071   @Override
1072   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1073       throws IOException {
1074     requirePermission("append", Permission.Action.WRITE, c.getEnvironment(), append.getFamilyCellMap());
1075     return null;
1076   }
1077 
1078   @Override
1079   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1080       final Increment increment)
1081       throws IOException {
1082     // Create a map of family to qualifiers.
1083     Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
1084     for (Map.Entry<byte [], List<Cell>> entry: increment.getFamilyCellMap().entrySet()) {
1085       Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
1086       for (Cell cell: entry.getValue()) {
1087         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
1088         qualifiers.add(kv.getQualifier());
1089       }
1090       familyMap.put(entry.getKey(), qualifiers);
1091     }
1092     requirePermission("increment", Permission.Action.WRITE, c.getEnvironment(), familyMap);
1093     return null;
1094   }
1095 
1096   @Override
1097   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1098       final Scan scan, final RegionScanner s) throws IOException {
1099     /*
1100      if column family level checks fail, check for a qualifier level permission
1101      in one of the families.  If it is present, then continue with the AccessControlFilter.
1102       */
1103     RegionCoprocessorEnvironment e = c.getEnvironment();
1104     User user = getActiveUser();
1105     AuthResult authResult = permissionGranted("scannerOpen", user, Permission.Action.READ, e,
1106         scan.getFamilyMap());
1107     if (!authResult.isAllowed()) {
1108       if (hasFamilyQualifierPermission(user, Permission.Action.READ, e,
1109           scan.getFamilyMap())) {
1110         TableName table = getTableName(e);
1111         AccessControlFilter filter = new AccessControlFilter(authManager,
1112             user, table);
1113 
1114         // wrap any existing filter
1115         if (scan.hasFilter()) {
1116           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1117               Lists.newArrayList(filter, scan.getFilter()));
1118           scan.setFilter(wrapper);
1119         } else {
1120           scan.setFilter(filter);
1121         }
1122         logResult(AuthResult.allow("scannerOpen", "Access allowed with filter", user,
1123             Permission.Action.READ, authResult.getTableName(), scan.getFamilyMap()));
1124       } else {
1125         // no table/family level perms and no qualifier level perms, reject
1126         logResult(authResult);
1127         throw new AccessDeniedException("Insufficient permissions for user '"+
1128             (user != null ? user.getShortName() : "null")+"' "+
1129             "for scanner open on table " + getTableName(e));
1130       }
1131     } else {
1132       // log success
1133       logResult(authResult);
1134     }
1135     return s;
1136   }
1137 
1138   @Override
1139   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1140       final Scan scan, final RegionScanner s) throws IOException {
1141     User user = getActiveUser();
1142     if (user != null && user.getShortName() != null) {      // store reference to scanner owner for later checks
1143       scannerOwners.put(s, user.getShortName());
1144     }
1145     return s;
1146   }
1147 
1148   @Override
1149   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1150       final InternalScanner s, final List<Result> result,
1151       final int limit, final boolean hasNext) throws IOException {
1152     requireScannerOwner(s);
1153     return hasNext;
1154   }
1155 
1156   @Override
1157   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1158       final InternalScanner s) throws IOException {
1159     requireScannerOwner(s);
1160   }
1161 
1162   @Override
1163   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1164       final InternalScanner s) throws IOException {
1165     // clean up any associated owner mapping
1166     scannerOwners.remove(s);
1167   }
1168 
1169   /**
1170    * Verify, when servicing an RPC, that the caller is the scanner owner.
1171    * If so, we assume that access control is correctly enforced based on
1172    * the checks performed in preScannerOpen()
1173    */
1174   private void requireScannerOwner(InternalScanner s)
1175       throws AccessDeniedException {
1176     if (RequestContext.isInRequestContext()) {
1177       String requestUserName = RequestContext.getRequestUserName();
1178       String owner = scannerOwners.get(s);
1179       if (owner != null && !owner.equals(requestUserName)) {
1180         throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
1181       }
1182     }
1183   }
1184 
1185   /**
1186    * Verifies user has WRITE privileges on
1187    * the Column Families involved in the bulkLoadHFile
1188    * request. Specific Column Write privileges are presently
1189    * ignored.
1190    */
1191   @Override
1192   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
1193       List<Pair<byte[], String>> familyPaths) throws IOException {
1194     List<byte[]> cfs = new LinkedList<byte[]>();
1195     for(Pair<byte[],String> el : familyPaths) {
1196       requirePermission("preBulkLoadHFile",
1197           ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
1198           el.getFirst(),
1199           null,
1200           Permission.Action.WRITE);
1201     }
1202   }
1203 
1204   private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String method, Action action) throws IOException {
1205     User requestUser = getActiveUser();
1206     TableName tableName = e.getRegion().getTableDesc().getTableName();
1207     AuthResult authResult = permissionGranted(method, requestUser,
1208         action, e, Collections.EMPTY_MAP);
1209     if (!authResult.isAllowed()) {
1210       for(UserPermission userPerm:
1211           AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), tableName)) {
1212         for(Permission.Action userAction: userPerm.getActions()) {
1213           if(userAction.equals(action)) {
1214             return AuthResult.allow(method, "Access allowed", requestUser,
1215                 action, tableName, null, null);
1216           }
1217         }
1218       }
1219     }
1220     return authResult;
1221   }
1222 
1223   /**
1224    * Authorization check for
1225    * SecureBulkLoadProtocol.prepareBulkLoad()
1226    * @param e
1227    * @throws IOException
1228    */
1229   //TODO this should end up as a coprocessor hook
1230   public void prePrepareBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1231     AuthResult authResult = hasSomeAccess(e, "prePrepareBulkLoad", Action.WRITE);
1232     logResult(authResult);
1233     if (!authResult.isAllowed()) {
1234       throw new AccessDeniedException("Insufficient permissions (table=" +
1235         e.getRegion().getTableDesc().getTableName() + ", action=WRITE)");
1236     }
1237   }
1238 
1239   /**
1240    * Authorization security check for
1241    * SecureBulkLoadProtocol.cleanupBulkLoad()
1242    * @param e
1243    * @throws IOException
1244    */
1245   //TODO this should end up as a coprocessor hook
1246   public void preCleanupBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1247     AuthResult authResult = hasSomeAccess(e, "preCleanupBulkLoad", Action.WRITE);
1248     logResult(authResult);
1249     if (!authResult.isAllowed()) {
1250       throw new AccessDeniedException("Insufficient permissions (table=" +
1251         e.getRegion().getTableDesc().getTableName() + ", action=WRITE)");
1252     }
1253   }
1254 
1255   /* ---- Protobuf AccessControlService implementation ---- */
1256   @Override
1257   public void grant(RpcController controller,
1258                     AccessControlProtos.GrantRequest request,
1259                     RpcCallback<AccessControlProtos.GrantResponse> done) {
1260     UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
1261     AccessControlProtos.GrantResponse response = null;
1262     try {
1263       // verify it's only running at .acl.
1264       if (aclRegion) {
1265         if (LOG.isDebugEnabled()) {
1266           LOG.debug("Received request to grant access permission " + perm.toString());
1267         }
1268 
1269         switch(request.getUserPermission().getPermission().getType()) {
1270           case Global :
1271           case Table :
1272             requirePermission("grant", perm.getTableName(), perm.getFamily(),
1273                 perm.getQualifier(), Action.ADMIN);
1274             break;
1275           case Namespace :
1276             requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace());
1277         }
1278 
1279         AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
1280         if (AUDITLOG.isTraceEnabled()) {
1281           // audit log should store permission changes in addition to auth results
1282           AUDITLOG.trace("Granted permission " + perm.toString());
1283         }
1284       } else {
1285         throw new CoprocessorException(AccessController.class, "This method "
1286             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1287       }
1288       response = AccessControlProtos.GrantResponse.getDefaultInstance();
1289     } catch (IOException ioe) {
1290       // pass exception back up
1291       ResponseConverter.setControllerException(controller, ioe);
1292     }
1293     done.run(response);
1294   }
1295 
1296   @Override
1297   public void revoke(RpcController controller,
1298                      AccessControlProtos.RevokeRequest request,
1299                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
1300     UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
1301     AccessControlProtos.RevokeResponse response = null;
1302     try {
1303       // only allowed to be called on _acl_ region
1304       if (aclRegion) {
1305         if (LOG.isDebugEnabled()) {
1306           LOG.debug("Received request to revoke access permission " + perm.toString());
1307         }
1308 
1309         switch(request.getUserPermission().getPermission().getType()) {
1310           case Global :
1311           case Table :
1312             requirePermission("revoke", perm.getTableName(), perm.getFamily(),
1313                               perm.getQualifier(), Action.ADMIN);
1314             break;
1315           case Namespace :
1316             requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace());
1317         }
1318 
1319         AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
1320         if (AUDITLOG.isTraceEnabled()) {
1321           // audit log should record all permission changes
1322           AUDITLOG.trace("Revoked permission " + perm.toString());
1323         }
1324       } else {
1325         throw new CoprocessorException(AccessController.class, "This method "
1326             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1327       }
1328       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
1329     } catch (IOException ioe) {
1330       // pass exception back up
1331       ResponseConverter.setControllerException(controller, ioe);
1332     }
1333     done.run(response);
1334   }
1335 
1336   @Override
1337   public void getUserPermissions(RpcController controller,
1338                                  AccessControlProtos.GetUserPermissionsRequest request,
1339                                  RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
1340     AccessControlProtos.GetUserPermissionsResponse response = null;
1341     try {
1342       // only allowed to be called on _acl_ region
1343       if (aclRegion) {
1344         List<UserPermission> perms = null;
1345         if(request.getType() == AccessControlProtos.Permission.Type.Table) {
1346           TableName table = null;
1347           if (request.hasTableName()) {
1348             table = ProtobufUtil.toTableName(request.getTableName());
1349           }
1350           requirePermission("userPermissions", table, null, null, Action.ADMIN);
1351 
1352           perms = AccessControlLists.getUserTablePermissions(
1353               regionEnv.getConfiguration(), table);
1354         } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
1355           perms = AccessControlLists.getUserNamespacePermissions(
1356               regionEnv.getConfiguration(), request.getNamespaceName().toStringUtf8());
1357         } else {
1358           perms = AccessControlLists.getUserPermissions(
1359               regionEnv.getConfiguration(), null);
1360         }
1361         response = ResponseConverter.buildGetUserPermissionsResponse(perms);
1362       } else {
1363         throw new CoprocessorException(AccessController.class, "This method "
1364             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1365       }
1366     } catch (IOException ioe) {
1367       // pass exception back up
1368       ResponseConverter.setControllerException(controller, ioe);
1369     }
1370     done.run(response);
1371   }
1372 
1373   @Override
1374   public void checkPermissions(RpcController controller,
1375                                AccessControlProtos.CheckPermissionsRequest request,
1376                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
1377     Permission[] permissions = new Permission[request.getPermissionCount()];
1378     for (int i=0; i < request.getPermissionCount(); i++) {
1379       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
1380     }
1381     AccessControlProtos.CheckPermissionsResponse response = null;
1382     try {
1383       TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
1384       for (Permission permission : permissions) {
1385         if (permission instanceof TablePermission) {
1386           TablePermission tperm = (TablePermission) permission;
1387           for (Permission.Action action : permission.getActions()) {
1388             if (!tperm.getTableName().equals(tableName)) {
1389               throw new CoprocessorException(AccessController.class, String.format("This method "
1390                   + "can only execute at the table specified in TablePermission. " +
1391                   "Table of the region:%s , requested table:%s", tableName,
1392                   tperm.getTableName()));
1393             }
1394 
1395             Map<byte[], Set<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
1396             if (tperm.getFamily() != null) {
1397               if (tperm.getQualifier() != null) {
1398                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
1399                 qualifiers.add(tperm.getQualifier());
1400                 familyMap.put(tperm.getFamily(), qualifiers);
1401               } else {
1402                 familyMap.put(tperm.getFamily(), null);
1403               }
1404             }
1405 
1406             requirePermission("checkPermissions", action, regionEnv, familyMap);
1407           }
1408 
1409         } else {
1410           for (Permission.Action action : permission.getActions()) {
1411             requirePermission("checkPermissions", action);
1412           }
1413         }
1414       }
1415       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
1416     } catch (IOException ioe) {
1417       ResponseConverter.setControllerException(controller, ioe);
1418     }
1419     done.run(response);
1420   }
1421 
1422   @Override
1423   public Service getService() {
1424     return AccessControlProtos.AccessControlService.newReflectiveService(this);
1425   }
1426 
1427   private TableName getTableName(RegionCoprocessorEnvironment e) {
1428     HRegion region = e.getRegion();
1429     TableName tableName = null;
1430 
1431     if (region != null) {
1432       HRegionInfo regionInfo = region.getRegionInfo();
1433       if (regionInfo != null) {
1434         tableName = regionInfo.getTable();
1435       }
1436     }
1437     return tableName;
1438   }
1439 
1440 
1441   @Override
1442   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
1443       throws IOException {
1444     requirePermission("preClose", Action.ADMIN);
1445   }
1446 
1447   private void isSystemOrSuperUser(Configuration conf) throws IOException {
1448     User user = userProvider.getCurrent();
1449     if (user == null) {
1450       throw new IOException("Unable to obtain the current user, " +
1451         "authorization checks for internal operations will not work correctly!");
1452     }
1453 
1454     String currentUser = user.getShortName();
1455     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
1456       AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
1457 
1458     User activeUser = getActiveUser();
1459     if (!(superusers.contains(activeUser.getShortName()))) {
1460       throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null") +
1461         "is not system or super user.");
1462     }
1463   }
1464 
1465   private boolean isSpecialTable(HRegionInfo regionInfo) {
1466     TableName tableName = regionInfo.getTable();
1467     return tableName.equals(AccessControlLists.ACL_TABLE_NAME)
1468         || tableName.equals(TableName.NAMESPACE_TABLE_NAME)
1469         || tableName.equals(TableName.META_TABLE_NAME);
1470   }
1471 
1472   @Override
1473   public void preStopRegionServer(
1474       ObserverContext<RegionServerCoprocessorEnvironment> env)
1475       throws IOException {
1476     requirePermission("preStopRegionServer", Permission.Action.ADMIN);
1477   }
1478 
1479   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
1480       byte[] qualifier) {
1481     if (family == null) {
1482       return null;
1483     }
1484 
1485     Map<byte[], Collection<byte[]>> familyMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
1486     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
1487     return familyMap;
1488   }
1489 
1490   @Override
1491   public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1492       List<TableName> tableNamesList,
1493       List<HTableDescriptor> descriptors) throws IOException {
1494     // If the list is empty, this is a request for all table descriptors and requires GLOBAL
1495     // ADMIN privs.
1496     if (tableNamesList == null || tableNamesList.isEmpty()) {
1497       requireGlobalPermission("getTableDescriptors", Permission.Action.ADMIN, null, null);
1498     }
1499     // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
1500     // request can be granted.
1501     else {
1502       MasterServices masterServices = ctx.getEnvironment().getMasterServices();
1503       for (TableName tableName: tableNamesList) {
1504         // Do not deny if the table does not exist
1505         try {
1506           masterServices.checkTableModifiable(tableName);
1507         } catch (TableNotFoundException ex) {
1508           // Skip checks for a table that does not exist
1509           continue;
1510         } catch (TableNotDisabledException ex) {
1511           // We don't care about this
1512         }
1513         requirePermission("getTableDescriptors", tableName, null, null,
1514           Permission.Action.ADMIN, Permission.Action.CREATE);
1515       }
1516     }
1517   }
1518 
1519   @Override
1520   public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1521       List<HTableDescriptor> descriptors) throws IOException {
1522   }
1523 }