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