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 */
018
019package org.apache.hadoop.hbase.security.visibility;
020
021import static org.apache.hadoop.hbase.HConstants.OperationStatusCode.SANITY_CHECK_FAILURE;
022import static org.apache.hadoop.hbase.HConstants.OperationStatusCode.SUCCESS;
023import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
024import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
025
026import java.io.IOException;
027import java.net.InetAddress;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Objects;
035import java.util.Optional;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.hbase.AuthUtil;
038import org.apache.hadoop.hbase.Cell;
039import org.apache.hadoop.hbase.CellScanner;
040import org.apache.hadoop.hbase.CoprocessorEnvironment;
041import org.apache.hadoop.hbase.DoNotRetryIOException;
042import org.apache.hadoop.hbase.HBaseInterfaceAudience;
043import org.apache.hadoop.hbase.PrivateCellUtil;
044import org.apache.hadoop.hbase.TableName;
045import org.apache.hadoop.hbase.Tag;
046import org.apache.hadoop.hbase.TagType;
047import org.apache.hadoop.hbase.client.Admin;
048import org.apache.hadoop.hbase.client.Append;
049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
050import org.apache.hadoop.hbase.client.Delete;
051import org.apache.hadoop.hbase.client.Get;
052import org.apache.hadoop.hbase.client.Increment;
053import org.apache.hadoop.hbase.client.MasterSwitchType;
054import org.apache.hadoop.hbase.client.Mutation;
055import org.apache.hadoop.hbase.client.Put;
056import org.apache.hadoop.hbase.client.Result;
057import org.apache.hadoop.hbase.client.Scan;
058import org.apache.hadoop.hbase.client.TableDescriptor;
059import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
060import org.apache.hadoop.hbase.constraint.ConstraintException;
061import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
062import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
063import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
064import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
065import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
066import org.apache.hadoop.hbase.coprocessor.MasterObserver;
067import org.apache.hadoop.hbase.coprocessor.ObserverContext;
068import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
069import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
070import org.apache.hadoop.hbase.coprocessor.RegionObserver;
071import org.apache.hadoop.hbase.exceptions.DeserializationException;
072import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException;
073import org.apache.hadoop.hbase.filter.Filter;
074import org.apache.hadoop.hbase.filter.FilterBase;
075import org.apache.hadoop.hbase.filter.FilterList;
076import org.apache.hadoop.hbase.io.hfile.HFile;
077import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
078import org.apache.hadoop.hbase.ipc.RpcServer;
079import org.apache.hadoop.hbase.regionserver.BloomType;
080import org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy;
081import org.apache.hadoop.hbase.regionserver.InternalScanner;
082import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
083import org.apache.hadoop.hbase.regionserver.OperationStatus;
084import org.apache.hadoop.hbase.regionserver.Region;
085import org.apache.hadoop.hbase.regionserver.RegionScanner;
086import org.apache.hadoop.hbase.regionserver.querymatcher.DeleteTracker;
087import org.apache.hadoop.hbase.security.AccessDeniedException;
088import org.apache.hadoop.hbase.security.Superusers;
089import org.apache.hadoop.hbase.security.User;
090import org.apache.hadoop.hbase.security.access.AccessChecker;
091import org.apache.hadoop.hbase.security.access.AccessController;
092import org.apache.hadoop.hbase.util.Bytes;
093import org.apache.hadoop.hbase.util.Pair;
094import org.apache.hadoop.util.StringUtils;
095import org.apache.yetus.audience.InterfaceAudience;
096import org.slf4j.Logger;
097import org.slf4j.LoggerFactory;
098
099import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
100import org.apache.hbase.thirdparty.com.google.common.collect.MapMaker;
101import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
102import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
103import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
104import org.apache.hbase.thirdparty.com.google.protobuf.Service;
105
106import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionActionResult;
107import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.NameBytesPair;
108import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos;
109import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.GetAuthsRequest;
110import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse;
111import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.ListLabelsRequest;
112import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.ListLabelsResponse;
113import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.SetAuthsRequest;
114import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabel;
115import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsRequest;
116import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
117import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsService;
118
119/**
120 * Coprocessor that has both the MasterObserver and RegionObserver implemented that supports in
121 * visibility labels
122 */
123@CoreCoprocessor
124@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
125// TODO: break out Observer functions into separate class/sub-class.
126public class VisibilityController implements MasterCoprocessor, RegionCoprocessor,
127    VisibilityLabelsService.Interface, MasterObserver, RegionObserver {
128
129
130  private static final Logger LOG = LoggerFactory.getLogger(VisibilityController.class);
131  private static final Logger AUDITLOG = LoggerFactory.getLogger("SecurityLogger."
132      + VisibilityController.class.getName());
133  // flags if we are running on a region of the 'labels' table
134  private boolean labelsRegion = false;
135  // Flag denoting whether AcessController is available or not.
136  private boolean accessControllerAvailable = false;
137  private Configuration conf;
138  private volatile boolean initialized = false;
139  private boolean checkAuths = false;
140  /** Mapping of scanner instances to the user who created them */
141  private Map<InternalScanner,String> scannerOwners =
142      new MapMaker().weakKeys().makeMap();
143
144  private VisibilityLabelService visibilityLabelService;
145
146  /** if we are active, usually false, only true if "hbase.security.authorization"
147    has been set to true in site configuration */
148  boolean authorizationEnabled;
149
150  // Add to this list if there are any reserved tag types
151  private static ArrayList<Byte> RESERVED_VIS_TAG_TYPES = new ArrayList<>();
152  static {
153    RESERVED_VIS_TAG_TYPES.add(TagType.VISIBILITY_TAG_TYPE);
154    RESERVED_VIS_TAG_TYPES.add(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE);
155    RESERVED_VIS_TAG_TYPES.add(TagType.STRING_VIS_TAG_TYPE);
156  }
157
158  public static boolean isCellAuthorizationSupported(Configuration conf) {
159    return AccessChecker.isAuthorizationSupported(conf);
160  }
161
162  @Override
163  public void start(CoprocessorEnvironment env) throws IOException {
164    this.conf = env.getConfiguration();
165
166    authorizationEnabled = AccessChecker.isAuthorizationSupported(conf);
167    if (!authorizationEnabled) {
168      LOG.warn("The VisibilityController has been loaded with authorization checks disabled.");
169    }
170
171    if (HFile.getFormatVersion(conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) {
172      throw new RuntimeException("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
173        + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY
174        + " accordingly.");
175    }
176
177    // Do not create for master CPs
178    if (!(env instanceof MasterCoprocessorEnvironment)) {
179      visibilityLabelService = VisibilityLabelServiceManager.getInstance()
180          .getVisibilityLabelService(this.conf);
181    }
182  }
183
184  @Override
185  public void stop(CoprocessorEnvironment env) throws IOException {
186
187  }
188
189  /**************************** Observer/Service Getters ************************************/
190  @Override
191  public Optional<RegionObserver> getRegionObserver() {
192    return Optional.of(this);
193  }
194
195  @Override
196  public Optional<MasterObserver> getMasterObserver() {
197    return Optional.of(this);
198  }
199
200  @Override
201  public Iterable<Service> getServices() {
202    return Collections.singleton(
203        VisibilityLabelsProtos.VisibilityLabelsService.newReflectiveService(this));
204  }
205
206  /********************************* Master related hooks **********************************/
207
208  @Override
209  public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
210    throws IOException {
211    // Need to create the new system table for labels here
212    try (Admin admin = ctx.getEnvironment().getConnection().getAdmin()) {
213      if (!admin.tableExists(LABELS_TABLE_NAME)) {
214        // We will cache all the labels. No need of normal table block cache.
215        // Let the "labels" table having only one region always. We are not expecting too many
216        // labels in the system.
217        TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(LABELS_TABLE_NAME)
218          .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(LABELS_TABLE_FAMILY)
219            .setBloomFilterType(BloomType.NONE).setBlockCacheEnabled(false).build())
220          .setValue(TableDescriptorBuilder.SPLIT_POLICY, DisabledRegionSplitPolicy.class.getName())
221          .build();
222
223        admin.createTable(tableDescriptor);
224      }
225    }
226  }
227
228  @Override
229  public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
230      TableName tableName, TableDescriptor currentDescriptor, TableDescriptor newDescriptor)
231      throws IOException {
232    if (authorizationEnabled) {
233      if (LABELS_TABLE_NAME.equals(tableName)) {
234        throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME);
235      }
236    }
237    return newDescriptor;
238  }
239
240  @Override
241  public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName)
242      throws IOException {
243    if (!authorizationEnabled) {
244      return;
245    }
246    if (LABELS_TABLE_NAME.equals(tableName)) {
247      throw new ConstraintException("Cannot disable " + LABELS_TABLE_NAME);
248    }
249  }
250
251  /****************************** Region related hooks ******************************/
252
253  @Override
254  public void postOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
255    // Read the entire labels table and populate the zk
256    if (e.getEnvironment().getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
257      this.labelsRegion = true;
258      synchronized (this) {
259        this.accessControllerAvailable = CoprocessorHost.getLoadedCoprocessors()
260          .contains(AccessController.class.getName());
261      }
262      initVisibilityLabelService(e.getEnvironment());
263    } else {
264      checkAuths = e.getEnvironment().getConfiguration()
265          .getBoolean(VisibilityConstants.CHECK_AUTHS_FOR_MUTATION, false);
266      initVisibilityLabelService(e.getEnvironment());
267    }
268  }
269
270  private void initVisibilityLabelService(RegionCoprocessorEnvironment env) {
271    try {
272      this.visibilityLabelService.init(env);
273      this.initialized = true;
274    } catch (IOException ioe) {
275      LOG.error("Error while initializing VisibilityLabelService..", ioe);
276      throw new RuntimeException(ioe);
277    }
278  }
279
280  @Override
281  public void postSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
282      final boolean newValue, final MasterSwitchType switchType) throws IOException {
283  }
284
285  @Override
286  public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
287      MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
288    if (c.getEnvironment().getRegion().getRegionInfo().getTable().isSystemTable()) {
289      return;
290    }
291    // TODO this can be made as a global LRU cache at HRS level?
292    Map<String, List<Tag>> labelCache = new HashMap<>();
293    for (int i = 0; i < miniBatchOp.size(); i++) {
294      Mutation m = miniBatchOp.getOperation(i);
295      CellVisibility cellVisibility = null;
296      try {
297        cellVisibility = m.getCellVisibility();
298      } catch (DeserializationException de) {
299        miniBatchOp.setOperationStatus(i,
300            new OperationStatus(SANITY_CHECK_FAILURE, de.getMessage()));
301        continue;
302      }
303      boolean sanityFailure = false;
304      boolean modifiedTagFound = false;
305      Pair<Boolean, Tag> pair = new Pair<>(false, null);
306      for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
307        pair = checkForReservedVisibilityTagPresence(cellScanner.current(), pair);
308        if (!pair.getFirst()) {
309          // Don't disallow reserved tags if authorization is disabled
310          if (authorizationEnabled) {
311            miniBatchOp.setOperationStatus(i, new OperationStatus(SANITY_CHECK_FAILURE,
312              "Mutation contains cell with reserved type tag"));
313            sanityFailure = true;
314          }
315          break;
316        } else {
317          // Indicates that the cell has a the tag which was modified in the src replication cluster
318          Tag tag = pair.getSecond();
319          if (cellVisibility == null && tag != null) {
320            // May need to store only the first one
321            cellVisibility = new CellVisibility(Tag.getValueAsString(tag));
322            modifiedTagFound = true;
323          }
324        }
325      }
326      if (!sanityFailure) {
327        if (cellVisibility != null) {
328          String labelsExp = cellVisibility.getExpression();
329          List<Tag> visibilityTags = labelCache.get(labelsExp);
330          if (visibilityTags == null) {
331            // Don't check user auths for labels with Mutations when the user is super user
332            boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser());
333            try {
334              visibilityTags = this.visibilityLabelService.createVisibilityExpTags(labelsExp, true,
335                  authCheck);
336            } catch (InvalidLabelException e) {
337              miniBatchOp.setOperationStatus(i,
338                  new OperationStatus(SANITY_CHECK_FAILURE, e.getMessage()));
339            }
340            if (visibilityTags != null) {
341              labelCache.put(labelsExp, visibilityTags);
342            }
343          }
344          if (visibilityTags != null) {
345            List<Cell> updatedCells = new ArrayList<>();
346            for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
347              Cell cell = cellScanner.current();
348              List<Tag> tags = PrivateCellUtil.getTags(cell);
349              if (modifiedTagFound) {
350                // Rewrite the tags by removing the modified tags.
351                removeReplicationVisibilityTag(tags);
352              }
353              tags.addAll(visibilityTags);
354              Cell updatedCell = PrivateCellUtil.createCell(cell, tags);
355              updatedCells.add(updatedCell);
356            }
357            m.getFamilyCellMap().clear();
358            // Clear and add new Cells to the Mutation.
359            for (Cell cell : updatedCells) {
360              if (m instanceof Put) {
361                Put p = (Put) m;
362                p.add(cell);
363              } else if (m instanceof Delete) {
364                Delete d = (Delete) m;
365                d.add(cell);
366              }
367            }
368          }
369        }
370      }
371    }
372  }
373
374  @Override
375  public void prePrepareTimeStampForDeleteVersion(
376      ObserverContext<RegionCoprocessorEnvironment> ctx, Mutation delete, Cell cell,
377      byte[] byteNow, Get get) throws IOException {
378    // Nothing to do if we are not filtering by visibility
379    if (!authorizationEnabled) {
380      return;
381    }
382
383    CellVisibility cellVisibility = null;
384    try {
385      cellVisibility = delete.getCellVisibility();
386    } catch (DeserializationException de) {
387      throw new IOException("Invalid cell visibility specified " + delete, de);
388    }
389    // The check for checkForReservedVisibilityTagPresence happens in preBatchMutate happens.
390    // It happens for every mutation and that would be enough.
391    List<Tag> visibilityTags = new ArrayList<>();
392    if (cellVisibility != null) {
393      String labelsExp = cellVisibility.getExpression();
394      try {
395        visibilityTags = this.visibilityLabelService.createVisibilityExpTags(labelsExp, false,
396            false);
397      } catch (InvalidLabelException e) {
398        throw new IOException("Invalid cell visibility specified " + labelsExp, e);
399      }
400    }
401    get.setFilter(new DeleteVersionVisibilityExpressionFilter(visibilityTags,
402        VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT));
403    List<Cell> result = ctx.getEnvironment().getRegion().get(get, false);
404
405    if (result.size() < get.getMaxVersions()) {
406      // Nothing to delete
407      PrivateCellUtil.updateLatestStamp(cell, byteNow);
408      return;
409    }
410    if (result.size() > get.getMaxVersions()) {
411      throw new RuntimeException("Unexpected size: " + result.size()
412          + ". Results more than the max versions obtained.");
413    }
414    Cell getCell = result.get(get.getMaxVersions() - 1);
415    PrivateCellUtil.setTimestamp(cell, getCell.getTimestamp());
416
417    // We are bypassing here because in the HRegion.updateDeleteLatestVersionTimeStamp we would
418    // update with the current timestamp after again doing a get. As the hook as already determined
419    // the needed timestamp we need to bypass here.
420    // TODO : See if HRegion.updateDeleteLatestVersionTimeStamp() could be
421    // called only if the hook is not called.
422    ctx.bypass();
423  }
424
425  /**
426   * Checks whether cell contains any tag with type as VISIBILITY_TAG_TYPE. This
427   * tag type is reserved and should not be explicitly set by user.
428   *
429   * @param cell The cell under consideration
430   * @param pair An optional pair of type {@code <Boolean, Tag>} which would be reused if already
431   *     set and new one will be created if NULL is passed
432   * @return If the boolean is false then it indicates that the cell has a RESERVERD_VIS_TAG and
433   *     with boolean as true, not null tag indicates that a string modified tag was found.
434   */
435  private Pair<Boolean, Tag> checkForReservedVisibilityTagPresence(Cell cell,
436      Pair<Boolean, Tag> pair) throws IOException {
437    if (pair == null) {
438      pair = new Pair<>(false, null);
439    } else {
440      pair.setFirst(false);
441      pair.setSecond(null);
442    }
443    // Bypass this check when the operation is done by a system/super user.
444    // This is done because, while Replication, the Cells coming to the peer cluster with reserved
445    // typed tags and this is fine and should get added to the peer cluster table
446    if (isSystemOrSuperUser()) {
447      // Does the cell contain special tag which indicates that the replicated
448      // cell visiblilty tags
449      // have been modified
450      Tag modifiedTag = null;
451      Iterator<Tag> tagsIterator = PrivateCellUtil.tagsIterator(cell);
452      while (tagsIterator.hasNext()) {
453        Tag tag = tagsIterator.next();
454        if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) {
455          modifiedTag = tag;
456          break;
457        }
458      }
459      pair.setFirst(true);
460      pair.setSecond(modifiedTag);
461      return pair;
462    }
463    Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(cell);
464    while (tagsItr.hasNext()) {
465      if (RESERVED_VIS_TAG_TYPES.contains(tagsItr.next().getType())) {
466        return pair;
467      }
468    }
469    pair.setFirst(true);
470    return pair;
471  }
472
473  /**
474   * Checks whether cell contains any tag with type as VISIBILITY_TAG_TYPE. This
475   * tag type is reserved and should not be explicitly set by user. There are
476   * two versions of this method one that accepts pair and other without pair.
477   * In case of preAppend and preIncrement the additional operations are not
478   * needed like checking for STRING_VIS_TAG_TYPE and hence the API without pair
479   * could be used.
480   *
481   * @param cell
482   * @throws IOException
483   */
484  private boolean checkForReservedVisibilityTagPresence(Cell cell) throws IOException {
485    // Bypass this check when the operation is done by a system/super user.
486    // This is done because, while Replication, the Cells coming to the peer
487    // cluster with reserved
488    // typed tags and this is fine and should get added to the peer cluster
489    // table
490    if (isSystemOrSuperUser()) {
491      return true;
492    }
493    Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(cell);
494    while (tagsItr.hasNext()) {
495      if (RESERVED_VIS_TAG_TYPES.contains(tagsItr.next().getType())) {
496        return false;
497      }
498    }
499    return true;
500  }
501
502  private void removeReplicationVisibilityTag(List<Tag> tags) throws IOException {
503    Iterator<Tag> iterator = tags.iterator();
504    while (iterator.hasNext()) {
505      Tag tag = iterator.next();
506      if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) {
507        iterator.remove();
508        break;
509      }
510    }
511  }
512
513  @Override
514  public void preScannerOpen(ObserverContext<RegionCoprocessorEnvironment> e, Scan scan)
515      throws IOException {
516    if (!initialized) {
517      throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized!");
518    }
519    // Nothing to do if authorization is not enabled
520    if (!authorizationEnabled) {
521      return;
522    }
523    Region region = e.getEnvironment().getRegion();
524    Authorizations authorizations = null;
525    try {
526      authorizations = scan.getAuthorizations();
527    } catch (DeserializationException de) {
528      throw new IOException(de);
529    }
530    if (authorizations == null) {
531      // No Authorizations present for this scan/Get!
532      // In case of system tables other than "labels" just scan with out visibility check and
533      // filtering. Checking visibility labels for META and NAMESPACE table is not needed.
534      TableName table = region.getRegionInfo().getTable();
535      if (table.isSystemTable() && !table.equals(LABELS_TABLE_NAME)) {
536        return;
537      }
538    }
539
540    Filter visibilityLabelFilter = VisibilityUtils.createVisibilityLabelFilter(region,
541        authorizations);
542    if (visibilityLabelFilter != null) {
543      Filter filter = scan.getFilter();
544      if (filter != null) {
545        scan.setFilter(new FilterList(filter, visibilityLabelFilter));
546      } else {
547        scan.setFilter(visibilityLabelFilter);
548      }
549    }
550  }
551
552  @Override
553  public DeleteTracker postInstantiateDeleteTracker(
554      ObserverContext<RegionCoprocessorEnvironment> ctx, DeleteTracker delTracker)
555      throws IOException {
556    // Nothing to do if we are not filtering by visibility
557    if (!authorizationEnabled) {
558      return delTracker;
559    }
560    Region region = ctx.getEnvironment().getRegion();
561    TableName table = region.getRegionInfo().getTable();
562    if (table.isSystemTable()) {
563      return delTracker;
564    }
565    // We are creating a new type of delete tracker here which is able to track
566    // the timestamps and also the
567    // visibility tags per cell. The covering cells are determined not only
568    // based on the delete type and ts
569    // but also on the visibility expression matching.
570    return new VisibilityScanDeleteTracker(delTracker.getCellComparator());
571  }
572
573  @Override
574  public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
575      final Scan scan, final RegionScanner s) throws IOException {
576    User user = VisibilityUtils.getActiveUser();
577    if (user != null && user.getShortName() != null) {
578      scannerOwners.put(s, user.getShortName());
579    }
580    return s;
581  }
582
583  @Override
584  public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
585      final InternalScanner s, final List<Result> result, final int limit, final boolean hasNext)
586      throws IOException {
587    requireScannerOwner(s);
588    return hasNext;
589  }
590
591  @Override
592  public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
593      final InternalScanner s) throws IOException {
594    requireScannerOwner(s);
595  }
596
597  @Override
598  public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
599      final InternalScanner s) throws IOException {
600    // clean up any associated owner mapping
601    scannerOwners.remove(s);
602  }
603
604  /**
605   * Verify, when servicing an RPC, that the caller is the scanner owner. If so, we assume that
606   * access control is correctly enforced based on the checks performed in preScannerOpen()
607   */
608  private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
609    if (!RpcServer.isInRpcCallContext())
610      return;
611    String requestUName = RpcServer.getRequestUserName().orElse(null);
612    String owner = scannerOwners.get(s);
613    if (authorizationEnabled && owner != null && !owner.equals(requestUName)) {
614      throw new AccessDeniedException("User '" + requestUName + "' is not the scanner owner!");
615    }
616  }
617
618  @Override
619  public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
620      List<Cell> results) throws IOException {
621    if (!initialized) {
622      throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized");
623    }
624    // Nothing useful to do if authorization is not enabled
625    if (!authorizationEnabled) {
626      return;
627    }
628    Region region = e.getEnvironment().getRegion();
629    Authorizations authorizations = null;
630    try {
631      authorizations = get.getAuthorizations();
632    } catch (DeserializationException de) {
633      throw new IOException(de);
634    }
635    if (authorizations == null) {
636      // No Authorizations present for this scan/Get!
637      // In case of system tables other than "labels" just scan with out visibility check and
638      // filtering. Checking visibility labels for META and NAMESPACE table is not needed.
639      TableName table = region.getRegionInfo().getTable();
640      if (table.isSystemTable() && !table.equals(LABELS_TABLE_NAME)) {
641        return;
642      }
643    }
644    Filter visibilityLabelFilter = VisibilityUtils.createVisibilityLabelFilter(e.getEnvironment()
645        .getRegion(), authorizations);
646    if (visibilityLabelFilter != null) {
647      Filter filter = get.getFilter();
648      if (filter != null) {
649        get.setFilter(new FilterList(filter, visibilityLabelFilter));
650      } else {
651        get.setFilter(visibilityLabelFilter);
652      }
653    }
654  }
655
656  private boolean isSystemOrSuperUser() throws IOException {
657    return Superusers.isSuperUser(VisibilityUtils.getActiveUser());
658  }
659
660  @Override
661  public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> e, Append append)
662      throws IOException {
663    // If authorization is not enabled, we don't care about reserved tags
664    if (!authorizationEnabled) {
665      return null;
666    }
667    for (CellScanner cellScanner = append.cellScanner(); cellScanner.advance();) {
668      if (!checkForReservedVisibilityTagPresence(cellScanner.current())) {
669        throw new FailedSanityCheckException("Append contains cell with reserved type tag");
670      }
671    }
672    return null;
673  }
674
675  @Override
676  public Result preIncrement(ObserverContext<RegionCoprocessorEnvironment> e, Increment increment)
677      throws IOException {
678    // If authorization is not enabled, we don't care about reserved tags
679    if (!authorizationEnabled) {
680      return null;
681    }
682    for (CellScanner cellScanner = increment.cellScanner(); cellScanner.advance();) {
683      if (!checkForReservedVisibilityTagPresence(cellScanner.current())) {
684        throw new FailedSanityCheckException("Increment contains cell with reserved type tag");
685      }
686    }
687    return null;
688  }
689
690  @Override
691  public List<Pair<Cell, Cell>> postIncrementBeforeWAL(
692      ObserverContext<RegionCoprocessorEnvironment> ctx, Mutation mutation,
693      List<Pair<Cell, Cell>> cellPairs) throws IOException {
694    List<Pair<Cell, Cell>> resultPairs = new ArrayList<>(cellPairs.size());
695    for (Pair<Cell, Cell> pair : cellPairs) {
696      resultPairs
697          .add(new Pair<>(pair.getFirst(), createNewCellWithTags(mutation, pair.getSecond())));
698    }
699    return resultPairs;
700  }
701
702  @Override
703  public List<Pair<Cell, Cell>> postAppendBeforeWAL(
704      ObserverContext<RegionCoprocessorEnvironment> ctx, Mutation mutation,
705      List<Pair<Cell, Cell>> cellPairs) throws IOException {
706    List<Pair<Cell, Cell>> resultPairs = new ArrayList<>(cellPairs.size());
707    for (Pair<Cell, Cell> pair : cellPairs) {
708      resultPairs
709          .add(new Pair<>(pair.getFirst(), createNewCellWithTags(mutation, pair.getSecond())));
710    }
711    return resultPairs;
712  }
713
714  private Cell createNewCellWithTags(Mutation mutation, Cell newCell) throws IOException {
715    List<Tag> tags = Lists.newArrayList();
716    CellVisibility cellVisibility = null;
717    try {
718      cellVisibility = mutation.getCellVisibility();
719    } catch (DeserializationException e) {
720      throw new IOException(e);
721    }
722    if (cellVisibility == null) {
723      return newCell;
724    }
725    // Prepend new visibility tags to a new list of tags for the cell
726    // Don't check user auths for labels with Mutations when the user is super user
727    boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser());
728    tags.addAll(this.visibilityLabelService.createVisibilityExpTags(cellVisibility.getExpression(),
729        true, authCheck));
730    // Carry forward all other tags
731    Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(newCell);
732    while (tagsItr.hasNext()) {
733      Tag tag = tagsItr.next();
734      if (tag.getType() != TagType.VISIBILITY_TAG_TYPE
735          && tag.getType() != TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) {
736        tags.add(tag);
737      }
738    }
739
740    return PrivateCellUtil.createCell(newCell, tags);
741  }
742
743  @Override
744  public boolean postScannerFilterRow(final ObserverContext<RegionCoprocessorEnvironment> e,
745      final InternalScanner s, final Cell curRowCell, final boolean hasMore) throws IOException {
746    // 'default' in RegionObserver might do unnecessary copy for Off heap backed Cells.
747    return hasMore;
748  }
749
750  /****************************** VisibilityEndpoint service related methods ******************************/
751  @Override
752  public synchronized void addLabels(RpcController controller, VisibilityLabelsRequest request,
753      RpcCallback<VisibilityLabelsResponse> done) {
754    VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
755    List<VisibilityLabel> visLabels = request.getVisLabelList();
756    if (!initialized) {
757      setExceptionResults(visLabels.size(),
758        new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"),
759        response);
760    } else {
761      List<byte[]> labels = new ArrayList<>(visLabels.size());
762      try {
763        if (authorizationEnabled) {
764          checkCallingUserAuth();
765        }
766        RegionActionResult successResult = RegionActionResult.newBuilder().build();
767        for (VisibilityLabel visLabel : visLabels) {
768          byte[] label = visLabel.getLabel().toByteArray();
769          labels.add(label);
770          response.addResult(successResult); // Just mark as success. Later it will get reset
771                                             // based on the result from
772                                             // visibilityLabelService.addLabels ()
773        }
774        if (!labels.isEmpty()) {
775          OperationStatus[] opStatus = this.visibilityLabelService.addLabels(labels);
776          logResult(true, "addLabels", "Adding labels allowed", null, labels, null);
777          int i = 0;
778          for (OperationStatus status : opStatus) {
779            while (!Objects.equals(response.getResult(i), successResult)) {
780              i++;
781            }
782            if (status.getOperationStatusCode() != SUCCESS) {
783              RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
784              failureResultBuilder.setException(buildException(new DoNotRetryIOException(
785                  status.getExceptionMsg())));
786              response.setResult(i, failureResultBuilder.build());
787            }
788            i++;
789          }
790        }
791      } catch (AccessDeniedException e) {
792        logResult(false, "addLabels", e.getMessage(), null, labels, null);
793        LOG.error("User is not having required permissions to add labels", e);
794        setExceptionResults(visLabels.size(), e, response);
795      } catch (IOException e) {
796        LOG.error(e.toString(), e);
797        setExceptionResults(visLabels.size(), e, response);
798      }
799    }
800    done.run(response.build());
801  }
802
803  private void setExceptionResults(int size, IOException e,
804      VisibilityLabelsResponse.Builder response) {
805    RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
806    failureResultBuilder.setException(buildException(e));
807    RegionActionResult failureResult = failureResultBuilder.build();
808    for (int i = 0; i < size; i++) {
809      response.addResult(i, failureResult);
810    }
811  }
812
813  @Override
814  public synchronized void setAuths(RpcController controller, SetAuthsRequest request,
815      RpcCallback<VisibilityLabelsResponse> done) {
816    VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
817    List<ByteString> auths = request.getAuthList();
818    if (!initialized) {
819      setExceptionResults(auths.size(),
820        new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"),
821        response);
822    } else {
823      byte[] user = request.getUser().toByteArray();
824      List<byte[]> labelAuths = new ArrayList<>(auths.size());
825      try {
826        if (authorizationEnabled) {
827          checkCallingUserAuth();
828        }
829        for (ByteString authBS : auths) {
830          labelAuths.add(authBS.toByteArray());
831        }
832        OperationStatus[] opStatus = this.visibilityLabelService.setAuths(user, labelAuths);
833        logResult(true, "setAuths", "Setting authorization for labels allowed", user, labelAuths,
834          null);
835        RegionActionResult successResult = RegionActionResult.newBuilder().build();
836        for (OperationStatus status : opStatus) {
837          if (status.getOperationStatusCode() == SUCCESS) {
838            response.addResult(successResult);
839          } else {
840            RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
841            failureResultBuilder.setException(buildException(new DoNotRetryIOException(
842                status.getExceptionMsg())));
843            response.addResult(failureResultBuilder.build());
844          }
845        }
846      } catch (AccessDeniedException e) {
847        logResult(false, "setAuths", e.getMessage(), user, labelAuths, null);
848        LOG.error("User is not having required permissions to set authorization", e);
849        setExceptionResults(auths.size(), e, response);
850      } catch (IOException e) {
851        LOG.error(e.toString(), e);
852        setExceptionResults(auths.size(), e, response);
853      }
854    }
855    done.run(response.build());
856  }
857
858  private void logResult(boolean isAllowed, String request, String reason, byte[] user,
859      List<byte[]> labelAuths, String regex) {
860    if (AUDITLOG.isTraceEnabled()) {
861      // This is more duplicated code!
862      List<String> labelAuthsStr = new ArrayList<>();
863      if (labelAuths != null) {
864        int labelAuthsSize = labelAuths.size();
865        labelAuthsStr = new ArrayList<>(labelAuthsSize);
866        for (int i = 0; i < labelAuthsSize; i++) {
867          labelAuthsStr.add(Bytes.toString(labelAuths.get(i)));
868        }
869      }
870
871      User requestingUser = null;
872      try {
873        requestingUser = VisibilityUtils.getActiveUser();
874      } catch (IOException e) {
875        LOG.warn("Failed to get active system user.");
876        LOG.debug("Details on failure to get active system user.", e);
877      }
878      AUDITLOG.trace("Access " + (isAllowed ? "allowed" : "denied") + " for user " +
879          (requestingUser != null ? requestingUser.getShortName() : "UNKNOWN") + "; reason: " +
880          reason + "; remote address: " +
881          RpcServer.getRemoteAddress().map(InetAddress::toString).orElse("") + "; request: " +
882          request + "; user: " + (user != null ? Bytes.toShort(user) : "null") + "; labels: " +
883          labelAuthsStr + "; regex: " + regex);
884    }
885  }
886
887  @Override
888  public synchronized void getAuths(RpcController controller, GetAuthsRequest request,
889      RpcCallback<GetAuthsResponse> done) {
890    GetAuthsResponse.Builder response = GetAuthsResponse.newBuilder();
891    if (!initialized) {
892      controller.setFailed("VisibilityController not yet initialized");
893    } else {
894      byte[] user = request.getUser().toByteArray();
895      List<String> labels = null;
896      try {
897        // We do ACL check here as we create scanner directly on region. It will not make calls to
898        // AccessController CP methods.
899        if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
900          User requestingUser = VisibilityUtils.getActiveUser();
901          throw new AccessDeniedException("User '"
902              + (requestingUser != null ? requestingUser.getShortName() : "null")
903              + "' is not authorized to perform this action.");
904        }
905        if (AuthUtil.isGroupPrincipal(Bytes.toString(user))) {
906          String group = AuthUtil.getGroupName(Bytes.toString(user));
907          labels = this.visibilityLabelService.getGroupAuths(new String[]{group}, false);
908        }
909        else {
910          labels = this.visibilityLabelService.getUserAuths(user, false);
911        }
912        logResult(true, "getAuths", "Get authorizations for user allowed", user, null, null);
913      } catch (AccessDeniedException e) {
914        logResult(false, "getAuths", e.getMessage(), user, null, null);
915        CoprocessorRpcUtils.setControllerException(controller, e);
916      } catch (IOException e) {
917        CoprocessorRpcUtils.setControllerException(controller, e);
918      }
919      response.setUser(request.getUser());
920      if (labels != null) {
921        for (String label : labels) {
922          response.addAuth(ByteString.copyFrom(Bytes.toBytes(label)));
923        }
924      }
925    }
926    done.run(response.build());
927  }
928
929  @Override
930  public synchronized void clearAuths(RpcController controller, SetAuthsRequest request,
931      RpcCallback<VisibilityLabelsResponse> done) {
932    VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
933    List<ByteString> auths = request.getAuthList();
934    if (!initialized) {
935      setExceptionResults(auths.size(), new CoprocessorException(
936          "VisibilityController not yet initialized"), response);
937    } else {
938      byte[] requestUser = request.getUser().toByteArray();
939      List<byte[]> labelAuths = new ArrayList<>(auths.size());
940      try {
941        // When AC is ON, do AC based user auth check
942        if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
943          User user = VisibilityUtils.getActiveUser();
944          throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null")
945              + " is not authorized to perform this action.");
946        }
947        if (authorizationEnabled) {
948          checkCallingUserAuth(); // When AC is not in place the calling user should have
949                                  // SYSTEM_LABEL auth to do this action.
950        }
951        for (ByteString authBS : auths) {
952          labelAuths.add(authBS.toByteArray());
953        }
954
955        OperationStatus[] opStatus =
956            this.visibilityLabelService.clearAuths(requestUser, labelAuths);
957        logResult(true, "clearAuths", "Removing authorization for labels allowed", requestUser,
958          labelAuths, null);
959        RegionActionResult successResult = RegionActionResult.newBuilder().build();
960        for (OperationStatus status : opStatus) {
961          if (status.getOperationStatusCode() == SUCCESS) {
962            response.addResult(successResult);
963          } else {
964            RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
965            failureResultBuilder.setException(buildException(new DoNotRetryIOException(
966                status.getExceptionMsg())));
967            response.addResult(failureResultBuilder.build());
968          }
969        }
970      } catch (AccessDeniedException e) {
971        logResult(false, "clearAuths", e.getMessage(), requestUser, labelAuths, null);
972        LOG.error("User is not having required permissions to clear authorization", e);
973        setExceptionResults(auths.size(), e, response);
974      } catch (IOException e) {
975        LOG.error(e.toString(), e);
976        setExceptionResults(auths.size(), e, response);
977      }
978    }
979    done.run(response.build());
980  }
981
982  @Override
983  public synchronized void listLabels(RpcController controller, ListLabelsRequest request,
984      RpcCallback<ListLabelsResponse> done) {
985    ListLabelsResponse.Builder response = ListLabelsResponse.newBuilder();
986    if (!initialized) {
987      controller.setFailed("VisibilityController not yet initialized");
988    } else {
989      List<String> labels = null;
990      String regex = request.hasRegex() ? request.getRegex() : null;
991      try {
992        // We do ACL check here as we create scanner directly on region. It will not make calls to
993        // AccessController CP methods.
994        if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
995          User requestingUser = VisibilityUtils.getActiveUser();
996          throw new AccessDeniedException("User '"
997              + (requestingUser != null ? requestingUser.getShortName() : "null")
998              + "' is not authorized to perform this action.");
999        }
1000        labels = this.visibilityLabelService.listLabels(regex);
1001        logResult(false, "listLabels", "Listing labels allowed", null, null, regex);
1002      } catch (AccessDeniedException e) {
1003        logResult(false, "listLabels", e.getMessage(), null, null, regex);
1004        CoprocessorRpcUtils.setControllerException(controller, e);
1005      } catch (IOException e) {
1006        CoprocessorRpcUtils.setControllerException(controller, e);
1007      }
1008      if (labels != null && !labels.isEmpty()) {
1009        for (String label : labels) {
1010          response.addLabel(ByteString.copyFrom(Bytes.toBytes(label)));
1011        }
1012      }
1013    }
1014    done.run(response.build());
1015  }
1016
1017  private void checkCallingUserAuth() throws IOException {
1018    if (!authorizationEnabled) { // Redundant, but just in case
1019      return;
1020    }
1021    if (!accessControllerAvailable) {
1022      User user = VisibilityUtils.getActiveUser();
1023      if (user == null) {
1024        throw new IOException("Unable to retrieve calling user");
1025      }
1026      if (!(this.visibilityLabelService.havingSystemAuth(user))) {
1027        throw new AccessDeniedException("User '" + user.getShortName()
1028            + "' is not authorized to perform this action.");
1029      }
1030    }
1031  }
1032
1033  private static class DeleteVersionVisibilityExpressionFilter extends FilterBase {
1034    private List<Tag> deleteCellVisTags;
1035    private Byte deleteCellVisTagsFormat;
1036
1037    public DeleteVersionVisibilityExpressionFilter(List<Tag> deleteCellVisTags,
1038        Byte deleteCellVisTagsFormat) {
1039      this.deleteCellVisTags = deleteCellVisTags;
1040      this.deleteCellVisTagsFormat = deleteCellVisTagsFormat;
1041    }
1042
1043    @Override
1044    public boolean filterRowKey(Cell cell) throws IOException {
1045      // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
1046      return false;
1047    }
1048
1049    @Override
1050    public ReturnCode filterCell(final Cell cell) throws IOException {
1051      List<Tag> putVisTags = new ArrayList<>();
1052      Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
1053      if (putVisTags.isEmpty() && deleteCellVisTags.isEmpty()) {
1054        // Early out if there are no tags in the cell
1055        return ReturnCode.INCLUDE;
1056      }
1057      boolean matchFound = VisibilityLabelServiceManager
1058          .getInstance().getVisibilityLabelService()
1059          .matchVisibility(putVisTags, putCellVisTagsFormat, deleteCellVisTags,
1060              deleteCellVisTagsFormat);
1061      return matchFound ? ReturnCode.INCLUDE : ReturnCode.SKIP;
1062    }
1063
1064    @Override
1065    public boolean equals(Object obj) {
1066      if (!(obj instanceof DeleteVersionVisibilityExpressionFilter)) {
1067        return false;
1068      }
1069      if (this == obj){
1070        return true;
1071      }
1072      DeleteVersionVisibilityExpressionFilter f = (DeleteVersionVisibilityExpressionFilter)obj;
1073      return this.deleteCellVisTags.equals(f.deleteCellVisTags) &&
1074          this.deleteCellVisTagsFormat.equals(f.deleteCellVisTagsFormat);
1075    }
1076
1077    @Override
1078    public int hashCode() {
1079      return Objects.hash(this.deleteCellVisTags, this.deleteCellVisTagsFormat);
1080    }
1081  }
1082
1083  /**
1084   * @param t
1085   * @return NameValuePair of the exception name to stringified version os exception.
1086   */
1087  // Copied from ResponseConverter and made private. Only used in here.
1088  private static NameBytesPair buildException(final Throwable t) {
1089    NameBytesPair.Builder parameterBuilder = NameBytesPair.newBuilder();
1090    parameterBuilder.setName(t.getClass().getName());
1091    parameterBuilder.setValue(
1092      ByteString.copyFromUtf8(StringUtils.stringifyException(t)));
1093    return parameterBuilder.build();
1094  }
1095}