blob: 4e27bbfa7c2065c722247dd20a148d83d4ca05be [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.security.visibility;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.regionserver.querymatcher.ScanDeleteTracker;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellComparator;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValue.Type;
import org.apache.hadoop.hbase.Tag;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Triple;
/**
* Similar to ScanDeletTracker but tracks the visibility expression also before
* deciding if a Cell can be considered deleted
*/
@InterfaceAudience.Private
public class VisibilityScanDeleteTracker extends ScanDeleteTracker {
private static final Log LOG = LogFactory.getLog(VisibilityScanDeleteTracker.class);
// Its better to track the visibility tags in delete based on each type. Create individual
// data structures for tracking each of them. This would ensure that there is no tracking based
// on time and also would handle all cases where deletefamily or deletecolumns is specified with
// Latest_timestamp. In such cases the ts in the delete marker and the masking
// put will not be same. So going with individual data structures for different delete
// type would solve this problem and also ensure that the combination of different type
// of deletes with diff ts would also work fine
// Track per TS
private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamily =
new ArrayList<Triple<List<Tag>, Byte, Long>>();
// Delete family version with different ts and different visibility expression could come.
// Need to track it per ts.
private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamilyVersion =
new ArrayList<Triple<List<Tag>, Byte, Long>>();
private List<Pair<List<Tag>, Byte>> visibilityTagsDeleteColumns;
// Tracking as List<List> is to handle same ts cell but different visibility tag.
// TODO : Need to handle puts with same ts but different vis tags.
private List<Pair<List<Tag>, Byte>> visiblityTagsDeleteColumnVersion =
new ArrayList<Pair<List<Tag>, Byte>>();
public VisibilityScanDeleteTracker() {
super();
}
@Override
public void add(Cell delCell) {
//Cannot call super.add because need to find if the delete needs to be considered
long timestamp = delCell.getTimestamp();
byte type = delCell.getTypeByte();
if (type == KeyValue.Type.DeleteFamily.getCode()) {
hasFamilyStamp = true;
boolean hasVisTag = extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamily);
if (!hasVisTag && timestamp > familyStamp) {
familyStamp = timestamp;
}
return;
} else if (type == KeyValue.Type.DeleteFamilyVersion.getCode()) {
familyVersionStamps.add(timestamp);
extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamilyVersion);
return;
}
// new column, or more general delete type
if (deleteBuffer != null) {
if (!(CellUtil.matchingQualifier(delCell, deleteBuffer, deleteOffset, deleteLength))) {
// A case where there are deletes for a column qualifier but there are
// no corresponding puts for them. Rare case.
visibilityTagsDeleteColumns = null;
visiblityTagsDeleteColumnVersion = null;
} else if (type == KeyValue.Type.Delete.getCode() && (deleteTimestamp != timestamp)) {
// there is a timestamp change which means we could clear the list
// when ts is same and the vis tags are different we need to collect
// them all. Interesting part is that in the normal case of puts if
// there are 2 cells with same ts and diff vis tags only one of them is
// returned. Handling with a single List<Tag> would mean that only one
// of the cell would be considered. Doing this as a precaution.
// Rare cases.
visiblityTagsDeleteColumnVersion = null;
}
}
deleteBuffer = delCell.getQualifierArray();
deleteOffset = delCell.getQualifierOffset();
deleteLength = delCell.getQualifierLength();
deleteType = type;
deleteTimestamp = timestamp;
extractDeleteCellVisTags(delCell, KeyValue.Type.codeToType(type));
}
private boolean extractDeleteCellVisTags(Cell delCell, Type type) {
// If tag is present in the delete
boolean hasVisTag = false;
if (delCell.getTagsLength() > 0) {
Byte deleteCellVisTagsFormat = null;
switch (type) {
case DeleteFamily:
List<Tag> delTags = new ArrayList<Tag>();
if (visibilityTagsDeleteFamily == null) {
visibilityTagsDeleteFamily = new ArrayList<Triple<List<Tag>, Byte, Long>>();
}
deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
if (!delTags.isEmpty()) {
visibilityTagsDeleteFamily.add(new Triple<List<Tag>, Byte, Long>(delTags,
deleteCellVisTagsFormat, delCell.getTimestamp()));
hasVisTag = true;
}
break;
case DeleteFamilyVersion:
if(visibilityTagsDeleteFamilyVersion == null) {
visibilityTagsDeleteFamilyVersion = new ArrayList<Triple<List<Tag>, Byte, Long>>();
}
delTags = new ArrayList<Tag>();
deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
if (!delTags.isEmpty()) {
visibilityTagsDeleteFamilyVersion.add(new Triple<List<Tag>, Byte, Long>(delTags,
deleteCellVisTagsFormat, delCell.getTimestamp()));
hasVisTag = true;
}
break;
case DeleteColumn:
if (visibilityTagsDeleteColumns == null) {
visibilityTagsDeleteColumns = new ArrayList<Pair<List<Tag>, Byte>>();
}
delTags = new ArrayList<Tag>();
deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
if (!delTags.isEmpty()) {
visibilityTagsDeleteColumns.add(new Pair<List<Tag>, Byte>(delTags,
deleteCellVisTagsFormat));
hasVisTag = true;
}
break;
case Delete:
if (visiblityTagsDeleteColumnVersion == null) {
visiblityTagsDeleteColumnVersion = new ArrayList<Pair<List<Tag>, Byte>>();
}
delTags = new ArrayList<Tag>();
deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
if (!delTags.isEmpty()) {
visiblityTagsDeleteColumnVersion.add(new Pair<List<Tag>, Byte>(delTags,
deleteCellVisTagsFormat));
hasVisTag = true;
}
break;
default:
throw new IllegalArgumentException("Invalid delete type");
}
}
return hasVisTag;
}
@Override
public DeleteResult isDeleted(Cell cell) {
long timestamp = cell.getTimestamp();
try {
if (hasFamilyStamp) {
if (visibilityTagsDeleteFamily != null) {
if (!visibilityTagsDeleteFamily.isEmpty()) {
for (int i = 0; i < visibilityTagsDeleteFamily.size(); i++) {
// visibilityTagsDeleteFamily is ArrayList
Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamily.get(i);
if (timestamp <= triple.getThird()) {
List<Tag> putVisTags = new ArrayList<Tag>();
Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
boolean matchFound = VisibilityLabelServiceManager.getInstance()
.getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
triple.getFirst(), triple.getSecond());
if (matchFound) {
// A return type of FAMILY_DELETED will cause skip for all remaining cells from
// this
// family. We would like to match visibility expression on every put cells after
// this and only remove those matching with the family delete visibility. So we
// are
// returning FAMILY_VERSION_DELETED from here.
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
// No tags
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
// No tags
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
}
if (familyVersionStamps.contains(Long.valueOf(timestamp))) {
if (visibilityTagsDeleteFamilyVersion != null) {
if (!visibilityTagsDeleteFamilyVersion.isEmpty()) {
for (int i = 0; i < visibilityTagsDeleteFamilyVersion.size(); i++) {
// visibilityTagsDeleteFamilyVersion is ArrayList
Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamilyVersion.get(i);
if (timestamp == triple.getThird()) {
List<Tag> putVisTags = new ArrayList<Tag>();
Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
boolean matchFound = VisibilityLabelServiceManager.getInstance()
.getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
triple.getFirst(), triple.getSecond());
if (matchFound) {
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.FAMILY_VERSION_DELETED;
}
}
}
if (deleteBuffer != null) {
int ret = CellComparator.compareQualifiers(cell, deleteBuffer, deleteOffset, deleteLength);
if (ret == 0) {
if (deleteType == KeyValue.Type.DeleteColumn.getCode()) {
if (visibilityTagsDeleteColumns != null) {
if (!visibilityTagsDeleteColumns.isEmpty()) {
for (Pair<List<Tag>, Byte> tags : visibilityTagsDeleteColumns) {
List<Tag> putVisTags = new ArrayList<Tag>();
Byte putCellVisTagsFormat =
VisibilityUtils.extractVisibilityTags(cell, putVisTags);
boolean matchFound = VisibilityLabelServiceManager.getInstance()
.getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
tags.getFirst(), tags.getSecond());
if (matchFound) {
return DeleteResult.VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.VERSION_DELETED;
}
}
}
// Delete (aka DeleteVersion)
// If the timestamp is the same, keep this one
if (timestamp == deleteTimestamp) {
if (visiblityTagsDeleteColumnVersion != null) {
if (!visiblityTagsDeleteColumnVersion.isEmpty()) {
for (Pair<List<Tag>, Byte> tags : visiblityTagsDeleteColumnVersion) {
List<Tag> putVisTags = new ArrayList<Tag>();
Byte putCellVisTagsFormat =
VisibilityUtils.extractVisibilityTags(cell, putVisTags);
boolean matchFound = VisibilityLabelServiceManager.getInstance()
.getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
tags.getFirst(), tags.getSecond());
if (matchFound) {
return DeleteResult.VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.VERSION_DELETED;
}
}
} else {
if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
// No tags
return DeleteResult.VERSION_DELETED;
}
}
}
} else if (ret > 0) {
// Next column case.
deleteBuffer = null;
// Can nullify this because we are moving to the next column
visibilityTagsDeleteColumns = null;
visiblityTagsDeleteColumnVersion = null;
} else {
throw new IllegalStateException("isDeleted failed: deleteBuffer="
+ Bytes.toStringBinary(deleteBuffer, deleteOffset, deleteLength) + ", qualifier="
+ Bytes.toStringBinary(cell.getQualifierArray(), cell.getQualifierOffset(),
cell.getQualifierLength())
+ ", timestamp=" + timestamp + ", comparison result: " + ret);
}
}
} catch (IOException e) {
LOG.error("Error in isDeleted() check! Will treat cell as not deleted", e);
}
return DeleteResult.NOT_DELETED;
}
@Override
public void reset() {
super.reset();
// clear only here
visibilityTagsDeleteColumns = null;
visibilityTagsDeleteFamily = null;
visibilityTagsDeleteFamilyVersion = null;
visiblityTagsDeleteColumnVersion = null;
}
}