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