001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.access;
019
020import java.io.IOException;
021import java.security.PrivilegedExceptionAction;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Optional;
031import java.util.Set;
032import java.util.TreeMap;
033import java.util.TreeSet;
034import java.util.stream.Collectors;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.hbase.ArrayBackedTag;
037import org.apache.hadoop.hbase.Cell;
038import org.apache.hadoop.hbase.CellUtil;
039import org.apache.hadoop.hbase.CompareOperator;
040import org.apache.hadoop.hbase.CompoundConfiguration;
041import org.apache.hadoop.hbase.CoprocessorEnvironment;
042import org.apache.hadoop.hbase.DoNotRetryIOException;
043import org.apache.hadoop.hbase.ExtendedCell;
044import org.apache.hadoop.hbase.ExtendedCellScanner;
045import org.apache.hadoop.hbase.HBaseInterfaceAudience;
046import org.apache.hadoop.hbase.HConstants;
047import org.apache.hadoop.hbase.KeyValue;
048import org.apache.hadoop.hbase.KeyValue.Type;
049import org.apache.hadoop.hbase.NamespaceDescriptor;
050import org.apache.hadoop.hbase.PrivateCellUtil;
051import org.apache.hadoop.hbase.ServerName;
052import org.apache.hadoop.hbase.TableName;
053import org.apache.hadoop.hbase.Tag;
054import org.apache.hadoop.hbase.client.Admin;
055import org.apache.hadoop.hbase.client.Append;
056import org.apache.hadoop.hbase.client.BalanceRequest;
057import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
058import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
059import org.apache.hadoop.hbase.client.Delete;
060import org.apache.hadoop.hbase.client.Durability;
061import org.apache.hadoop.hbase.client.Get;
062import org.apache.hadoop.hbase.client.Increment;
063import org.apache.hadoop.hbase.client.MasterSwitchType;
064import org.apache.hadoop.hbase.client.Mutation;
065import org.apache.hadoop.hbase.client.Put;
066import org.apache.hadoop.hbase.client.Query;
067import org.apache.hadoop.hbase.client.RegionInfo;
068import org.apache.hadoop.hbase.client.Result;
069import org.apache.hadoop.hbase.client.Scan;
070import org.apache.hadoop.hbase.client.SnapshotDescription;
071import org.apache.hadoop.hbase.client.Table;
072import org.apache.hadoop.hbase.client.TableDescriptor;
073import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
074import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver;
075import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
076import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
077import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
078import org.apache.hadoop.hbase.coprocessor.HasMasterServices;
079import org.apache.hadoop.hbase.coprocessor.HasRegionServerServices;
080import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
081import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
082import org.apache.hadoop.hbase.coprocessor.MasterObserver;
083import org.apache.hadoop.hbase.coprocessor.ObserverContext;
084import org.apache.hadoop.hbase.coprocessor.ObserverRpcCallContext;
085import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
086import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
087import org.apache.hadoop.hbase.coprocessor.RegionObserver;
088import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor;
089import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
090import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
091import org.apache.hadoop.hbase.filter.ByteArrayComparable;
092import org.apache.hadoop.hbase.filter.Filter;
093import org.apache.hadoop.hbase.filter.FilterList;
094import org.apache.hadoop.hbase.io.hfile.HFile;
095import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
096import org.apache.hadoop.hbase.ipc.RpcServer;
097import org.apache.hadoop.hbase.master.MasterServices;
098import org.apache.hadoop.hbase.net.Address;
099import org.apache.hadoop.hbase.quotas.GlobalQuotaSettings;
100import org.apache.hadoop.hbase.regionserver.BloomType;
101import org.apache.hadoop.hbase.regionserver.FlushLifeCycleTracker;
102import org.apache.hadoop.hbase.regionserver.InternalScanner;
103import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
104import org.apache.hadoop.hbase.regionserver.Region;
105import org.apache.hadoop.hbase.regionserver.RegionScanner;
106import org.apache.hadoop.hbase.regionserver.RegionServerServices;
107import org.apache.hadoop.hbase.regionserver.ScanType;
108import org.apache.hadoop.hbase.regionserver.ScannerContext;
109import org.apache.hadoop.hbase.regionserver.Store;
110import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
111import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
112import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
113import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
114import org.apache.hadoop.hbase.replication.SyncReplicationState;
115import org.apache.hadoop.hbase.security.AccessDeniedException;
116import org.apache.hadoop.hbase.security.Superusers;
117import org.apache.hadoop.hbase.security.User;
118import org.apache.hadoop.hbase.security.UserProvider;
119import org.apache.hadoop.hbase.security.access.Permission.Action;
120import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
121import org.apache.hadoop.hbase.util.ByteRange;
122import org.apache.hadoop.hbase.util.Bytes;
123import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
124import org.apache.hadoop.hbase.util.Pair;
125import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
126import org.apache.hadoop.hbase.wal.WALEdit;
127import org.apache.yetus.audience.InterfaceAudience;
128import org.slf4j.Logger;
129import org.slf4j.LoggerFactory;
130
131import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
132import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableSet;
133import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
134import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
135import org.apache.hbase.thirdparty.com.google.common.collect.MapMaker;
136import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
137import org.apache.hbase.thirdparty.com.google.protobuf.Message;
138import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
139import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
140import org.apache.hbase.thirdparty.com.google.protobuf.Service;
141
142import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
143import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter;
144import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos;
145import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.AccessControlService;
146import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.HasPermissionRequest;
147import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.HasPermissionResponse;
148
149/**
150 * Provides basic authorization checks for data access and administrative operations.
151 * <p>
152 * {@code AccessController} performs authorization checks for HBase operations based on:
153 * </p>
154 * <ul>
155 * <li>the identity of the user performing the operation</li>
156 * <li>the scope over which the operation is performed, in increasing specificity: global, table,
157 * column family, or qualifier</li>
158 * <li>the type of action being performed (as mapped to {@link Permission.Action} values)</li>
159 * </ul>
160 * <p>
161 * If the authorization check fails, an {@link AccessDeniedException} will be thrown for the
162 * operation.
163 * </p>
164 * <p>
165 * To perform authorization checks, {@code AccessController} relies on the RpcServerEngine being
166 * loaded to provide the user identities for remote requests.
167 * </p>
168 * <p>
169 * The access control lists used for authorization can be manipulated via the exposed
170 * {@link AccessControlService} Interface implementation, and the associated {@code grant},
171 * {@code revoke}, and {@code user_permission} HBase shell commands.
172 * </p>
173 */
174@CoreCoprocessor
175@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
176public class AccessController implements MasterCoprocessor, RegionCoprocessor,
177  RegionServerCoprocessor, AccessControlService.Interface, MasterObserver, RegionObserver,
178  RegionServerObserver, EndpointObserver, BulkLoadObserver {
179  // TODO: encapsulate observer functions into separate class/sub-class.
180
181  private static final Logger LOG = LoggerFactory.getLogger(AccessController.class);
182
183  private static final Logger AUDITLOG =
184    LoggerFactory.getLogger("SecurityLogger." + AccessController.class.getName());
185  private static final String CHECK_COVERING_PERM = "check_covering_perm";
186  private static final String TAG_CHECK_PASSED = "tag_check_passed";
187  private static final byte[] TRUE = Bytes.toBytes(true);
188
189  private AccessChecker accessChecker;
190  private ZKPermissionWatcher zkPermissionWatcher;
191
192  /** flags if we are running on a region of the _acl_ table */
193  private boolean aclRegion = false;
194
195  /**
196   * defined only for Endpoint implementation, so it can have way to access region services
197   */
198  private RegionCoprocessorEnvironment regionEnv;
199
200  /** Mapping of scanner instances to the user who created them */
201  private Map<InternalScanner, String> scannerOwners = new MapMaker().weakKeys().makeMap();
202
203  private Map<TableName, List<UserPermission>> tableAcls;
204
205  /** Provider for mapping principal names to Users */
206  private UserProvider userProvider;
207
208  /**
209   * if we are active, usually false, only true if "hbase.security.authorization" has been set to
210   * true in site configuration
211   */
212  private boolean authorizationEnabled;
213
214  /** if we are able to support cell ACLs */
215  private boolean cellFeaturesEnabled;
216
217  /** if we should check EXEC permissions */
218  private boolean shouldCheckExecPermission;
219
220  /**
221   * if we should terminate access checks early as soon as table or CF grants allow access; pre-0.98
222   * compatible behavior
223   */
224  private boolean compatibleEarlyTermination;
225
226  /** if we have been successfully initialized */
227  private volatile boolean initialized = false;
228
229  /** if the ACL table is available, only relevant in the master */
230  private volatile boolean aclTabAvailable = false;
231
232  public static boolean isCellAuthorizationSupported(Configuration conf) {
233    return AccessChecker.isAuthorizationSupported(conf)
234      && (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
235  }
236
237  public Region getRegion() {
238    return regionEnv != null ? regionEnv.getRegion() : null;
239  }
240
241  public AuthManager getAuthManager() {
242    return accessChecker.getAuthManager();
243  }
244
245  private void initialize(RegionCoprocessorEnvironment e) throws IOException {
246    final Region region = e.getRegion();
247    Configuration conf = e.getConfiguration();
248    Map<byte[], ListMultimap<String, UserPermission>> tables = PermissionStorage.loadAll(region);
249    // For each table, write out the table's permissions to the respective
250    // znode for that table.
251    for (Map.Entry<byte[], ListMultimap<String, UserPermission>> t : tables.entrySet()) {
252      byte[] entry = t.getKey();
253      ListMultimap<String, UserPermission> perms = t.getValue();
254      byte[] serialized = PermissionStorage.writePermissionsAsBytes(perms, conf);
255      zkPermissionWatcher.writeToZookeeper(entry, serialized);
256    }
257    initialized = true;
258  }
259
260  /**
261   * Writes all table ACLs for the tables in the given Map up into ZooKeeper znodes. This is called
262   * to synchronize ACL changes following {@code _acl_} table updates.
263   */
264  private void updateACL(RegionCoprocessorEnvironment e, final Map<byte[], List<Cell>> familyMap) {
265    Set<byte[]> entries = new TreeSet<>(Bytes.BYTES_RAWCOMPARATOR);
266    for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
267      List<Cell> cells = f.getValue();
268      for (Cell cell : cells) {
269        if (CellUtil.matchingFamily(cell, PermissionStorage.ACL_LIST_FAMILY)) {
270          entries.add(CellUtil.cloneRow(cell));
271        }
272      }
273    }
274    Configuration conf = regionEnv.getConfiguration();
275    byte[] currentEntry = null;
276    // TODO: Here we are already on the ACL region. (And it is single
277    // region) We can even just get the region from the env and do get
278    // directly. The short circuit connection would avoid the RPC overhead
279    // so no socket communication, req write/read .. But we have the PB
280    // to and fro conversion overhead. get req is converted to PB req
281    // and results are converted to PB results 1st and then to POJOs
282    // again. We could have avoided such at least in ACL table context..
283    try (Table t = e.getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
284      for (byte[] entry : entries) {
285        currentEntry = entry;
286        ListMultimap<String, UserPermission> perms =
287          PermissionStorage.getPermissions(conf, entry, t, null, null, null, false);
288        byte[] serialized = PermissionStorage.writePermissionsAsBytes(perms, conf);
289        zkPermissionWatcher.writeToZookeeper(entry, serialized);
290      }
291    } catch (IOException ex) {
292      LOG.error("Failed updating permissions mirror for '"
293        + (currentEntry == null ? "null" : Bytes.toString(currentEntry)) + "'", ex);
294    }
295  }
296
297  /**
298   * Check the current user for authorization to perform a specific action against the given set of
299   * row data.
300   * @param opType   the operation type
301   * @param user     the user
302   * @param e        the coprocessor environment
303   * @param families the map of column families to qualifiers present in the request
304   * @param actions  the desired actions
305   * @return an authorization result
306   */
307  private AuthResult permissionGranted(OpType opType, User user, RegionCoprocessorEnvironment e,
308    Map<byte[], ? extends Collection<?>> families, Action... actions) {
309    AuthResult result = null;
310    for (Action action : actions) {
311      result = accessChecker.permissionGranted(opType.toString(), user, action,
312        e.getRegion().getRegionInfo().getTable(), families);
313      if (!result.isAllowed()) {
314        return result;
315      }
316    }
317    return result;
318  }
319
320  public void requireAccess(ObserverContext<?> ctx, String request, TableName tableName,
321    Action... permissions) throws IOException {
322    accessChecker.requireAccess(getActiveUser(ctx), request, tableName, permissions);
323  }
324
325  public void requirePermission(ObserverContext<?> ctx, String request, Action perm)
326    throws IOException {
327    accessChecker.requirePermission(getActiveUser(ctx), request, null, perm);
328  }
329
330  public void requireGlobalPermission(ObserverContext<?> ctx, String request, Action perm,
331    TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
332    accessChecker.requireGlobalPermission(getActiveUser(ctx), request, perm, tableName, familyMap,
333      null);
334  }
335
336  public void requireGlobalPermission(ObserverContext<?> ctx, String request, Action perm,
337    String namespace) throws IOException {
338    accessChecker.requireGlobalPermission(getActiveUser(ctx), request, perm, namespace);
339  }
340
341  public void requireNamespacePermission(ObserverContext<?> ctx, String request, String namespace,
342    Action... permissions) throws IOException {
343    accessChecker.requireNamespacePermission(getActiveUser(ctx), request, namespace, null,
344      permissions);
345  }
346
347  public void requireNamespacePermission(ObserverContext<?> ctx, String request, String namespace,
348    TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
349    throws IOException {
350    accessChecker.requireNamespacePermission(getActiveUser(ctx), request, namespace, tableName,
351      familyMap, permissions);
352  }
353
354  public void requirePermission(ObserverContext<?> ctx, String request, TableName tableName,
355    byte[] family, byte[] qualifier, Action... permissions) throws IOException {
356    accessChecker.requirePermission(getActiveUser(ctx), request, tableName, family, qualifier, null,
357      permissions);
358  }
359
360  public void requireTablePermission(ObserverContext<?> ctx, String request, TableName tableName,
361    byte[] family, byte[] qualifier, Action... permissions) throws IOException {
362    accessChecker.requireTablePermission(getActiveUser(ctx), request, tableName, family, qualifier,
363      permissions);
364  }
365
366  public void checkLockPermissions(ObserverContext<?> ctx, String namespace, TableName tableName,
367    RegionInfo[] regionInfos, String reason) throws IOException {
368    accessChecker.checkLockPermissions(getActiveUser(ctx), namespace, tableName, regionInfos,
369      reason);
370  }
371
372  /**
373   * Returns <code>true</code> if the current user is allowed the given action over at least one of
374   * the column qualifiers in the given column families.
375   */
376  private boolean hasFamilyQualifierPermission(User user, Action perm,
377    RegionCoprocessorEnvironment env, Map<byte[], ? extends Collection<byte[]>> familyMap)
378    throws IOException {
379    RegionInfo hri = env.getRegion().getRegionInfo();
380    TableName tableName = hri.getTable();
381
382    if (user == null) {
383      return false;
384    }
385
386    if (familyMap != null && familyMap.size() > 0) {
387      // at least one family must be allowed
388      for (Map.Entry<byte[], ? extends Collection<byte[]>> family : familyMap.entrySet()) {
389        if (family.getValue() != null && !family.getValue().isEmpty()) {
390          for (byte[] qualifier : family.getValue()) {
391            if (
392              getAuthManager().authorizeUserTable(user, tableName, family.getKey(), qualifier, perm)
393            ) {
394              return true;
395            }
396          }
397        } else {
398          if (getAuthManager().authorizeUserFamily(user, tableName, family.getKey(), perm)) {
399            return true;
400          }
401        }
402      }
403    } else if (LOG.isDebugEnabled()) {
404      LOG.debug("Empty family map passed for permission check");
405    }
406
407    return false;
408  }
409
410  private enum OpType {
411    GET("get"),
412    EXISTS("exists"),
413    SCAN("scan"),
414    PUT("put"),
415    DELETE("delete"),
416    CHECK_AND_PUT("checkAndPut"),
417    CHECK_AND_DELETE("checkAndDelete"),
418    APPEND("append"),
419    INCREMENT("increment");
420
421    private String type;
422
423    private OpType(String type) {
424      this.type = type;
425    }
426
427    @Override
428    public String toString() {
429      return type;
430    }
431  }
432
433  /**
434   * Determine if cell ACLs covered by the operation grant access. This is expensive.
435   * @return false if cell ACLs failed to grant access, true otherwise
436   */
437  private boolean checkCoveringPermission(User user, OpType request, RegionCoprocessorEnvironment e,
438    byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions)
439    throws IOException {
440    if (!cellFeaturesEnabled) {
441      return false;
442    }
443    long cellGrants = 0;
444    long latestCellTs = 0;
445    Get get = new Get(row);
446    // Only in case of Put/Delete op, consider TS within cell (if set for individual cells).
447    // When every cell, within a Mutation, can be linked with diff TS we can not rely on only one
448    // version. We have to get every cell version and check its TS against the TS asked for in
449    // Mutation and skip those Cells which is outside this Mutation TS.In case of Put, we have to
450    // consider only one such passing cell. In case of Delete we have to consider all the cell
451    // versions under this passing version. When Delete Mutation contains columns which are a
452    // version delete just consider only one version for those column cells.
453    boolean considerCellTs = (request == OpType.PUT || request == OpType.DELETE);
454    if (considerCellTs) {
455      get.readAllVersions();
456    } else {
457      get.readVersions(1);
458    }
459    boolean diffCellTsFromOpTs = false;
460    for (Map.Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
461      byte[] col = entry.getKey();
462      // TODO: HBASE-7114 could possibly unify the collection type in family
463      // maps so we would not need to do this
464      if (entry.getValue() instanceof Set) {
465        Set<byte[]> set = (Set<byte[]>) entry.getValue();
466        if (set == null || set.isEmpty()) {
467          get.addFamily(col);
468        } else {
469          for (byte[] qual : set) {
470            get.addColumn(col, qual);
471          }
472        }
473      } else if (entry.getValue() instanceof List) {
474        List<ExtendedCell> list = (List<ExtendedCell>) entry.getValue();
475        if (list == null || list.isEmpty()) {
476          get.addFamily(col);
477        } else {
478          // In case of family delete, a Cell will be added into the list with Qualifier as null.
479          for (ExtendedCell cell : list) {
480            if (
481              cell.getQualifierLength() == 0 && (cell.getTypeByte() == Type.DeleteFamily.getCode()
482                || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode())
483            ) {
484              get.addFamily(col);
485            } else {
486              get.addColumn(col, CellUtil.cloneQualifier(cell));
487            }
488            if (considerCellTs) {
489              long cellTs = cell.getTimestamp();
490              latestCellTs = Math.max(latestCellTs, cellTs);
491              diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs);
492            }
493          }
494        }
495      } else if (entry.getValue() == null) {
496        get.addFamily(col);
497      } else {
498        throw new RuntimeException(
499          "Unhandled collection type " + entry.getValue().getClass().getName());
500      }
501    }
502    // We want to avoid looking into the future. So, if the cells of the
503    // operation specify a timestamp, or the operation itself specifies a
504    // timestamp, then we use the maximum ts found. Otherwise, we bound
505    // the Get to the current server time. We add 1 to the timerange since
506    // the upper bound of a timerange is exclusive yet we need to examine
507    // any cells found there inclusively.
508    long latestTs = Math.max(opTs, latestCellTs);
509    if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) {
510      latestTs = EnvironmentEdgeManager.currentTime();
511    }
512    get.setTimeRange(0, latestTs + 1);
513    // In case of Put operation we set to read all versions. This was done to consider the case
514    // where columns are added with TS other than the Mutation TS. But normally this wont be the
515    // case with Put. There no need to get all versions but get latest version only.
516    if (!diffCellTsFromOpTs && request == OpType.PUT) {
517      get.readVersions(1);
518    }
519    if (LOG.isTraceEnabled()) {
520      LOG.trace("Scanning for cells with " + get);
521    }
522    // This Map is identical to familyMap. The key is a BR rather than byte[].
523    // It will be easy to do gets over this new Map as we can create get keys over the Cell cf by
524    // new SimpleByteRange(cell.familyArray, cell.familyOffset, cell.familyLen)
525    Map<ByteRange, List<Cell>> familyMap1 = new HashMap<>();
526    for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
527      if (entry.getValue() instanceof List) {
528        familyMap1.put(new SimpleMutableByteRange(entry.getKey()), (List<Cell>) entry.getValue());
529      }
530    }
531    RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
532    List<Cell> cells = Lists.newArrayList();
533    Cell prevCell = null;
534    ByteRange curFam = new SimpleMutableByteRange();
535    boolean curColAllVersions = (request == OpType.DELETE);
536    long curColCheckTs = opTs;
537    boolean foundColumn = false;
538    try {
539      boolean more = false;
540      ScannerContext scannerContext = ScannerContext.newBuilder().setBatchLimit(1).build();
541
542      do {
543        cells.clear();
544        // scan with limit as 1 to hold down memory use on wide rows
545        more = scanner.next(cells, scannerContext);
546        for (Cell cell : cells) {
547          if (LOG.isTraceEnabled()) {
548            LOG.trace("Found cell " + cell);
549          }
550          boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell);
551          if (colChange) foundColumn = false;
552          prevCell = cell;
553          if (!curColAllVersions && foundColumn) {
554            continue;
555          }
556          if (colChange && considerCellTs) {
557            curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
558            List<Cell> cols = familyMap1.get(curFam);
559            for (Cell col : cols) {
560              // null/empty qualifier is used to denote a Family delete. The TS and delete type
561              // associated with this is applicable for all columns within the family. That is
562              // why the below (col.getQualifierLength() == 0) check.
563              if (
564                (col.getQualifierLength() == 0 && request == OpType.DELETE)
565                  || CellUtil.matchingQualifier(cell, col)
566              ) {
567                byte type = PrivateCellUtil.getTypeByte(col);
568                if (considerCellTs) {
569                  curColCheckTs = col.getTimestamp();
570                }
571                // For a Delete op we pass allVersions as true. When a Delete Mutation contains
572                // a version delete for a column no need to check all the covering cells within
573                // that column. Check all versions when Type is DeleteColumn or DeleteFamily
574                // One version delete types are Delete/DeleteFamilyVersion
575                curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type)
576                  || (KeyValue.Type.DeleteFamily.getCode() == type);
577                break;
578              }
579            }
580          }
581          if (cell.getTimestamp() > curColCheckTs) {
582            // Just ignore this cell. This is not a covering cell.
583            continue;
584          }
585          foundColumn = true;
586          for (Action action : actions) {
587            // Are there permissions for this user for the cell?
588            if (!getAuthManager().authorizeCell(user, getTableName(e), cell, action)) {
589              // We can stop if the cell ACL denies access
590              return false;
591            }
592          }
593          cellGrants++;
594        }
595      } while (more);
596    } catch (AccessDeniedException ex) {
597      throw ex;
598    } catch (IOException ex) {
599      LOG.error("Exception while getting cells to calculate covering permission", ex);
600    } finally {
601      scanner.close();
602    }
603    // We should not authorize unless we have found one or more cell ACLs that
604    // grant access. This code is used to check for additional permissions
605    // after no table or CF grants are found.
606    return cellGrants > 0;
607  }
608
609  private static void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) {
610    // Iterate over the entries in the familyMap, replacing the cells therein
611    // with new cells including the ACL data
612    for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) {
613      List<Cell> newCells = Lists.newArrayList();
614      for (Cell c : e.getValue()) {
615        assert c instanceof ExtendedCell;
616        ExtendedCell cell = (ExtendedCell) c;
617        // Prepend the supplied perms in a new ACL tag to an update list of tags for the cell
618        List<Tag> tags = new ArrayList<>();
619        tags.add(new ArrayBackedTag(PermissionStorage.ACL_TAG_TYPE, perms));
620        Iterator<Tag> tagIterator = PrivateCellUtil.tagsIterator(cell);
621        while (tagIterator.hasNext()) {
622          tags.add(tagIterator.next());
623        }
624        newCells.add(PrivateCellUtil.createCell(cell, tags));
625      }
626      // This is supposed to be safe, won't CME
627      e.setValue(newCells);
628    }
629  }
630
631  // Checks whether incoming cells contain any tag with type as ACL_TAG_TYPE. This tag
632  // type is reserved and should not be explicitly set by user.
633  private void checkForReservedTagPresence(User user, Mutation m) throws IOException {
634    // No need to check if we're not going to throw
635    if (!authorizationEnabled) {
636      m.setAttribute(TAG_CHECK_PASSED, TRUE);
637      return;
638    }
639    // Superusers are allowed to store cells unconditionally.
640    if (Superusers.isSuperUser(user)) {
641      m.setAttribute(TAG_CHECK_PASSED, TRUE);
642      return;
643    }
644    // We already checked (prePut vs preBatchMutation)
645    if (m.getAttribute(TAG_CHECK_PASSED) != null) {
646      return;
647    }
648    for (ExtendedCellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
649      Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(cellScanner.current());
650      while (tagsItr.hasNext()) {
651        if (tagsItr.next().getType() == PermissionStorage.ACL_TAG_TYPE) {
652          throw new AccessDeniedException("Mutation contains cell with reserved type tag");
653        }
654      }
655    }
656    m.setAttribute(TAG_CHECK_PASSED, TRUE);
657  }
658
659  /* ---- MasterObserver implementation ---- */
660  @Override
661  public void start(CoprocessorEnvironment env) throws IOException {
662    CompoundConfiguration conf = new CompoundConfiguration();
663    conf.add(env.getConfiguration());
664
665    authorizationEnabled = AccessChecker.isAuthorizationSupported(conf);
666    if (!authorizationEnabled) {
667      LOG.warn("AccessController has been loaded with authorization checks DISABLED!");
668    }
669
670    shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY,
671      AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS);
672
673    cellFeaturesEnabled = (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
674    if (!cellFeaturesEnabled) {
675      LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
676        + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY
677        + " accordingly.");
678    }
679
680    if (env instanceof MasterCoprocessorEnvironment) {
681      // if running on HMaster
682      MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
683      if (mEnv instanceof HasMasterServices) {
684        MasterServices masterServices = ((HasMasterServices) mEnv).getMasterServices();
685        zkPermissionWatcher = masterServices.getZKPermissionWatcher();
686        accessChecker = masterServices.getAccessChecker();
687      }
688    } else if (env instanceof RegionServerCoprocessorEnvironment) {
689      RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
690      if (rsEnv instanceof HasRegionServerServices) {
691        RegionServerServices rsServices =
692          ((HasRegionServerServices) rsEnv).getRegionServerServices();
693        zkPermissionWatcher = rsServices.getZKPermissionWatcher();
694        accessChecker = rsServices.getAccessChecker();
695      }
696    } else if (env instanceof RegionCoprocessorEnvironment) {
697      // if running at region
698      regionEnv = (RegionCoprocessorEnvironment) env;
699      conf.addBytesMap(regionEnv.getRegion().getTableDescriptor().getValues());
700      compatibleEarlyTermination = conf.getBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT,
701        AccessControlConstants.DEFAULT_ATTRIBUTE_EARLY_OUT);
702      if (regionEnv instanceof HasRegionServerServices) {
703        RegionServerServices rsServices =
704          ((HasRegionServerServices) regionEnv).getRegionServerServices();
705        zkPermissionWatcher = rsServices.getZKPermissionWatcher();
706        accessChecker = rsServices.getAccessChecker();
707      }
708    }
709
710    Preconditions.checkState(zkPermissionWatcher != null, "ZKPermissionWatcher is null");
711    Preconditions.checkState(accessChecker != null, "AccessChecker is null");
712
713    // set the user-provider.
714    this.userProvider = UserProvider.instantiate(env.getConfiguration());
715    tableAcls = new MapMaker().weakValues().makeMap();
716  }
717
718  @Override
719  public void stop(CoprocessorEnvironment env) {
720  }
721
722  /*********************************** Observer/Service Getters ***********************************/
723  @Override
724  public Optional<RegionObserver> getRegionObserver() {
725    return Optional.of(this);
726  }
727
728  @Override
729  public Optional<MasterObserver> getMasterObserver() {
730    return Optional.of(this);
731  }
732
733  @Override
734  public Optional<EndpointObserver> getEndpointObserver() {
735    return Optional.of(this);
736  }
737
738  @Override
739  public Optional<BulkLoadObserver> getBulkLoadObserver() {
740    return Optional.of(this);
741  }
742
743  @Override
744  public Optional<RegionServerObserver> getRegionServerObserver() {
745    return Optional.of(this);
746  }
747
748  @Override
749  public Iterable<Service> getServices() {
750    return Collections
751      .singleton(AccessControlProtos.AccessControlService.newReflectiveService(this));
752  }
753
754  /*********************************** Observer implementations ***********************************/
755
756  @Override
757  public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c, TableDescriptor desc,
758    RegionInfo[] regions) throws IOException {
759    Set<byte[]> families = desc.getColumnFamilyNames();
760    Map<byte[], Set<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
761    for (byte[] family : families) {
762      familyMap.put(family, null);
763    }
764    requireNamespacePermission(c, "createTable", desc.getTableName().getNamespaceAsString(),
765      desc.getTableName(), familyMap, Action.ADMIN, Action.CREATE);
766  }
767
768  @Override
769  public void postCompletedCreateTableAction(final ObserverContext<MasterCoprocessorEnvironment> c,
770    final TableDescriptor desc, final RegionInfo[] regions) throws IOException {
771    // When AC is used, it should be configured as the 1st CP.
772    // In Master, the table operations like create, are handled by a Thread pool but the max size
773    // for this pool is 1. So if multiple CPs create tables on startup, these creations will happen
774    // sequentially only.
775    // Related code in HMaster#startServiceThreads
776    // {code}
777    // // We depend on there being only one instance of this executor running
778    // // at a time. To do concurrency, would need fencing of enable/disable of
779    // // tables.
780    // this.service.startExecutorService(ExecutorType.MASTER_TABLE_OPERATIONS, 1);
781    // {code}
782    // In future if we change this pool to have more threads, then there is a chance for thread,
783    // creating acl table, getting delayed and by that time another table creation got over and
784    // this hook is getting called. In such a case, we will need a wait logic here which will
785    // wait till the acl table is created.
786    if (PermissionStorage.isAclTable(desc)) {
787      this.aclTabAvailable = true;
788    } else {
789      if (!aclTabAvailable) {
790        LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". "
791          + PermissionStorage.ACL_TABLE_NAME + " is not yet created. " + getClass().getSimpleName()
792          + " should be configured as the first Coprocessor");
793      } else {
794        String owner = getActiveUser(c).getShortName();
795        final UserPermission userPermission = new UserPermission(owner,
796          Permission.newBuilder(desc.getTableName()).withActions(Action.values()).build());
797        // switch to the real hbase master user for doing the RPC on the ACL table
798        User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
799          @Override
800          public Void run() throws Exception {
801            try (Table table =
802              c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
803              PermissionStorage.addUserPermission(c.getEnvironment().getConfiguration(),
804                userPermission, table);
805            }
806            return null;
807          }
808        });
809      }
810    }
811  }
812
813  @Override
814  public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
815    throws IOException {
816    requirePermission(c, "deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
817  }
818
819  @Override
820  public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
821    final TableName tableName) throws IOException {
822    final Configuration conf = c.getEnvironment().getConfiguration();
823    User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
824      @Override
825      public Void run() throws Exception {
826        try (Table table =
827          c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
828          PermissionStorage.removeTablePermissions(conf, tableName, table);
829        }
830        return null;
831      }
832    });
833    zkPermissionWatcher.deleteTableACLNode(tableName);
834  }
835
836  @Override
837  public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
838    final TableName tableName) throws IOException {
839    requirePermission(c, "truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE);
840
841    final Configuration conf = c.getEnvironment().getConfiguration();
842    User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
843      @Override
844      public Void run() throws Exception {
845        List<UserPermission> acls =
846          PermissionStorage.getUserTablePermissions(conf, tableName, null, null, null, false);
847        if (acls != null) {
848          tableAcls.put(tableName, acls);
849        }
850        return null;
851      }
852    });
853  }
854
855  @Override
856  public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
857    final TableName tableName) throws IOException {
858    final Configuration conf = ctx.getEnvironment().getConfiguration();
859    User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
860      @Override
861      public Void run() throws Exception {
862        List<UserPermission> perms = tableAcls.get(tableName);
863        if (perms != null) {
864          for (UserPermission perm : perms) {
865            try (Table table =
866              ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
867              PermissionStorage.addUserPermission(conf, perm, table);
868            }
869          }
870        }
871        tableAcls.remove(tableName);
872        return null;
873      }
874    });
875  }
876
877  @Override
878  public void preTruncateRegion(final ObserverContext<MasterCoprocessorEnvironment> ctx,
879    final RegionInfo regionInfo) throws IOException {
880    requirePermission(ctx, "truncateRegion", regionInfo.getTable(), null, null, Action.ADMIN,
881      Action.CREATE);
882  }
883
884  @Override
885  public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
886    TableName tableName, TableDescriptor currentDesc, TableDescriptor newDesc) throws IOException {
887    // TODO: potentially check if this is a add/modify/delete column operation
888    requirePermission(c, "modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
889    return newDesc;
890  }
891
892  @Override
893  public String preModifyTableStoreFileTracker(ObserverContext<MasterCoprocessorEnvironment> c,
894    TableName tableName, String dstSFT) throws IOException {
895    requirePermission(c, "modifyTableStoreFileTracker", tableName, null, null, Action.ADMIN,
896      Action.CREATE);
897    return dstSFT;
898  }
899
900  @Override
901  public String preModifyColumnFamilyStoreFileTracker(
902    ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName, byte[] family,
903    String dstSFT) throws IOException {
904    requirePermission(c, "modifyColumnFamilyStoreFileTracker", tableName, family, null,
905      Action.ADMIN, Action.CREATE);
906    return dstSFT;
907  }
908
909  @Override
910  public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
911    TableDescriptor oldDesc, TableDescriptor currentDesc) throws IOException {
912    final Configuration conf = c.getEnvironment().getConfiguration();
913    // default the table owner to current user, if not specified.
914    final String owner = getActiveUser(c).getShortName();
915    User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
916      @Override
917      public Void run() throws Exception {
918        UserPermission userperm = new UserPermission(owner,
919          Permission.newBuilder(currentDesc.getTableName()).withActions(Action.values()).build());
920        try (Table table =
921          c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
922          PermissionStorage.addUserPermission(conf, userperm, table);
923        }
924        return null;
925      }
926    });
927  }
928
929  @Override
930  public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
931    throws IOException {
932    requirePermission(c, "enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
933  }
934
935  @Override
936  public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
937    throws IOException {
938    if (Bytes.equals(tableName.getName(), PermissionStorage.ACL_GLOBAL_NAME)) {
939      // We have to unconditionally disallow disable of the ACL table when we are installed,
940      // even if not enforcing authorizations. We are still allowing grants and revocations,
941      // checking permissions and logging audit messages, etc. If the ACL table is not
942      // available we will fail random actions all over the place.
943      throw new AccessDeniedException("Not allowed to disable " + PermissionStorage.ACL_TABLE_NAME
944        + " table with AccessController installed");
945    }
946    requirePermission(c, "disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
947  }
948
949  @Override
950  public void preAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx,
951    final long procId) throws IOException {
952    requirePermission(ctx, "abortProcedure", Action.ADMIN);
953  }
954
955  @Override
956  public void postAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx)
957    throws IOException {
958    // There is nothing to do at this time after the procedure abort request was sent.
959  }
960
961  @Override
962  public void preGetProcedures(ObserverContext<MasterCoprocessorEnvironment> ctx)
963    throws IOException {
964    requirePermission(ctx, "getProcedure", Action.ADMIN);
965  }
966
967  @Override
968  public void preGetLocks(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
969    User user = getActiveUser(ctx);
970    accessChecker.requirePermission(user, "getLocks", null, Action.ADMIN);
971  }
972
973  @Override
974  public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo region,
975    ServerName srcServer, ServerName destServer) throws IOException {
976    requirePermission(c, "move", region.getTable(), null, null, Action.ADMIN);
977  }
978
979  @Override
980  public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo regionInfo)
981    throws IOException {
982    requirePermission(c, "assign", regionInfo.getTable(), null, null, Action.ADMIN);
983  }
984
985  @Override
986  public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo regionInfo)
987    throws IOException {
988    requirePermission(c, "unassign", regionInfo.getTable(), null, null, Action.ADMIN);
989  }
990
991  @Override
992  public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
993    RegionInfo regionInfo) throws IOException {
994    requirePermission(c, "regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
995  }
996
997  @Override
998  public void preSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
999    final boolean newValue, final MasterSwitchType switchType) throws IOException {
1000    requirePermission(ctx, "setSplitOrMergeEnabled", Action.ADMIN);
1001  }
1002
1003  @Override
1004  public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c, BalanceRequest request)
1005    throws IOException {
1006    requirePermission(c, "balance", Action.ADMIN);
1007  }
1008
1009  @Override
1010  public void preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c, boolean newValue)
1011    throws IOException {
1012    requirePermission(c, "balanceSwitch", Action.ADMIN);
1013  }
1014
1015  @Override
1016  public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException {
1017    requirePermission(c, "shutdown", Action.ADMIN);
1018  }
1019
1020  @Override
1021  public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException {
1022    requirePermission(c, "stopMaster", Action.ADMIN);
1023  }
1024
1025  @Override
1026  public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
1027    throws IOException {
1028    try (Admin admin = ctx.getEnvironment().getConnection().getAdmin()) {
1029      if (!admin.tableExists(PermissionStorage.ACL_TABLE_NAME)) {
1030        createACLTable(admin);
1031      } else {
1032        this.aclTabAvailable = true;
1033      }
1034    }
1035  }
1036
1037  /**
1038   * Create the ACL table
1039   */
1040  private static void createACLTable(Admin admin) throws IOException {
1041    /** Table descriptor for ACL table */
1042    ColumnFamilyDescriptor cfd =
1043      ColumnFamilyDescriptorBuilder.newBuilder(PermissionStorage.ACL_LIST_FAMILY).setMaxVersions(1)
1044        .setInMemory(true).setBlockCacheEnabled(true).setBlocksize(8 * 1024)
1045        .setBloomFilterType(BloomType.NONE).setScope(HConstants.REPLICATION_SCOPE_LOCAL).build();
1046    TableDescriptor td = TableDescriptorBuilder.newBuilder(PermissionStorage.ACL_TABLE_NAME)
1047      .setColumnFamily(cfd).build();
1048    admin.createTable(td);
1049  }
1050
1051  @Override
1052  public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1053    final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException {
1054    // Move this ACL check to SnapshotManager#checkPermissions as part of AC deprecation.
1055    requirePermission(ctx, "snapshot " + snapshot.getName(), hTableDescriptor.getTableName(), null,
1056      null, Permission.Action.ADMIN);
1057  }
1058
1059  @Override
1060  public void preListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
1061    final SnapshotDescription snapshot) throws IOException {
1062    User user = getActiveUser(ctx);
1063    if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1064      // list it, if user is the owner of snapshot
1065      AuthResult result = AuthResult.allow("listSnapshot " + snapshot.getName(),
1066        "Snapshot owner check allowed", user, null, null, null);
1067      AccessChecker.logResult(result);
1068    } else {
1069      accessChecker.requirePermission(user, "listSnapshot " + snapshot.getName(), null,
1070        Action.ADMIN);
1071    }
1072  }
1073
1074  @Override
1075  public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1076    final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException {
1077    User user = getActiveUser(ctx);
1078    if (
1079      SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)
1080        && hTableDescriptor.getTableName().getNameAsString().equals(snapshot.getTableNameAsString())
1081    ) {
1082      // Snapshot owner is allowed to create a table with the same name as the snapshot he took
1083      AuthResult result = AuthResult.allow("cloneSnapshot " + snapshot.getName(),
1084        "Snapshot owner check allowed", user, null, hTableDescriptor.getTableName(), null);
1085      AccessChecker.logResult(result);
1086    } else if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1087      requireNamespacePermission(ctx, "cloneSnapshot",
1088        hTableDescriptor.getTableName().getNamespaceAsString(), Action.ADMIN);
1089    } else {
1090      accessChecker.requirePermission(user, "cloneSnapshot " + snapshot.getName(), null,
1091        Action.ADMIN);
1092    }
1093  }
1094
1095  @Override
1096  public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1097    final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException {
1098    User user = getActiveUser(ctx);
1099    if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1100      accessChecker.requirePermission(user, "restoreSnapshot " + snapshot.getName(),
1101        hTableDescriptor.getTableName(), null, null, null, Permission.Action.ADMIN);
1102    } else {
1103      accessChecker.requirePermission(user, "restoreSnapshot " + snapshot.getName(), null,
1104        Action.ADMIN);
1105    }
1106  }
1107
1108  @Override
1109  public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1110    final SnapshotDescription snapshot) throws IOException {
1111    User user = getActiveUser(ctx);
1112    if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1113      // Snapshot owner is allowed to delete the snapshot
1114      AuthResult result = AuthResult.allow("deleteSnapshot " + snapshot.getName(),
1115        "Snapshot owner check allowed", user, null, null, null);
1116      AccessChecker.logResult(result);
1117    } else {
1118      accessChecker.requirePermission(user, "deleteSnapshot " + snapshot.getName(), null,
1119        Action.ADMIN);
1120    }
1121  }
1122
1123  @Override
1124  public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1125    NamespaceDescriptor ns) throws IOException {
1126    requireGlobalPermission(ctx, "createNamespace", Action.ADMIN, ns.getName());
1127  }
1128
1129  @Override
1130  public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1131    String namespace) throws IOException {
1132    requireGlobalPermission(ctx, "deleteNamespace", Action.ADMIN, namespace);
1133  }
1134
1135  @Override
1136  public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1137    final String namespace) throws IOException {
1138    final Configuration conf = ctx.getEnvironment().getConfiguration();
1139    User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1140      @Override
1141      public Void run() throws Exception {
1142        try (Table table =
1143          ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
1144          PermissionStorage.removeNamespacePermissions(conf, namespace, table);
1145        }
1146        return null;
1147      }
1148    });
1149    zkPermissionWatcher.deleteNamespaceACLNode(namespace);
1150    LOG.info(namespace + " entry deleted in " + PermissionStorage.ACL_TABLE_NAME + " table.");
1151  }
1152
1153  @Override
1154  public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1155    NamespaceDescriptor currentNsDesc, NamespaceDescriptor newNsDesc) throws IOException {
1156    // We require only global permission so that
1157    // a user with NS admin cannot altering namespace configurations. i.e. namespace quota
1158    requireGlobalPermission(ctx, "modifyNamespace", Action.ADMIN, newNsDesc.getName());
1159  }
1160
1161  @Override
1162  public void preGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx,
1163    String namespace) throws IOException {
1164    requireNamespacePermission(ctx, "getNamespaceDescriptor", namespace, Action.ADMIN);
1165  }
1166
1167  @Override
1168  public void postListNamespaces(ObserverContext<MasterCoprocessorEnvironment> ctx,
1169    List<String> namespaces) throws IOException {
1170    /* always allow namespace listing */
1171  }
1172
1173  @Override
1174  public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1175    List<NamespaceDescriptor> descriptors) throws IOException {
1176    // Retains only those which passes authorization checks, as the checks weren't done as part
1177    // of preGetTableDescriptors.
1178    Iterator<NamespaceDescriptor> itr = descriptors.iterator();
1179    User user = getActiveUser(ctx);
1180    while (itr.hasNext()) {
1181      NamespaceDescriptor desc = itr.next();
1182      try {
1183        accessChecker.requireNamespacePermission(user, "listNamespaces", desc.getName(), null,
1184          Action.ADMIN);
1185      } catch (AccessDeniedException e) {
1186        itr.remove();
1187      }
1188    }
1189  }
1190
1191  @Override
1192  public void preTableFlush(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1193    final TableName tableName) throws IOException {
1194    // Move this ACL check to MasterFlushTableProcedureManager#checkPermissions as part of AC
1195    // deprecation.
1196    requirePermission(ctx, "flushTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1197  }
1198
1199  @Override
1200  public void preSplitRegion(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1201    final TableName tableName, final byte[] splitRow) throws IOException {
1202    requirePermission(ctx, "split", tableName, null, null, Action.ADMIN);
1203  }
1204
1205  @Override
1206  public void preClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx)
1207    throws IOException {
1208    requirePermission(ctx, "clearDeadServers", Action.ADMIN);
1209  }
1210
1211  @Override
1212  public void preDecommissionRegionServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
1213    List<ServerName> servers, boolean offload) throws IOException {
1214    requirePermission(ctx, "decommissionRegionServers", Action.ADMIN);
1215  }
1216
1217  @Override
1218  public void preListDecommissionedRegionServers(ObserverContext<MasterCoprocessorEnvironment> ctx)
1219    throws IOException {
1220    requirePermission(ctx, "listDecommissionedRegionServers", Action.READ);
1221  }
1222
1223  @Override
1224  public void preRecommissionRegionServer(ObserverContext<MasterCoprocessorEnvironment> ctx,
1225    ServerName server, List<byte[]> encodedRegionNames) throws IOException {
1226    requirePermission(ctx, "recommissionRegionServers", Action.ADMIN);
1227  }
1228
1229  /* ---- RegionObserver implementation ---- */
1230
1231  @Override
1232  public void preOpen(ObserverContext<? extends RegionCoprocessorEnvironment> c)
1233    throws IOException {
1234    RegionCoprocessorEnvironment env = c.getEnvironment();
1235    final Region region = env.getRegion();
1236    if (region == null) {
1237      LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1238    } else {
1239      RegionInfo regionInfo = region.getRegionInfo();
1240      if (regionInfo.getTable().isSystemTable()) {
1241        checkSystemOrSuperUser(getActiveUser(c));
1242      } else {
1243        requirePermission(c, "preOpen", Action.ADMIN);
1244      }
1245    }
1246  }
1247
1248  @Override
1249  public void postOpen(ObserverContext<? extends RegionCoprocessorEnvironment> c) {
1250    RegionCoprocessorEnvironment env = c.getEnvironment();
1251    final Region region = env.getRegion();
1252    if (region == null) {
1253      LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1254      return;
1255    }
1256    if (PermissionStorage.isAclRegion(region)) {
1257      aclRegion = true;
1258      try {
1259        initialize(env);
1260      } catch (IOException ex) {
1261        // if we can't obtain permissions, it's better to fail
1262        // than perform checks incorrectly
1263        throw new RuntimeException("Failed to initialize permissions cache", ex);
1264      }
1265    } else {
1266      initialized = true;
1267    }
1268  }
1269
1270  @Override
1271  public void preFlush(ObserverContext<? extends RegionCoprocessorEnvironment> c,
1272    FlushLifeCycleTracker tracker) throws IOException {
1273    requirePermission(c, "flush", getTableName(c.getEnvironment()), null, null, Action.ADMIN,
1274      Action.CREATE);
1275  }
1276
1277  @Override
1278  public InternalScanner preCompact(ObserverContext<? extends RegionCoprocessorEnvironment> c,
1279    Store store, InternalScanner scanner, ScanType scanType, CompactionLifeCycleTracker tracker,
1280    CompactionRequest request) throws IOException {
1281    requirePermission(c, "compact", getTableName(c.getEnvironment()), null, null, Action.ADMIN,
1282      Action.CREATE);
1283    return scanner;
1284  }
1285
1286  private void internalPreRead(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1287    final Query query, OpType opType) throws IOException {
1288    Filter filter = query.getFilter();
1289    // Don't wrap an AccessControlFilter
1290    if (filter != null && filter instanceof AccessControlFilter) {
1291      return;
1292    }
1293    User user = getActiveUser(c);
1294    RegionCoprocessorEnvironment env = c.getEnvironment();
1295    Map<byte[], ? extends Collection<byte[]>> families = null;
1296    switch (opType) {
1297      case GET:
1298      case EXISTS:
1299        families = ((Get) query).getFamilyMap();
1300        break;
1301      case SCAN:
1302        families = ((Scan) query).getFamilyMap();
1303        break;
1304      default:
1305        throw new RuntimeException("Unhandled operation " + opType);
1306    }
1307    AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ);
1308    Region region = getRegion(env);
1309    TableName table = getTableName(region);
1310    Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap();
1311    for (ColumnFamilyDescriptor hcd : region.getTableDescriptor().getColumnFamilies()) {
1312      cfVsMaxVersions.put(new SimpleMutableByteRange(hcd.getName()), hcd.getMaxVersions());
1313    }
1314    if (!authResult.isAllowed()) {
1315      if (!cellFeaturesEnabled || compatibleEarlyTermination) {
1316        // Old behavior: Scan with only qualifier checks if we have partial
1317        // permission. Backwards compatible behavior is to throw an
1318        // AccessDeniedException immediately if there are no grants for table
1319        // or CF or CF+qual. Only proceed with an injected filter if there are
1320        // grants for qualifiers. Otherwise we will fall through below and log
1321        // the result and throw an ADE. We may end up checking qualifier
1322        // grants three times (permissionGranted above, here, and in the
1323        // filter) but that's the price of backwards compatibility.
1324        if (hasFamilyQualifierPermission(user, Action.READ, env, families)) {
1325          authResult.setAllowed(true);
1326          authResult.setReason("Access allowed with filter");
1327          // Only wrap the filter if we are enforcing authorizations
1328          if (authorizationEnabled) {
1329            Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table,
1330              AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY, cfVsMaxVersions);
1331            // wrap any existing filter
1332            if (filter != null) {
1333              ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1334                Lists.newArrayList(ourFilter, filter));
1335            }
1336            switch (opType) {
1337              case GET:
1338              case EXISTS:
1339                ((Get) query).setFilter(ourFilter);
1340                break;
1341              case SCAN:
1342                ((Scan) query).setFilter(ourFilter);
1343                break;
1344              default:
1345                throw new RuntimeException("Unhandled operation " + opType);
1346            }
1347          }
1348        }
1349      } else {
1350        // New behavior: Any access we might be granted is more fine-grained
1351        // than whole table or CF. Simply inject a filter and return what is
1352        // allowed. We will not throw an AccessDeniedException. This is a
1353        // behavioral change since 0.96.
1354        authResult.setAllowed(true);
1355        authResult.setReason("Access allowed with filter");
1356        // Only wrap the filter if we are enforcing authorizations
1357        if (authorizationEnabled) {
1358          Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table,
1359            AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions);
1360          // wrap any existing filter
1361          if (filter != null) {
1362            ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1363              Lists.newArrayList(ourFilter, filter));
1364          }
1365          switch (opType) {
1366            case GET:
1367            case EXISTS:
1368              ((Get) query).setFilter(ourFilter);
1369              break;
1370            case SCAN:
1371              ((Scan) query).setFilter(ourFilter);
1372              break;
1373            default:
1374              throw new RuntimeException("Unhandled operation " + opType);
1375          }
1376        }
1377      }
1378    }
1379
1380    AccessChecker.logResult(authResult);
1381    if (authorizationEnabled && !authResult.isAllowed()) {
1382      throw new AccessDeniedException("Insufficient permissions for user '"
1383        + (user != null ? user.getShortName() : "null") + "' (table=" + table + ", action=READ)");
1384    }
1385  }
1386
1387  @Override
1388  public void preGetOp(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1389    final Get get, final List<Cell> result) throws IOException {
1390    internalPreRead(c, get, OpType.GET);
1391  }
1392
1393  @Override
1394  public boolean preExists(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1395    final Get get, final boolean exists) throws IOException {
1396    internalPreRead(c, get, OpType.EXISTS);
1397    return exists;
1398  }
1399
1400  @Override
1401  public void prePut(final ObserverContext<? extends RegionCoprocessorEnvironment> c, final Put put,
1402    final WALEdit edit, final Durability durability) throws IOException {
1403    User user = getActiveUser(c);
1404    checkForReservedTagPresence(user, put);
1405
1406    // Require WRITE permission to the table, CF, or top visible value, if any.
1407    // NOTE: We don't need to check the permissions for any earlier Puts
1408    // because we treat the ACLs in each Put as timestamped like any other
1409    // HBase value. A new ACL in a new Put applies to that Put. It doesn't
1410    // change the ACL of any previous Put. This allows simple evolution of
1411    // security policy over time without requiring expensive updates.
1412    RegionCoprocessorEnvironment env = c.getEnvironment();
1413    Map<byte[], ? extends Collection<Cell>> families = put.getFamilyCellMap();
1414    AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE);
1415    AccessChecker.logResult(authResult);
1416    if (!authResult.isAllowed()) {
1417      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1418        put.setAttribute(CHECK_COVERING_PERM, TRUE);
1419      } else if (authorizationEnabled) {
1420        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1421      }
1422    }
1423
1424    // Add cell ACLs from the operation to the cells themselves
1425    byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1426    if (bytes != null) {
1427      if (cellFeaturesEnabled) {
1428        addCellPermissions(bytes, put.getFamilyCellMap());
1429      } else {
1430        throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1431      }
1432    }
1433  }
1434
1435  @Override
1436  public void postPut(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1437    final Put put, final WALEdit edit, final Durability durability) {
1438    if (aclRegion) {
1439      updateACL(c.getEnvironment(), put.getFamilyCellMap());
1440    }
1441  }
1442
1443  @Override
1444  public void preDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1445    final Delete delete, final WALEdit edit, final Durability durability) throws IOException {
1446    // An ACL on a delete is useless, we shouldn't allow it
1447    if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1448      throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1449    }
1450    // Require WRITE permissions on all cells covered by the delete. Unlike
1451    // for Puts we need to check all visible prior versions, because a major
1452    // compaction could remove them. If the user doesn't have permission to
1453    // overwrite any of the visible versions ('visible' defined as not covered
1454    // by a tombstone already) then we have to disallow this operation.
1455    RegionCoprocessorEnvironment env = c.getEnvironment();
1456    Map<byte[], ? extends Collection<Cell>> families = delete.getFamilyCellMap();
1457    User user = getActiveUser(c);
1458    AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE);
1459    AccessChecker.logResult(authResult);
1460    if (!authResult.isAllowed()) {
1461      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1462        delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1463      } else if (authorizationEnabled) {
1464        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1465      }
1466    }
1467  }
1468
1469  @Override
1470  public void preBatchMutate(ObserverContext<? extends RegionCoprocessorEnvironment> c,
1471    MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1472    if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1473      TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1474      User user = getActiveUser(c);
1475      for (int i = 0; i < miniBatchOp.size(); i++) {
1476        Mutation m = miniBatchOp.getOperation(i);
1477        if (m.getAttribute(CHECK_COVERING_PERM) != null) {
1478          // We have a failure with table, cf and q perm checks and now giving a chance for cell
1479          // perm check
1480          OpType opType;
1481          long timestamp;
1482          if (m instanceof Put) {
1483            checkForReservedTagPresence(user, m);
1484            opType = OpType.PUT;
1485            timestamp = m.getTimestamp();
1486          } else if (m instanceof Delete) {
1487            opType = OpType.DELETE;
1488            timestamp = m.getTimestamp();
1489          } else if (m instanceof Increment) {
1490            opType = OpType.INCREMENT;
1491            timestamp = ((Increment) m).getTimeRange().getMax();
1492          } else if (m instanceof Append) {
1493            opType = OpType.APPEND;
1494            timestamp = ((Append) m).getTimeRange().getMax();
1495          } else {
1496            // If the operation type is not Put/Delete/Increment/Append, do nothing
1497            continue;
1498          }
1499          AuthResult authResult = null;
1500          if (
1501            checkCoveringPermission(user, opType, c.getEnvironment(), m.getRow(),
1502              m.getFamilyCellMap(), timestamp, Action.WRITE)
1503          ) {
1504            authResult = AuthResult.allow(opType.toString(), "Covering cell set", user,
1505              Action.WRITE, table, m.getFamilyCellMap());
1506          } else {
1507            authResult = AuthResult.deny(opType.toString(), "Covering cell set", user, Action.WRITE,
1508              table, m.getFamilyCellMap());
1509          }
1510          AccessChecker.logResult(authResult);
1511          if (authorizationEnabled && !authResult.isAllowed()) {
1512            throw new AccessDeniedException(
1513              "Insufficient permissions " + authResult.toContextString());
1514          }
1515        }
1516      }
1517    }
1518  }
1519
1520  @Override
1521  public void postDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1522    final Delete delete, final WALEdit edit, final Durability durability) throws IOException {
1523    if (aclRegion) {
1524      updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1525    }
1526  }
1527
1528  @Override
1529  public boolean preCheckAndPut(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1530    final byte[] row, final byte[] family, final byte[] qualifier, final CompareOperator op,
1531    final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException {
1532    User user = getActiveUser(c);
1533    checkForReservedTagPresence(user, put);
1534
1535    // Require READ and WRITE permissions on the table, CF, and KV to update
1536    RegionCoprocessorEnvironment env = c.getEnvironment();
1537    Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1538    AuthResult authResult =
1539      permissionGranted(OpType.CHECK_AND_PUT, user, env, families, Action.READ, Action.WRITE);
1540    AccessChecker.logResult(authResult);
1541    if (!authResult.isAllowed()) {
1542      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1543        put.setAttribute(CHECK_COVERING_PERM, TRUE);
1544      } else if (authorizationEnabled) {
1545        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1546      }
1547    }
1548
1549    byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1550    if (bytes != null) {
1551      if (cellFeaturesEnabled) {
1552        addCellPermissions(bytes, put.getFamilyCellMap());
1553      } else {
1554        throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1555      }
1556    }
1557    return result;
1558  }
1559
1560  @Override
1561  public boolean preCheckAndPutAfterRowLock(
1562    final ObserverContext<? extends RegionCoprocessorEnvironment> c, final byte[] row,
1563    final byte[] family, final byte[] qualifier, final CompareOperator opp,
1564    final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException {
1565    if (put.getAttribute(CHECK_COVERING_PERM) != null) {
1566      // We had failure with table, cf and q perm checks and now giving a chance for cell
1567      // perm check
1568      TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1569      Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1570      AuthResult authResult = null;
1571      User user = getActiveUser(c);
1572      if (
1573        checkCoveringPermission(user, OpType.CHECK_AND_PUT, c.getEnvironment(), row, families,
1574          HConstants.LATEST_TIMESTAMP, Action.READ)
1575      ) {
1576        authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set", user,
1577          Action.READ, table, families);
1578      } else {
1579        authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set", user,
1580          Action.READ, table, families);
1581      }
1582      AccessChecker.logResult(authResult);
1583      if (authorizationEnabled && !authResult.isAllowed()) {
1584        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1585      }
1586    }
1587    return result;
1588  }
1589
1590  @Override
1591  public boolean preCheckAndDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1592    final byte[] row, final byte[] family, final byte[] qualifier, final CompareOperator op,
1593    final ByteArrayComparable comparator, final Delete delete, final boolean result)
1594    throws IOException {
1595    // An ACL on a delete is useless, we shouldn't allow it
1596    if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1597      throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " + delete.toString());
1598    }
1599    // Require READ and WRITE permissions on the table, CF, and the KV covered
1600    // by the delete
1601    RegionCoprocessorEnvironment env = c.getEnvironment();
1602    Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1603    User user = getActiveUser(c);
1604    AuthResult authResult =
1605      permissionGranted(OpType.CHECK_AND_DELETE, user, env, families, Action.READ, Action.WRITE);
1606    AccessChecker.logResult(authResult);
1607    if (!authResult.isAllowed()) {
1608      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1609        delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1610      } else if (authorizationEnabled) {
1611        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1612      }
1613    }
1614    return result;
1615  }
1616
1617  @Override
1618  public boolean preCheckAndDeleteAfterRowLock(
1619    final ObserverContext<? extends RegionCoprocessorEnvironment> c, final byte[] row,
1620    final byte[] family, final byte[] qualifier, final CompareOperator op,
1621    final ByteArrayComparable comparator, final Delete delete, final boolean result)
1622    throws IOException {
1623    if (delete.getAttribute(CHECK_COVERING_PERM) != null) {
1624      // We had failure with table, cf and q perm checks and now giving a chance for cell
1625      // perm check
1626      TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1627      Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1628      AuthResult authResult = null;
1629      User user = getActiveUser(c);
1630      if (
1631        checkCoveringPermission(user, OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families,
1632          HConstants.LATEST_TIMESTAMP, Action.READ)
1633      ) {
1634        authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set", user,
1635          Action.READ, table, families);
1636      } else {
1637        authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set", user,
1638          Action.READ, table, families);
1639      }
1640      AccessChecker.logResult(authResult);
1641      if (authorizationEnabled && !authResult.isAllowed()) {
1642        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1643      }
1644    }
1645    return result;
1646  }
1647
1648  @Override
1649  public Result preAppend(ObserverContext<? extends RegionCoprocessorEnvironment> c, Append append)
1650    throws IOException {
1651    User user = getActiveUser(c);
1652    checkForReservedTagPresence(user, append);
1653
1654    // Require WRITE permission to the table, CF, and the KV to be appended
1655    RegionCoprocessorEnvironment env = c.getEnvironment();
1656    Map<byte[], ? extends Collection<Cell>> families = append.getFamilyCellMap();
1657    AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE);
1658    AccessChecker.logResult(authResult);
1659    if (!authResult.isAllowed()) {
1660      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1661        append.setAttribute(CHECK_COVERING_PERM, TRUE);
1662      } else if (authorizationEnabled) {
1663        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1664      }
1665    }
1666
1667    byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1668    if (bytes != null) {
1669      if (cellFeaturesEnabled) {
1670        addCellPermissions(bytes, append.getFamilyCellMap());
1671      } else {
1672        throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1673      }
1674    }
1675
1676    return null;
1677  }
1678
1679  @Override
1680  public Result preIncrement(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1681    final Increment increment) throws IOException {
1682    User user = getActiveUser(c);
1683    checkForReservedTagPresence(user, increment);
1684
1685    // Require WRITE permission to the table, CF, and the KV to be replaced by
1686    // the incremented value
1687    RegionCoprocessorEnvironment env = c.getEnvironment();
1688    Map<byte[], ? extends Collection<Cell>> families = increment.getFamilyCellMap();
1689    AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families, Action.WRITE);
1690    AccessChecker.logResult(authResult);
1691    if (!authResult.isAllowed()) {
1692      if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1693        increment.setAttribute(CHECK_COVERING_PERM, TRUE);
1694      } else if (authorizationEnabled) {
1695        throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1696      }
1697    }
1698
1699    byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1700    if (bytes != null) {
1701      if (cellFeaturesEnabled) {
1702        addCellPermissions(bytes, increment.getFamilyCellMap());
1703      } else {
1704        throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1705      }
1706    }
1707
1708    return null;
1709  }
1710
1711  @Override
1712  public List<Pair<Cell, Cell>> postIncrementBeforeWAL(
1713    ObserverContext<? extends RegionCoprocessorEnvironment> ctx, Mutation mutation,
1714    List<Pair<Cell, Cell>> cellPairs) throws IOException {
1715    // If the HFile version is insufficient to persist tags, we won't have any
1716    // work to do here
1717    if (!cellFeaturesEnabled || mutation.getACL() == null) {
1718      return cellPairs;
1719    }
1720    return cellPairs.stream()
1721      .map(pair -> new Pair<>(pair.getFirst(),
1722        createNewCellWithTags(mutation, pair.getFirst(), pair.getSecond())))
1723      .collect(Collectors.toList());
1724  }
1725
1726  @Override
1727  public List<Pair<Cell, Cell>> postAppendBeforeWAL(
1728    ObserverContext<? extends RegionCoprocessorEnvironment> ctx, Mutation mutation,
1729    List<Pair<Cell, Cell>> cellPairs) throws IOException {
1730    // If the HFile version is insufficient to persist tags, we won't have any
1731    // work to do here
1732    if (!cellFeaturesEnabled || mutation.getACL() == null) {
1733      return cellPairs;
1734    }
1735    return cellPairs.stream()
1736      .map(pair -> new Pair<>(pair.getFirst(),
1737        createNewCellWithTags(mutation, pair.getFirst(), pair.getSecond())))
1738      .collect(Collectors.toList());
1739  }
1740
1741  private Cell createNewCellWithTags(Mutation mutation, Cell oldCell, Cell newCell) {
1742    // As Increment and Append operations have already copied the tags of oldCell to the newCell,
1743    // there is no need to rewrite them again. Just extract non-acl tags of newCell if we need to
1744    // add a new acl tag for the cell. Actually, oldCell is useless here.
1745    List<Tag> tags = Lists.newArrayList();
1746    ExtendedCell newExtendedCell = (ExtendedCell) newCell;
1747    if (newExtendedCell != null) {
1748      Iterator<Tag> tagIterator = PrivateCellUtil.tagsIterator(newExtendedCell);
1749      while (tagIterator.hasNext()) {
1750        Tag tag = tagIterator.next();
1751        if (tag.getType() != PermissionStorage.ACL_TAG_TYPE) {
1752          // Not an ACL tag, just carry it through
1753          if (LOG.isTraceEnabled()) {
1754            LOG.trace("Carrying forward tag from " + newCell + ": type " + tag.getType()
1755              + " length " + tag.getValueLength());
1756          }
1757          tags.add(tag);
1758        }
1759      }
1760    }
1761
1762    // We have checked the ACL tag of mutation is not null.
1763    // So that the tags could not be empty.
1764    tags.add(new ArrayBackedTag(PermissionStorage.ACL_TAG_TYPE, mutation.getACL()));
1765    return PrivateCellUtil.createCell(newExtendedCell, tags);
1766  }
1767
1768  @Override
1769  public void preScannerOpen(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1770    final Scan scan) throws IOException {
1771    internalPreRead(c, scan, OpType.SCAN);
1772  }
1773
1774  @Override
1775  public RegionScanner postScannerOpen(
1776    final ObserverContext<? extends RegionCoprocessorEnvironment> c, final Scan scan,
1777    final RegionScanner s) throws IOException {
1778    User user = getActiveUser(c);
1779    if (user != null && user.getShortName() != null) {
1780      // store reference to scanner owner for later checks
1781      scannerOwners.put(s, user.getShortName());
1782    }
1783    return s;
1784  }
1785
1786  @Override
1787  public boolean preScannerNext(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1788    final InternalScanner s, final List<Result> result, final int limit, final boolean hasNext)
1789    throws IOException {
1790    requireScannerOwner(s);
1791    return hasNext;
1792  }
1793
1794  @Override
1795  public void preScannerClose(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1796    final InternalScanner s) throws IOException {
1797    requireScannerOwner(s);
1798  }
1799
1800  @Override
1801  public void postScannerClose(final ObserverContext<? extends RegionCoprocessorEnvironment> c,
1802    final InternalScanner s) throws IOException {
1803    // clean up any associated owner mapping
1804    scannerOwners.remove(s);
1805  }
1806
1807  /**
1808   * Verify, when servicing an RPC, that the caller is the scanner owner. If so, we assume that
1809   * access control is correctly enforced based on the checks performed in preScannerOpen()
1810   */
1811  private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
1812    if (!RpcServer.isInRpcCallContext()) {
1813      return;
1814    }
1815    String requestUserName = RpcServer.getRequestUserName().orElse(null);
1816    String owner = scannerOwners.get(s);
1817    if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) {
1818      throw new AccessDeniedException("User '" + requestUserName + "' is not the scanner owner!");
1819    }
1820  }
1821
1822  /**
1823   * Verifies user has CREATE or ADMIN privileges on the Column Families involved in the
1824   * bulkLoadHFile request. Specific Column Write privileges are presently ignored.
1825   */
1826  @Override
1827  public void preBulkLoadHFile(ObserverContext<? extends RegionCoprocessorEnvironment> ctx,
1828    List<Pair<byte[], String>> familyPaths) throws IOException {
1829    User user = getActiveUser(ctx);
1830    for (Pair<byte[], String> el : familyPaths) {
1831      accessChecker.requirePermission(user, "preBulkLoadHFile",
1832        ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), el.getFirst(), null,
1833        null, Action.ADMIN, Action.CREATE);
1834    }
1835  }
1836
1837  /**
1838   * Authorization check for SecureBulkLoadProtocol.prepareBulkLoad()
1839   * @param ctx the context
1840   */
1841  @Override
1842  public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx)
1843    throws IOException {
1844    requireAccess(ctx, "prePrepareBulkLoad",
1845      ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), Action.ADMIN,
1846      Action.CREATE);
1847  }
1848
1849  /**
1850   * Authorization security check for SecureBulkLoadProtocol.cleanupBulkLoad()
1851   * @param ctx the context
1852   */
1853  @Override
1854  public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx)
1855    throws IOException {
1856    requireAccess(ctx, "preCleanupBulkLoad",
1857      ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), Action.ADMIN,
1858      Action.CREATE);
1859  }
1860
1861  /* ---- EndpointObserver implementation ---- */
1862
1863  @Override
1864  public Message preEndpointInvocation(ObserverContext<? extends RegionCoprocessorEnvironment> ctx,
1865    Service service, String methodName, Message request) throws IOException {
1866    // Don't intercept calls to our own AccessControlService, we check for
1867    // appropriate permissions in the service handlers
1868    if (shouldCheckExecPermission && !(service instanceof AccessControlService)) {
1869      requirePermission(ctx,
1870        "invoke(" + service.getDescriptorForType().getName() + "." + methodName + ")",
1871        getTableName(ctx.getEnvironment()), null, null, Action.EXEC);
1872    }
1873    return request;
1874  }
1875
1876  @Override
1877  public void postEndpointInvocation(ObserverContext<? extends RegionCoprocessorEnvironment> ctx,
1878    Service service, String methodName, Message request, Message.Builder responseBuilder)
1879    throws IOException {
1880  }
1881
1882  /* ---- Protobuf AccessControlService implementation ---- */
1883
1884  /**
1885   * @deprecated since 2.2.0 and will be removed in 4.0.0. Use
1886   *             {@link Admin#grant(UserPermission, boolean)} instead.
1887   * @see Admin#grant(UserPermission, boolean)
1888   * @see <a href="https://issues.apache.org/jira/browse/HBASE-21739">HBASE-21739</a>
1889   */
1890  @Deprecated
1891  @Override
1892  public void grant(RpcController controller, AccessControlProtos.GrantRequest request,
1893    RpcCallback<AccessControlProtos.GrantResponse> done) {
1894    final UserPermission perm = AccessControlUtil.toUserPermission(request.getUserPermission());
1895    AccessControlProtos.GrantResponse response = null;
1896    try {
1897      // verify it's only running at .acl.
1898      if (aclRegion) {
1899        if (!initialized) {
1900          throw new CoprocessorException("AccessController not yet initialized");
1901        }
1902        User caller = RpcServer.getRequestUser().orElse(null);
1903        if (LOG.isDebugEnabled()) {
1904          LOG.debug("Received request from {} to grant access permission {}", caller.getName(),
1905            perm.toString());
1906        }
1907        preGrantOrRevoke(caller, "grant", perm);
1908
1909        // regionEnv is set at #start. Hopefully not null at this point.
1910        regionEnv.getConnection().getAdmin().grant(
1911          new UserPermission(perm.getUser(), perm.getPermission()),
1912          request.getMergeExistingPermissions());
1913        if (AUDITLOG.isTraceEnabled()) {
1914          // audit log should store permission changes in addition to auth results
1915          AUDITLOG.trace("Granted permission " + perm.toString());
1916        }
1917      } else {
1918        throw new CoprocessorException(AccessController.class,
1919          "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table.");
1920      }
1921      response = AccessControlProtos.GrantResponse.getDefaultInstance();
1922    } catch (IOException ioe) {
1923      // pass exception back up
1924      CoprocessorRpcUtils.setControllerException(controller, ioe);
1925    }
1926    done.run(response);
1927  }
1928
1929  /**
1930   * @deprecated since 2.2.0 and will be removed in 4.0.0. Use {@link Admin#revoke(UserPermission)}
1931   *             instead.
1932   * @see Admin#revoke(UserPermission)
1933   * @see <a href="https://issues.apache.org/jira/browse/HBASE-21739">HBASE-21739</a>
1934   */
1935  @Deprecated
1936  @Override
1937  public void revoke(RpcController controller, AccessControlProtos.RevokeRequest request,
1938    RpcCallback<AccessControlProtos.RevokeResponse> done) {
1939    final UserPermission perm = AccessControlUtil.toUserPermission(request.getUserPermission());
1940    AccessControlProtos.RevokeResponse response = null;
1941    try {
1942      // only allowed to be called on _acl_ region
1943      if (aclRegion) {
1944        if (!initialized) {
1945          throw new CoprocessorException("AccessController not yet initialized");
1946        }
1947        User caller = RpcServer.getRequestUser().orElse(null);
1948        if (LOG.isDebugEnabled()) {
1949          LOG.debug("Received request from {} to revoke access permission {}",
1950            caller.getShortName(), perm.toString());
1951        }
1952        preGrantOrRevoke(caller, "revoke", perm);
1953        // regionEnv is set at #start. Hopefully not null here.
1954        regionEnv.getConnection().getAdmin()
1955          .revoke(new UserPermission(perm.getUser(), perm.getPermission()));
1956        if (AUDITLOG.isTraceEnabled()) {
1957          // audit log should record all permission changes
1958          AUDITLOG.trace("Revoked permission " + perm.toString());
1959        }
1960      } else {
1961        throw new CoprocessorException(AccessController.class,
1962          "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table.");
1963      }
1964      response = AccessControlProtos.RevokeResponse.getDefaultInstance();
1965    } catch (IOException ioe) {
1966      // pass exception back up
1967      CoprocessorRpcUtils.setControllerException(controller, ioe);
1968    }
1969    done.run(response);
1970  }
1971
1972  /**
1973   * @deprecated since 2.2.0 and will be removed in 4.0.0. Use
1974   *             {@link Admin#getUserPermissions(GetUserPermissionsRequest)} instead.
1975   * @see Admin#getUserPermissions(GetUserPermissionsRequest)
1976   * @see <a href="https://issues.apache.org/jira/browse/HBASE-21911">HBASE-21911</a>
1977   */
1978  @Deprecated
1979  @Override
1980  public void getUserPermissions(RpcController controller,
1981    AccessControlProtos.GetUserPermissionsRequest request,
1982    RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
1983    AccessControlProtos.GetUserPermissionsResponse response = null;
1984    try {
1985      // only allowed to be called on _acl_ region
1986      if (aclRegion) {
1987        if (!initialized) {
1988          throw new CoprocessorException("AccessController not yet initialized");
1989        }
1990        User caller = RpcServer.getRequestUser().orElse(null);
1991        final String userName = request.hasUserName() ? request.getUserName().toStringUtf8() : null;
1992        final String namespace =
1993          request.hasNamespaceName() ? request.getNamespaceName().toStringUtf8() : null;
1994        final TableName table =
1995          request.hasTableName() ? ProtobufUtil.toTableName(request.getTableName()) : null;
1996        final byte[] cf =
1997          request.hasColumnFamily() ? request.getColumnFamily().toByteArray() : null;
1998        final byte[] cq =
1999          request.hasColumnQualifier() ? request.getColumnQualifier().toByteArray() : null;
2000        AccessControlProtos.Permission.Type protoType =
2001          request.hasType() ? request.getType() : null;
2002        Permission.Scope permissionScope = ShadedAccessControlUtil.toPermissionScope(protoType);
2003        preGetUserPermissions(caller, userName, namespace, table, cf, cq, permissionScope);
2004        GetUserPermissionsRequest getUserPermissionsRequest = null;
2005        if (request.getType() == AccessControlProtos.Permission.Type.Table) {
2006          getUserPermissionsRequest = GetUserPermissionsRequest.newBuilder(table).withFamily(cf)
2007            .withQualifier(cq).withUserName(userName).build();
2008        } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
2009          getUserPermissionsRequest =
2010            GetUserPermissionsRequest.newBuilder(namespace).withUserName(userName).build();
2011        } else {
2012          getUserPermissionsRequest =
2013            GetUserPermissionsRequest.newBuilder().withUserName(userName).build();
2014        }
2015        List<UserPermission> perms =
2016          regionEnv.getConnection().getAdmin().getUserPermissions(getUserPermissionsRequest);
2017        response = AccessControlUtil.buildGetUserPermissionsResponse(perms);
2018      } else {
2019        throw new CoprocessorException(AccessController.class,
2020          "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table.");
2021      }
2022    } catch (IOException ioe) {
2023      // pass exception back up
2024      CoprocessorRpcUtils.setControllerException(controller, ioe);
2025    }
2026    done.run(response);
2027  }
2028
2029  /**
2030   * @deprecated since 2.2.0 and will be removed 4.0.0. Use {@link Admin#hasUserPermissions(List)}
2031   *             instead.
2032   * @see Admin#hasUserPermissions(List)
2033   * @see <a href="https://issues.apache.org/jira/browse/HBASE-22117">HBASE-22117</a>
2034   */
2035  @Deprecated
2036  @Override
2037  public void checkPermissions(RpcController controller,
2038    AccessControlProtos.CheckPermissionsRequest request,
2039    RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
2040    AccessControlProtos.CheckPermissionsResponse response = null;
2041    try {
2042      User user = RpcServer.getRequestUser().orElse(null);
2043      TableName tableName = regionEnv.getRegion().getTableDescriptor().getTableName();
2044      List<Permission> permissions = new ArrayList<>();
2045      for (int i = 0; i < request.getPermissionCount(); i++) {
2046        Permission permission = AccessControlUtil.toPermission(request.getPermission(i));
2047        permissions.add(permission);
2048        if (permission instanceof TablePermission) {
2049          TablePermission tperm = (TablePermission) permission;
2050          if (!tperm.getTableName().equals(tableName)) {
2051            throw new CoprocessorException(AccessController.class,
2052              String.format(
2053                "This method can only execute at the table specified in "
2054                  + "TablePermission. Table of the region:%s , requested table:%s",
2055                tableName, tperm.getTableName()));
2056          }
2057        }
2058      }
2059      for (Permission permission : permissions) {
2060        boolean hasPermission =
2061          accessChecker.hasUserPermission(user, "checkPermissions", permission);
2062        if (!hasPermission) {
2063          throw new AccessDeniedException("Insufficient permissions " + permission.toString());
2064        }
2065      }
2066      response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
2067    } catch (IOException ioe) {
2068      CoprocessorRpcUtils.setControllerException(controller, ioe);
2069    }
2070    done.run(response);
2071  }
2072
2073  private Region getRegion(RegionCoprocessorEnvironment e) {
2074    return e.getRegion();
2075  }
2076
2077  private TableName getTableName(RegionCoprocessorEnvironment e) {
2078    Region region = e.getRegion();
2079    if (region != null) {
2080      return getTableName(region);
2081    }
2082    return null;
2083  }
2084
2085  private TableName getTableName(Region region) {
2086    RegionInfo regionInfo = region.getRegionInfo();
2087    if (regionInfo != null) {
2088      return regionInfo.getTable();
2089    }
2090    return null;
2091  }
2092
2093  @Override
2094  public void preClose(ObserverContext<? extends RegionCoprocessorEnvironment> c,
2095    boolean abortRequested) throws IOException {
2096    requirePermission(c, "preClose", Action.ADMIN);
2097  }
2098
2099  private void checkSystemOrSuperUser(User activeUser) throws IOException {
2100    // No need to check if we're not going to throw
2101    if (!authorizationEnabled) {
2102      return;
2103    }
2104    if (!Superusers.isSuperUser(activeUser)) {
2105      throw new AccessDeniedException(
2106        "User '" + (activeUser != null ? activeUser.getShortName() : "null")
2107          + "' is not system or super user.");
2108    }
2109  }
2110
2111  @Override
2112  public void preStopRegionServer(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2113    throws IOException {
2114    requirePermission(ctx, "preStopRegionServer", Action.ADMIN);
2115  }
2116
2117  private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family, byte[] qualifier) {
2118    if (family == null) {
2119      return null;
2120    }
2121
2122    Map<byte[], Collection<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
2123    familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
2124    return familyMap;
2125  }
2126
2127  @Override
2128  public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2129    List<TableName> tableNamesList, List<TableDescriptor> descriptors, String regex)
2130    throws IOException {
2131    // We are delegating the authorization check to postGetTableDescriptors as we don't have
2132    // any concrete set of table names when a regex is present or the full list is requested.
2133    if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) {
2134      // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
2135      // request can be granted.
2136      try (Admin admin = ctx.getEnvironment().getConnection().getAdmin()) {
2137        for (TableName tableName : tableNamesList) {
2138          // Skip checks for a table that does not exist
2139          if (!admin.tableExists(tableName)) {
2140            continue;
2141          }
2142          requirePermission(ctx, "getTableDescriptors", tableName, null, null, Action.ADMIN,
2143            Action.CREATE);
2144        }
2145      }
2146    }
2147  }
2148
2149  @Override
2150  public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2151    List<TableName> tableNamesList, List<TableDescriptor> descriptors, String regex)
2152    throws IOException {
2153    // Skipping as checks in this case are already done by preGetTableDescriptors.
2154    if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) {
2155      return;
2156    }
2157
2158    // Retains only those which passes authorization checks, as the checks weren't done as part
2159    // of preGetTableDescriptors.
2160    Iterator<TableDescriptor> itr = descriptors.iterator();
2161    while (itr.hasNext()) {
2162      TableDescriptor htd = itr.next();
2163      try {
2164        requirePermission(ctx, "getTableDescriptors", htd.getTableName(), null, null, Action.ADMIN,
2165          Action.CREATE);
2166      } catch (AccessDeniedException e) {
2167        itr.remove();
2168      }
2169    }
2170  }
2171
2172  @Override
2173  public void postGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx,
2174    List<TableDescriptor> descriptors, String regex) throws IOException {
2175    // Retains only those which passes authorization checks.
2176    Iterator<TableDescriptor> itr = descriptors.iterator();
2177    while (itr.hasNext()) {
2178      TableDescriptor htd = itr.next();
2179      try {
2180        requireAccess(ctx, "getTableNames", htd.getTableName(), Action.values());
2181      } catch (AccessDeniedException e) {
2182        itr.remove();
2183      }
2184    }
2185  }
2186
2187  @Override
2188  public void preMergeRegions(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2189    final RegionInfo[] regionsToMerge) throws IOException {
2190    requirePermission(ctx, "mergeRegions", regionsToMerge[0].getTable(), null, null, Action.ADMIN);
2191  }
2192
2193  @Override
2194  public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2195    throws IOException {
2196    requirePermission(ctx, "preRollLogWriterRequest", Permission.Action.ADMIN);
2197  }
2198
2199  @Override
2200  public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2201    throws IOException {
2202  }
2203
2204  @Override
2205  public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2206    final String userName, final GlobalQuotaSettings quotas) throws IOException {
2207    requirePermission(ctx, "setUserQuota", Action.ADMIN);
2208  }
2209
2210  @Override
2211  public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2212    final String userName, final TableName tableName, final GlobalQuotaSettings quotas)
2213    throws IOException {
2214    requirePermission(ctx, "setUserTableQuota", tableName, null, null, Action.ADMIN);
2215  }
2216
2217  @Override
2218  public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2219    final String userName, final String namespace, final GlobalQuotaSettings quotas)
2220    throws IOException {
2221    requirePermission(ctx, "setUserNamespaceQuota", Action.ADMIN);
2222  }
2223
2224  @Override
2225  public void preSetTableQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2226    final TableName tableName, final GlobalQuotaSettings quotas) throws IOException {
2227    requirePermission(ctx, "setTableQuota", tableName, null, null, Action.ADMIN);
2228  }
2229
2230  @Override
2231  public void preSetNamespaceQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2232    final String namespace, final GlobalQuotaSettings quotas) throws IOException {
2233    requirePermission(ctx, "setNamespaceQuota", Action.ADMIN);
2234  }
2235
2236  @Override
2237  public void preSetRegionServerQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
2238    final String regionServer, GlobalQuotaSettings quotas) throws IOException {
2239    requirePermission(ctx, "setRegionServerQuota", Action.ADMIN);
2240  }
2241
2242  @Override
2243  public ReplicationEndpoint postCreateReplicationEndPoint(
2244    ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
2245    return endpoint;
2246  }
2247
2248  @Override
2249  public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2250    throws IOException {
2251    requirePermission(ctx, "replicateLogEntries", Action.WRITE);
2252  }
2253
2254  @Override
2255  public void preClearCompactionQueues(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2256    throws IOException {
2257    requirePermission(ctx, "preClearCompactionQueues", Permission.Action.ADMIN);
2258  }
2259
2260  @Override
2261  public void preAddReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2262    String peerId, ReplicationPeerConfig peerConfig) throws IOException {
2263    requirePermission(ctx, "addReplicationPeer", Action.ADMIN);
2264  }
2265
2266  @Override
2267  public void preRemoveReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2268    String peerId) throws IOException {
2269    requirePermission(ctx, "removeReplicationPeer", Action.ADMIN);
2270  }
2271
2272  @Override
2273  public void preEnableReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2274    String peerId) throws IOException {
2275    requirePermission(ctx, "enableReplicationPeer", Action.ADMIN);
2276  }
2277
2278  @Override
2279  public void preDisableReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2280    String peerId) throws IOException {
2281    requirePermission(ctx, "disableReplicationPeer", Action.ADMIN);
2282  }
2283
2284  @Override
2285  public void preGetReplicationPeerConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2286    String peerId) throws IOException {
2287    requirePermission(ctx, "getReplicationPeerConfig", Action.ADMIN);
2288  }
2289
2290  @Override
2291  public void preUpdateReplicationPeerConfig(
2292    final ObserverContext<MasterCoprocessorEnvironment> ctx, String peerId,
2293    ReplicationPeerConfig peerConfig) throws IOException {
2294    requirePermission(ctx, "updateReplicationPeerConfig", Action.ADMIN);
2295  }
2296
2297  @Override
2298  public void preTransitReplicationPeerSyncReplicationState(
2299    final ObserverContext<MasterCoprocessorEnvironment> ctx, String peerId,
2300    SyncReplicationState clusterState) throws IOException {
2301    requirePermission(ctx, "transitSyncReplicationPeerState", Action.ADMIN);
2302  }
2303
2304  @Override
2305  public void preListReplicationPeers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2306    String regex) throws IOException {
2307    requirePermission(ctx, "listReplicationPeers", Action.ADMIN);
2308  }
2309
2310  @Override
2311  public void preRequestLock(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace,
2312    TableName tableName, RegionInfo[] regionInfos, String description) throws IOException {
2313    // There are operations in the CREATE and ADMIN domain which may require lock, READ
2314    // or WRITE. So for any lock request, we check for these two perms irrespective of lock type.
2315    String reason = String.format("Description=%s", description);
2316    checkLockPermissions(ctx, namespace, tableName, regionInfos, reason);
2317  }
2318
2319  @Override
2320  public void preLockHeartbeat(ObserverContext<MasterCoprocessorEnvironment> ctx,
2321    TableName tableName, String description) throws IOException {
2322    checkLockPermissions(ctx, null, tableName, null, description);
2323  }
2324
2325  @Override
2326  public void preExecuteProcedures(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2327    throws IOException {
2328    checkSystemOrSuperUser(getActiveUser(ctx));
2329  }
2330
2331  @Override
2332  public void preSwitchRpcThrottle(ObserverContext<MasterCoprocessorEnvironment> ctx,
2333    boolean enable) throws IOException {
2334    requirePermission(ctx, "switchRpcThrottle", Action.ADMIN);
2335  }
2336
2337  @Override
2338  public void preIsRpcThrottleEnabled(ObserverContext<MasterCoprocessorEnvironment> ctx)
2339    throws IOException {
2340    requirePermission(ctx, "isRpcThrottleEnabled", Action.ADMIN);
2341  }
2342
2343  @Override
2344  public void preSwitchExceedThrottleQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
2345    boolean enable) throws IOException {
2346    requirePermission(ctx, "switchExceedThrottleQuota", Action.ADMIN);
2347  }
2348
2349  /**
2350   * Returns the active user to which authorization checks should be applied. If we are in the
2351   * context of an RPC call, the remote user is used, otherwise the currently logged in user is
2352   * used.
2353   */
2354  private User getActiveUser(ObserverContext<?> ctx) throws IOException {
2355    // for non-rpc handling, fallback to system user
2356    Optional<User> optionalUser = ctx.getRpcCallContext().map(ObserverRpcCallContext::getUser);
2357    if (optionalUser.isPresent()) {
2358      return optionalUser.get();
2359    }
2360    return userProvider.getCurrent();
2361  }
2362
2363  /**
2364   * @deprecated since 2.2.0 and will be removed in 4.0.0. Use
2365   *             {@link Admin#hasUserPermissions(String, List)} instead.
2366   * @see Admin#hasUserPermissions(String, List)
2367   * @see <a href="https://issues.apache.org/jira/browse/HBASE-22117">HBASE-22117</a>
2368   */
2369  @Deprecated
2370  @Override
2371  public void hasPermission(RpcController controller, HasPermissionRequest request,
2372    RpcCallback<HasPermissionResponse> done) {
2373    // Converts proto to a TablePermission object.
2374    TablePermission tPerm = AccessControlUtil.toTablePermission(request.getTablePermission());
2375    // Check input user name
2376    if (!request.hasUserName()) {
2377      throw new IllegalStateException("Input username cannot be empty");
2378    }
2379    final String inputUserName = request.getUserName().toStringUtf8();
2380    AccessControlProtos.HasPermissionResponse response = null;
2381    try {
2382      User caller = RpcServer.getRequestUser().orElse(null);
2383      List<Permission> permissions = Lists.newArrayList(tPerm);
2384      preHasUserPermissions(caller, inputUserName, permissions);
2385      boolean hasPermission =
2386        regionEnv.getConnection().getAdmin().hasUserPermissions(inputUserName, permissions).get(0);
2387      response = ResponseConverter.buildHasPermissionResponse(hasPermission);
2388    } catch (IOException ioe) {
2389      ResponseConverter.setControllerException(controller, ioe);
2390    }
2391    done.run(response);
2392  }
2393
2394  @Override
2395  public void preGrant(ObserverContext<MasterCoprocessorEnvironment> ctx,
2396    UserPermission userPermission, boolean mergeExistingPermissions) throws IOException {
2397    preGrantOrRevoke(getActiveUser(ctx), "grant", userPermission);
2398  }
2399
2400  @Override
2401  public void preRevoke(ObserverContext<MasterCoprocessorEnvironment> ctx,
2402    UserPermission userPermission) throws IOException {
2403    preGrantOrRevoke(getActiveUser(ctx), "revoke", userPermission);
2404  }
2405
2406  private void preGrantOrRevoke(User caller, String request, UserPermission userPermission)
2407    throws IOException {
2408    switch (userPermission.getPermission().scope) {
2409      case GLOBAL:
2410        accessChecker.requireGlobalPermission(caller, request, Action.ADMIN, "");
2411        break;
2412      case NAMESPACE:
2413        NamespacePermission namespacePerm = (NamespacePermission) userPermission.getPermission();
2414        accessChecker.requireNamespacePermission(caller, request, namespacePerm.getNamespace(),
2415          null, Action.ADMIN);
2416        break;
2417      case TABLE:
2418        TablePermission tablePerm = (TablePermission) userPermission.getPermission();
2419        accessChecker.requirePermission(caller, request, tablePerm.getTableName(),
2420          tablePerm.getFamily(), tablePerm.getQualifier(), null, Action.ADMIN);
2421        break;
2422      default:
2423    }
2424    if (!Superusers.isSuperUser(caller)) {
2425      accessChecker.performOnSuperuser(request, caller, userPermission.getUser());
2426    }
2427  }
2428
2429  @Override
2430  public void preGetUserPermissions(ObserverContext<MasterCoprocessorEnvironment> ctx,
2431    String userName, String namespace, TableName tableName, byte[] family, byte[] qualifier,
2432    Permission.Scope permissionScope) throws IOException {
2433    preGetUserPermissions(getActiveUser(ctx), userName, namespace, tableName, family, qualifier,
2434      permissionScope);
2435  }
2436
2437  private void preGetUserPermissions(User caller, String userName, String namespace,
2438    TableName tableName, byte[] family, byte[] qualifier, Permission.Scope permissionScope)
2439    throws IOException {
2440    if (permissionScope == Permission.Scope.TABLE) {
2441      accessChecker.requirePermission(caller, "getUserPermissions", tableName, family, qualifier,
2442        userName, Action.ADMIN);
2443    } else if (permissionScope == Permission.Scope.NAMESPACE) {
2444      accessChecker.requireNamespacePermission(caller, "getUserPermissions", namespace, userName,
2445        Action.ADMIN);
2446    } else if (permissionScope == Permission.Scope.GLOBAL) {
2447      accessChecker.requirePermission(caller, "getUserPermissions", userName, Action.ADMIN);
2448    } else {
2449      preGetUserPermissions(caller, userName, namespace, tableName, family, qualifier);
2450    }
2451  }
2452
2453  private void preGetUserPermissions(User caller, String userName, String namespace,
2454    TableName tableName, byte[] family, byte[] qualifier) throws IOException {
2455    if (tableName != null) {
2456      accessChecker.requirePermission(caller, "getUserPermissions", tableName, family, qualifier,
2457        userName, Action.ADMIN);
2458    } else if (namespace != null) {
2459      accessChecker.requireNamespacePermission(caller, "getUserPermissions", namespace, userName,
2460        Action.ADMIN);
2461    } else {
2462      accessChecker.requirePermission(caller, "getUserPermissions", userName, Action.ADMIN);
2463    }
2464  }
2465
2466  @Override
2467  public void preHasUserPermissions(ObserverContext<MasterCoprocessorEnvironment> ctx,
2468    String userName, List<Permission> permissions) throws IOException {
2469    preHasUserPermissions(getActiveUser(ctx), userName, permissions);
2470  }
2471
2472  private void preHasUserPermissions(User caller, String userName, List<Permission> permissions)
2473    throws IOException {
2474    String request = "hasUserPermissions";
2475    for (Permission permission : permissions) {
2476      if (!caller.getShortName().equals(userName)) {
2477        // User should have admin privilege if checking permission for other users
2478        if (permission instanceof TablePermission) {
2479          TablePermission tPerm = (TablePermission) permission;
2480          accessChecker.requirePermission(caller, request, tPerm.getTableName(), tPerm.getFamily(),
2481            tPerm.getQualifier(), userName, Action.ADMIN);
2482        } else if (permission instanceof NamespacePermission) {
2483          NamespacePermission nsPerm = (NamespacePermission) permission;
2484          accessChecker.requireNamespacePermission(caller, request, nsPerm.getNamespace(), userName,
2485            Action.ADMIN);
2486        } else {
2487          accessChecker.requirePermission(caller, request, userName, Action.ADMIN);
2488        }
2489      } else {
2490        // User don't need ADMIN privilege for self check.
2491        // Setting action as null in AuthResult to display empty action in audit log
2492        AuthResult result;
2493        if (permission instanceof TablePermission) {
2494          TablePermission tPerm = (TablePermission) permission;
2495          result = AuthResult.allow(request, "Self user validation allowed", caller, null,
2496            tPerm.getTableName(), tPerm.getFamily(), tPerm.getQualifier());
2497        } else if (permission instanceof NamespacePermission) {
2498          NamespacePermission nsPerm = (NamespacePermission) permission;
2499          result = AuthResult.allow(request, "Self user validation allowed", caller, null,
2500            nsPerm.getNamespace());
2501        } else {
2502          result = AuthResult.allow(request, "Self user validation allowed", caller, null, null,
2503            null, null);
2504        }
2505        AccessChecker.logResult(result);
2506      }
2507    }
2508  }
2509
2510  @Override
2511  public void preMoveServersAndTables(ObserverContext<MasterCoprocessorEnvironment> ctx,
2512    Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
2513    accessChecker.requirePermission(getActiveUser(ctx), "moveServersAndTables", null,
2514      Permission.Action.ADMIN);
2515  }
2516
2517  @Override
2518  public void preMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2519    Set<Address> servers, String targetGroup) throws IOException {
2520    accessChecker.requirePermission(getActiveUser(ctx), "moveServers", null,
2521      Permission.Action.ADMIN);
2522  }
2523
2524  @Override
2525  public void preMoveTables(ObserverContext<MasterCoprocessorEnvironment> ctx,
2526    Set<TableName> tables, String targetGroup) throws IOException {
2527    accessChecker.requirePermission(getActiveUser(ctx), "moveTables", null,
2528      Permission.Action.ADMIN);
2529  }
2530
2531  @Override
2532  public void preAddRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
2533    throws IOException {
2534    accessChecker.requirePermission(getActiveUser(ctx), "addRSGroup", null,
2535      Permission.Action.ADMIN);
2536  }
2537
2538  @Override
2539  public void preRemoveRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
2540    throws IOException {
2541    accessChecker.requirePermission(getActiveUser(ctx), "removeRSGroup", null,
2542      Permission.Action.ADMIN);
2543  }
2544
2545  @Override
2546  public void preBalanceRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName,
2547    BalanceRequest request) throws IOException {
2548    accessChecker.requirePermission(getActiveUser(ctx), "balanceRSGroup", null,
2549      Permission.Action.ADMIN);
2550  }
2551
2552  @Override
2553  public void preRemoveServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
2554    Set<Address> servers) throws IOException {
2555    accessChecker.requirePermission(getActiveUser(ctx), "removeServers", null,
2556      Permission.Action.ADMIN);
2557  }
2558
2559  @Override
2560  public void preGetRSGroupInfo(ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName)
2561    throws IOException {
2562    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfo", null,
2563      Permission.Action.ADMIN);
2564  }
2565
2566  @Override
2567  public void preGetRSGroupInfoOfTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
2568    TableName tableName) throws IOException {
2569    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfTable", null,
2570      Permission.Action.ADMIN);
2571    // todo: should add check for table existence
2572  }
2573
2574  @Override
2575  public void preListRSGroups(ObserverContext<MasterCoprocessorEnvironment> ctx)
2576    throws IOException {
2577    accessChecker.requirePermission(getActiveUser(ctx), "listRSGroups", null,
2578      Permission.Action.ADMIN);
2579  }
2580
2581  @Override
2582  public void preListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
2583    String groupName) throws IOException {
2584    accessChecker.requirePermission(getActiveUser(ctx), "listTablesInRSGroup", null,
2585      Permission.Action.ADMIN);
2586  }
2587
2588  @Override
2589  public void preGetConfiguredNamespacesAndTablesInRSGroup(
2590    ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
2591    accessChecker.requirePermission(getActiveUser(ctx), "getConfiguredNamespacesAndTablesInRSGroup",
2592      null, Permission.Action.ADMIN);
2593  }
2594
2595  @Override
2596  public void preGetRSGroupInfoOfServer(ObserverContext<MasterCoprocessorEnvironment> ctx,
2597    Address server) throws IOException {
2598    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfServer", null,
2599      Permission.Action.ADMIN);
2600  }
2601
2602  @Override
2603  public void preRenameRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String oldName,
2604    String newName) throws IOException {
2605    accessChecker.requirePermission(getActiveUser(ctx), "renameRSGroup", null,
2606      Permission.Action.ADMIN);
2607  }
2608
2609  @Override
2610  public void preUpdateRSGroupConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2611    final String groupName, final Map<String, String> configuration) throws IOException {
2612    accessChecker.requirePermission(getActiveUser(ctx), "updateRSGroupConfig", null,
2613      Permission.Action.ADMIN);
2614  }
2615
2616  @Override
2617  public void preClearRegionBlockCache(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2618    throws IOException {
2619    accessChecker.requirePermission(getActiveUser(ctx), "clearRegionBlockCache", null,
2620      Permission.Action.ADMIN);
2621  }
2622
2623  @Override
2624  public void preUpdateRegionServerConfiguration(
2625    ObserverContext<RegionServerCoprocessorEnvironment> ctx, Configuration preReloadConf)
2626    throws IOException {
2627    accessChecker.requirePermission(getActiveUser(ctx), "updateConfiguration", null,
2628      Permission.Action.ADMIN);
2629  }
2630
2631  @Override
2632  public void preUpdateMasterConfiguration(ObserverContext<MasterCoprocessorEnvironment> ctx,
2633    Configuration preReloadConf) throws IOException {
2634    accessChecker.requirePermission(getActiveUser(ctx), "updateConfiguration", null,
2635      Permission.Action.ADMIN);
2636  }
2637
2638}