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.Collections;
023import java.util.List;
024import org.apache.hadoop.hbase.CellComparator;
025import org.apache.hadoop.hbase.CellUtil;
026import org.apache.hadoop.hbase.ExtendedCell;
027import org.apache.hadoop.hbase.KeyValue;
028import org.apache.hadoop.hbase.KeyValue.Type;
029import org.apache.hadoop.hbase.Tag;
030import org.apache.hadoop.hbase.regionserver.querymatcher.ScanDeleteTracker;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.apache.hadoop.hbase.util.Pair;
033import org.apache.hadoop.hbase.util.Triple;
034import org.apache.yetus.audience.InterfaceAudience;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Similar to ScanDeletTracker but tracks the visibility expression also before deciding if a Cell
040 * can be considered deleted
041 */
042@InterfaceAudience.Private
043public class VisibilityScanDeleteTracker extends ScanDeleteTracker {
044
045  private static final Logger LOG = LoggerFactory.getLogger(VisibilityScanDeleteTracker.class);
046
047  /**
048   * This tag is used for the DELETE cell which has no visibility label.
049   */
050  private static final List<Tag> EMPTY_TAG = Collections.emptyList();
051  // Its better to track the visibility tags in delete based on each type. Create individual
052  // data structures for tracking each of them. This would ensure that there is no tracking based
053  // on time and also would handle all cases where deletefamily or deletecolumns is specified with
054  // Latest_timestamp. In such cases the ts in the delete marker and the masking
055  // put will not be same. So going with individual data structures for different delete
056  // type would solve this problem and also ensure that the combination of different type
057  // of deletes with diff ts would also work fine
058  // Track per TS
059  private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamily = new ArrayList<>();
060  // Delete family version with different ts and different visibility expression could come.
061  // Need to track it per ts.
062  private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamilyVersion = new ArrayList<>();
063  private List<Pair<List<Tag>, Byte>> visibilityTagsDeleteColumns;
064  // Tracking as List<List> is to handle same ts cell but different visibility tag.
065  // TODO : Need to handle puts with same ts but different vis tags.
066  private List<Pair<List<Tag>, Byte>> visiblityTagsDeleteColumnVersion = new ArrayList<>();
067
068  public VisibilityScanDeleteTracker(CellComparator comparator) {
069    super(comparator);
070  }
071
072  @Override
073  public void add(ExtendedCell delCell) {
074    // Cannot call super.add because need to find if the delete needs to be considered
075    long timestamp = delCell.getTimestamp();
076    byte type = delCell.getTypeByte();
077    if (type == KeyValue.Type.DeleteFamily.getCode()) {
078      hasFamilyStamp = true;
079      boolean hasVisTag = extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamily);
080      if (!hasVisTag && timestamp > familyStamp) {
081        familyStamp = timestamp;
082      }
083      return;
084    } else if (type == KeyValue.Type.DeleteFamilyVersion.getCode()) {
085      familyVersionStamps.add(timestamp);
086      extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamilyVersion);
087      return;
088    }
089    // new column, or more general delete type
090    if (deleteCell != null) {
091      if (!(CellUtil.matchingQualifier(delCell, deleteCell))) {
092        // A case where there are deletes for a column qualifier but there are
093        // no corresponding puts for them. Rare case.
094        visibilityTagsDeleteColumns = null;
095        visiblityTagsDeleteColumnVersion = null;
096      } else if (type == KeyValue.Type.Delete.getCode() && (deleteTimestamp != timestamp)) {
097        // there is a timestamp change which means we could clear the list
098        // when ts is same and the vis tags are different we need to collect
099        // them all. Interesting part is that in the normal case of puts if
100        // there are 2 cells with same ts and diff vis tags only one of them is
101        // returned. Handling with a single List<Tag> would mean that only one
102        // of the cell would be considered. Doing this as a precaution.
103        // Rare cases.
104        visiblityTagsDeleteColumnVersion = null;
105      }
106    }
107    deleteCell = delCell;
108    deleteType = type;
109    deleteTimestamp = timestamp;
110    extractDeleteCellVisTags(delCell, KeyValue.Type.codeToType(type));
111  }
112
113  private boolean extractDeleteCellVisTags(ExtendedCell delCell, Type type) {
114    // If tag is present in the delete
115    boolean hasVisTag = false;
116    Byte deleteCellVisTagsFormat = null;
117    switch (type) {
118      case DeleteFamily:
119        List<Tag> delTags = new ArrayList<>();
120        if (visibilityTagsDeleteFamily == null) {
121          visibilityTagsDeleteFamily = new ArrayList<>();
122        }
123        deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
124        if (!delTags.isEmpty()) {
125          visibilityTagsDeleteFamily
126            .add(new Triple<>(delTags, deleteCellVisTagsFormat, delCell.getTimestamp()));
127          hasVisTag = true;
128        } else {
129          visibilityTagsDeleteFamily
130            .add(new Triple<>(EMPTY_TAG, deleteCellVisTagsFormat, delCell.getTimestamp()));
131        }
132        break;
133      case DeleteFamilyVersion:
134        if (visibilityTagsDeleteFamilyVersion == null) {
135          visibilityTagsDeleteFamilyVersion = new ArrayList<>();
136        }
137        delTags = new ArrayList<>();
138        deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
139        if (!delTags.isEmpty()) {
140          visibilityTagsDeleteFamilyVersion
141            .add(new Triple<>(delTags, deleteCellVisTagsFormat, delCell.getTimestamp()));
142          hasVisTag = true;
143        } else {
144          visibilityTagsDeleteFamilyVersion
145            .add(new Triple<>(EMPTY_TAG, deleteCellVisTagsFormat, delCell.getTimestamp()));
146        }
147        break;
148      case DeleteColumn:
149        if (visibilityTagsDeleteColumns == null) {
150          visibilityTagsDeleteColumns = new ArrayList<>();
151        }
152        delTags = new ArrayList<>();
153        deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
154        if (!delTags.isEmpty()) {
155          visibilityTagsDeleteColumns.add(new Pair<>(delTags, deleteCellVisTagsFormat));
156          hasVisTag = true;
157        } else {
158          visibilityTagsDeleteColumns.add(new Pair<>(EMPTY_TAG, deleteCellVisTagsFormat));
159        }
160        break;
161      case Delete:
162        if (visiblityTagsDeleteColumnVersion == null) {
163          visiblityTagsDeleteColumnVersion = new ArrayList<>();
164        }
165        delTags = new ArrayList<>();
166        deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
167        if (!delTags.isEmpty()) {
168          visiblityTagsDeleteColumnVersion.add(new Pair<>(delTags, deleteCellVisTagsFormat));
169          hasVisTag = true;
170        } else {
171          visiblityTagsDeleteColumnVersion.add(new Pair<>(EMPTY_TAG, deleteCellVisTagsFormat));
172        }
173        break;
174      default:
175        throw new IllegalArgumentException("Invalid delete type");
176    }
177    return hasVisTag;
178  }
179
180  @Override
181  public DeleteResult isDeleted(ExtendedCell cell) {
182    long timestamp = cell.getTimestamp();
183    try {
184      if (hasFamilyStamp) {
185        if (visibilityTagsDeleteFamily != null) {
186          if (!visibilityTagsDeleteFamily.isEmpty()) {
187            for (int i = 0; i < visibilityTagsDeleteFamily.size(); i++) {
188              // visibilityTagsDeleteFamily is ArrayList
189              Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamily.get(i);
190              if (timestamp <= triple.getThird()) {
191                List<Tag> putVisTags = new ArrayList<>();
192                Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
193                boolean matchFound = VisibilityLabelServiceManager.getInstance()
194                  .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
195                    triple.getFirst(), triple.getSecond());
196                if (matchFound) {
197                  // A return type of FAMILY_DELETED will cause skip for all remaining cells from
198                  // this
199                  // family. We would like to match visibility expression on every put cells after
200                  // this and only remove those matching with the family delete visibility. So we
201                  // are
202                  // returning FAMILY_VERSION_DELETED from here.
203                  return DeleteResult.FAMILY_VERSION_DELETED;
204                }
205              }
206            }
207          } else {
208            if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
209              // No tags
210              return DeleteResult.FAMILY_VERSION_DELETED;
211            }
212          }
213        } else {
214          if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
215            // No tags
216            return DeleteResult.FAMILY_VERSION_DELETED;
217          }
218        }
219      }
220      if (familyVersionStamps.contains(Long.valueOf(timestamp))) {
221        if (visibilityTagsDeleteFamilyVersion != null) {
222          if (!visibilityTagsDeleteFamilyVersion.isEmpty()) {
223            for (int i = 0; i < visibilityTagsDeleteFamilyVersion.size(); i++) {
224              // visibilityTagsDeleteFamilyVersion is ArrayList
225              Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamilyVersion.get(i);
226              if (timestamp == triple.getThird()) {
227                List<Tag> putVisTags = new ArrayList<>();
228                Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
229                boolean matchFound = VisibilityLabelServiceManager.getInstance()
230                  .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
231                    triple.getFirst(), triple.getSecond());
232                if (matchFound) {
233                  return DeleteResult.FAMILY_VERSION_DELETED;
234                }
235              }
236            }
237          } else {
238            if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
239              // No tags
240              return DeleteResult.FAMILY_VERSION_DELETED;
241            }
242          }
243        } else {
244          if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
245            // No tags
246            return DeleteResult.FAMILY_VERSION_DELETED;
247          }
248        }
249      }
250      if (deleteCell != null) {
251        int ret = comparator.compareQualifiers(cell, deleteCell);
252        if (ret == 0) {
253          if (deleteType == KeyValue.Type.DeleteColumn.getCode()) {
254            if (visibilityTagsDeleteColumns != null) {
255              if (!visibilityTagsDeleteColumns.isEmpty()) {
256                for (Pair<List<Tag>, Byte> tags : visibilityTagsDeleteColumns) {
257                  List<Tag> putVisTags = new ArrayList<>();
258                  Byte putCellVisTagsFormat =
259                    VisibilityUtils.extractVisibilityTags(cell, putVisTags);
260                  boolean matchFound = VisibilityLabelServiceManager.getInstance()
261                    .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
262                      tags.getFirst(), tags.getSecond());
263                  if (matchFound) {
264                    return DeleteResult.VERSION_DELETED;
265                  }
266                }
267              } else {
268                if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
269                  // No tags
270                  return DeleteResult.VERSION_DELETED;
271                }
272              }
273            } else {
274              if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
275                // No tags
276                return DeleteResult.VERSION_DELETED;
277              }
278            }
279          }
280          // Delete (aka DeleteVersion)
281          // If the timestamp is the same, keep this one
282          if (timestamp == deleteTimestamp) {
283            if (visiblityTagsDeleteColumnVersion != null) {
284              if (!visiblityTagsDeleteColumnVersion.isEmpty()) {
285                for (Pair<List<Tag>, Byte> tags : visiblityTagsDeleteColumnVersion) {
286                  List<Tag> putVisTags = new ArrayList<>();
287                  Byte putCellVisTagsFormat =
288                    VisibilityUtils.extractVisibilityTags(cell, putVisTags);
289                  boolean matchFound = VisibilityLabelServiceManager.getInstance()
290                    .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
291                      tags.getFirst(), tags.getSecond());
292                  if (matchFound) {
293                    return DeleteResult.VERSION_DELETED;
294                  }
295                }
296              } else {
297                if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
298                  // No tags
299                  return DeleteResult.VERSION_DELETED;
300                }
301              }
302            } else {
303              if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
304                // No tags
305                return DeleteResult.VERSION_DELETED;
306              }
307            }
308          }
309        } else if (ret > 0) {
310          // Next column case.
311          deleteCell = null;
312          // Can nullify this because we are moving to the next column
313          visibilityTagsDeleteColumns = null;
314          visiblityTagsDeleteColumnVersion = null;
315        } else {
316          throw new IllegalStateException("isDeleted failed: deleteBuffer="
317            + Bytes.toStringBinary(deleteCell.getQualifierArray(), deleteCell.getQualifierOffset(),
318              deleteCell.getQualifierLength())
319            + ", qualifier="
320            + Bytes.toStringBinary(cell.getQualifierArray(), cell.getQualifierOffset(),
321              cell.getQualifierLength())
322            + ", timestamp=" + timestamp + ", comparison result: " + ret);
323        }
324      }
325    } catch (IOException e) {
326      LOG.error("Error in isDeleted() check! Will treat cell as not deleted", e);
327    }
328    return DeleteResult.NOT_DELETED;
329  }
330
331  @Override
332  public void reset() {
333    super.reset();
334    // clear only here
335    visibilityTagsDeleteColumns = null;
336    visibilityTagsDeleteFamily = null;
337    visibilityTagsDeleteFamilyVersion = null;
338    visiblityTagsDeleteColumnVersion = null;
339  }
340}