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.visibility;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.NavigableMap;
026import java.util.NavigableSet;
027import java.util.SortedMap;
028import java.util.SortedSet;
029import java.util.TreeMap;
030import java.util.TreeSet;
031import org.apache.hadoop.hbase.Cell;
032import org.apache.hadoop.hbase.CellComparator;
033import org.apache.hadoop.hbase.KeyValue;
034import org.apache.hadoop.hbase.Tag;
035import org.apache.hadoop.hbase.regionserver.querymatcher.NewVersionBehaviorTracker;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * Similar to MvccSensitiveTracker but tracks the visibility expression also before deciding if a
042 * Cell can be considered deleted
043 */
044@InterfaceAudience.Private
045public class VisibilityNewVersionBehaivorTracker extends NewVersionBehaviorTracker {
046  private static final Logger LOG =
047    LoggerFactory.getLogger(VisibilityNewVersionBehaivorTracker.class);
048
049  public VisibilityNewVersionBehaivorTracker(NavigableSet<byte[]> columns,
050    CellComparator cellComparator, int minVersion, int maxVersion, int resultMaxVersions,
051    long oldestUnexpiredTS) {
052    super(columns, cellComparator, minVersion, maxVersion, resultMaxVersions, oldestUnexpiredTS);
053  }
054
055  private static class TagInfo {
056    List<Tag> tags;
057    Byte format;
058
059    private TagInfo(Cell c) {
060      tags = new ArrayList<>();
061      format = VisibilityUtils.extractVisibilityTags(c, tags);
062    }
063
064    private TagInfo() {
065      tags = new ArrayList<>();
066    }
067  }
068
069  private class VisibilityDeleteVersionsNode extends DeleteVersionsNode {
070    private TagInfo tagInfo;
071
072    // <timestamp, set<mvcc>>
073    // Key is ts of version deletes, value is its mvccs.
074    // We may delete more than one time for a version.
075    private Map<Long, SortedMap<Long, TagInfo>> deletesMap = new HashMap<>();
076
077    // <mvcc, set<mvcc>>
078    // Key is mvcc of version deletes, value is mvcc of visible puts before the delete effect.
079    private NavigableMap<Long, SortedSet<Long>> mvccCountingMap = new TreeMap<>();
080
081    protected VisibilityDeleteVersionsNode(long ts, long mvcc, TagInfo tagInfo) {
082      this.tagInfo = tagInfo;
083      this.ts = ts;
084      this.mvcc = mvcc;
085      mvccCountingMap.put(Long.MAX_VALUE, new TreeSet<Long>());
086    }
087
088    @Override
089    protected VisibilityDeleteVersionsNode getDeepCopy() {
090      VisibilityDeleteVersionsNode node = new VisibilityDeleteVersionsNode(ts, mvcc, tagInfo);
091      for (Map.Entry<Long, SortedMap<Long, TagInfo>> e : deletesMap.entrySet()) {
092        node.deletesMap.put(e.getKey(), new TreeMap<>(e.getValue()));
093      }
094      for (Map.Entry<Long, SortedSet<Long>> e : mvccCountingMap.entrySet()) {
095        node.mvccCountingMap.put(e.getKey(), new TreeSet<>(e.getValue()));
096      }
097      return node;
098    }
099
100    @Override
101    public void addVersionDelete(Cell cell) {
102      SortedMap<Long, TagInfo> set = deletesMap.get(cell.getTimestamp());
103      if (set == null) {
104        set = new TreeMap<>();
105        deletesMap.put(cell.getTimestamp(), set);
106      }
107      set.put(cell.getSequenceId(), new TagInfo(cell));
108      // The init set should be the puts whose mvcc is smaller than this Delete. Because
109      // there may be some Puts masked by them. The Puts whose mvcc is larger than this Delete can
110      // not be copied to this node because we may delete one version and the oldest put may not be
111      // masked.
112      SortedSet<Long> nextValue = mvccCountingMap.ceilingEntry(cell.getSequenceId()).getValue();
113      SortedSet<Long> thisValue = new TreeSet<>(nextValue.headSet(cell.getSequenceId()));
114      mvccCountingMap.put(cell.getSequenceId(), thisValue);
115    }
116
117  }
118
119  @Override
120  public void add(Cell cell) {
121    prepare(cell);
122    byte type = cell.getTypeByte();
123    switch (KeyValue.Type.codeToType(type)) {
124      // By the order of seen. We put null cq at first.
125      case DeleteFamily: // Delete all versions of all columns of the specified family
126        delFamMap.put(cell.getSequenceId(), new VisibilityDeleteVersionsNode(cell.getTimestamp(),
127          cell.getSequenceId(), new TagInfo(cell)));
128        break;
129      case DeleteFamilyVersion: // Delete all columns of the specified family and specified version
130        delFamMap.ceilingEntry(cell.getSequenceId()).getValue().addVersionDelete(cell);
131        break;
132
133      // These two kinds of markers are mix with Puts.
134      case DeleteColumn: // Delete all versions of the specified column
135        delColMap.put(cell.getSequenceId(), new VisibilityDeleteVersionsNode(cell.getTimestamp(),
136          cell.getSequenceId(), new TagInfo(cell)));
137        break;
138      case Delete: // Delete the specified version of the specified column.
139        delColMap.ceilingEntry(cell.getSequenceId()).getValue().addVersionDelete(cell);
140        break;
141      default:
142        throw new AssertionError("Unknown delete marker type for " + cell);
143    }
144  }
145
146  private boolean tagMatched(Cell put, TagInfo delInfo) throws IOException {
147    List<Tag> putVisTags = new ArrayList<>();
148    Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(put, putVisTags);
149    return putVisTags.isEmpty() == delInfo.tags.isEmpty()
150      && ((putVisTags.isEmpty() && delInfo.tags.isEmpty())
151        || VisibilityLabelServiceManager.getInstance().getVisibilityLabelService()
152          .matchVisibility(putVisTags, putCellVisTagsFormat, delInfo.tags, delInfo.format));
153  }
154
155  @Override
156  public DeleteResult isDeleted(Cell cell) {
157    try {
158      long duplicateMvcc = prepare(cell);
159
160      for (Map.Entry<Long, DeleteVersionsNode> e : delColMap.tailMap(cell.getSequenceId())
161        .entrySet()) {
162        VisibilityDeleteVersionsNode node = (VisibilityDeleteVersionsNode) e.getValue();
163        long deleteMvcc = Long.MAX_VALUE;
164        SortedMap<Long, TagInfo> deleteVersionMvccs = node.deletesMap.get(cell.getTimestamp());
165        if (deleteVersionMvccs != null) {
166          SortedMap<Long, TagInfo> tail = deleteVersionMvccs.tailMap(cell.getSequenceId());
167          for (Map.Entry<Long, TagInfo> entry : tail.entrySet()) {
168            if (tagMatched(cell, entry.getValue())) {
169              deleteMvcc = tail.firstKey();
170              break;
171            }
172          }
173        }
174        SortedMap<Long, SortedSet<Long>> subMap = node.mvccCountingMap.subMap(cell.getSequenceId(),
175          true, Math.min(duplicateMvcc, deleteMvcc), true);
176        for (Map.Entry<Long, SortedSet<Long>> seg : subMap.entrySet()) {
177          if (seg.getValue().size() >= maxVersions) {
178            return DeleteResult.VERSION_MASKED;
179          }
180          seg.getValue().add(cell.getSequenceId());
181        }
182        if (deleteMvcc < Long.MAX_VALUE) {
183          return DeleteResult.VERSION_DELETED;
184        }
185
186        if (cell.getTimestamp() <= node.ts && tagMatched(cell, node.tagInfo)) {
187          return DeleteResult.COLUMN_DELETED;
188        }
189      }
190      if (duplicateMvcc < Long.MAX_VALUE) {
191        return DeleteResult.VERSION_MASKED;
192      }
193    } catch (IOException e) {
194      LOG.error("Error in isDeleted() check! Will treat cell as not deleted", e);
195    }
196    return DeleteResult.NOT_DELETED;
197  }
198
199  @Override
200  protected void resetInternal() {
201    delFamMap.put(Long.MAX_VALUE,
202      new VisibilityDeleteVersionsNode(Long.MIN_VALUE, Long.MAX_VALUE, new TagInfo()));
203  }
204}