View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.security.visibility;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.Cell;
30  import org.apache.hadoop.hbase.KeyValue;
31  import org.apache.hadoop.hbase.KeyValue.Type;
32  import org.apache.hadoop.hbase.Tag;
33  import org.apache.hadoop.hbase.regionserver.ScanDeleteTracker;
34  import org.apache.hadoop.hbase.util.Bytes;
35  import org.apache.hadoop.hbase.util.Pair;
36  import org.apache.hadoop.hbase.util.Triple;
37  
38  /**
39   * Similar to ScanDeletTracker but tracks the visibility expression also before
40   * deciding if a Cell can be considered deleted
41   */
42  @InterfaceAudience.Private
43  public class VisibilityScanDeleteTracker extends ScanDeleteTracker {
44  
45    private static final Log LOG = LogFactory.getLog(VisibilityScanDeleteTracker.class);
46  
47    /**
48     * This tag is used for the DELETE cell which has no visibility label.
49     */
50    private static final List<Tag> EMPTY_TAG = Collections.EMPTY_LIST;
51    // Its better to track the visibility tags in delete based on each type.  Create individual
52    // data structures for tracking each of them.  This would ensure that there is no tracking based
53    // on time and also would handle all cases where deletefamily or deletecolumns is specified with
54    // Latest_timestamp.  In such cases the ts in the delete marker and the masking
55    // put will not be same. So going with individual data structures for different delete
56    // type would solve this problem and also ensure that the combination of different type
57    // of deletes with diff ts would also work fine
58    // Track per TS
59    private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamily =
60        new ArrayList<Triple<List<Tag>, Byte, Long>>();
61    // Delete family version with different ts and different visibility expression could come.
62    // Need to track it per ts.
63    private List<Triple<List<Tag>, Byte, Long>> visibilityTagsDeleteFamilyVersion =
64        new ArrayList<Triple<List<Tag>, Byte, Long>>();
65    private List<Pair<List<Tag>, Byte>> visibilityTagsDeleteColumns;
66    // Tracking as List<List> is to handle same ts cell but different visibility tag. 
67    // TODO : Need to handle puts with same ts but different vis tags.
68    private List<Pair<List<Tag>, Byte>> visiblityTagsDeleteColumnVersion =
69        new ArrayList<Pair<List<Tag>, Byte>>();
70  
71    public VisibilityScanDeleteTracker() {
72      super();
73    }
74  
75    @Override
76    public void add(Cell delCell) {
77      //Cannot call super.add because need to find if the delete needs to be considered
78      long timestamp = delCell.getTimestamp();
79      int qualifierOffset = delCell.getQualifierOffset();
80      int qualifierLength = delCell.getQualifierLength();
81      byte type = delCell.getTypeByte();
82      if (type == KeyValue.Type.DeleteFamily.getCode()) {
83        hasFamilyStamp = true;
84        boolean hasVisTag = extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamily);
85        if (!hasVisTag && timestamp > familyStamp) {
86          familyStamp = timestamp;
87        }
88        return;
89      } else if (type == KeyValue.Type.DeleteFamilyVersion.getCode()) {
90        familyVersionStamps.add(timestamp);
91        extractDeleteCellVisTags(delCell, KeyValue.Type.DeleteFamilyVersion);
92        return;
93      }
94      // new column, or more general delete type
95      if (deleteBuffer != null) {
96        if (Bytes.compareTo(deleteBuffer, deleteOffset, deleteLength, delCell.getQualifierArray(),
97            qualifierOffset, qualifierLength) != 0) {
98          // A case where there are deletes for a column qualifier but there are
99          // no corresponding puts for them. Rare case.
100         visibilityTagsDeleteColumns = null;
101         visiblityTagsDeleteColumnVersion = null;
102       } else if (type == KeyValue.Type.Delete.getCode() && (deleteTimestamp != timestamp)) {
103         // there is a timestamp change which means we could clear the list
104         // when ts is same and the vis tags are different we need to collect
105         // them all. Interesting part is that in the normal case of puts if
106         // there are 2 cells with same ts and diff vis tags only one of them is
107         // returned. Handling with a single List<Tag> would mean that only one
108         // of the cell would be considered. Doing this as a precaution.
109         // Rare cases.
110         visiblityTagsDeleteColumnVersion = null;
111       }
112     }
113     deleteBuffer = delCell.getQualifierArray();
114     deleteOffset = qualifierOffset;
115     deleteLength = qualifierLength;
116     deleteType = type;
117     deleteTimestamp = timestamp;
118     extractDeleteCellVisTags(delCell, KeyValue.Type.codeToType(type));
119   }
120 
121   private boolean extractDeleteCellVisTags(Cell delCell, Type type) {
122     // If tag is present in the delete
123     boolean hasVisTag = false;
124     Byte deleteCellVisTagsFormat = null;
125     switch (type) {
126       case DeleteFamily:
127         List<Tag> delTags = new ArrayList<Tag>();
128         if (visibilityTagsDeleteFamily == null) {
129           visibilityTagsDeleteFamily = new ArrayList<Triple<List<Tag>, Byte, Long>>();
130         }
131         deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
132         if (!delTags.isEmpty()) {
133           visibilityTagsDeleteFamily.add(new Triple<List<Tag>, Byte, Long>(delTags,
134               deleteCellVisTagsFormat, delCell.getTimestamp()));
135           hasVisTag = true;
136         } else {
137           visibilityTagsDeleteFamily.add(new Triple<>(EMPTY_TAG, deleteCellVisTagsFormat, delCell.getTimestamp()));
138         }
139         break;
140       case DeleteFamilyVersion:
141         if(visibilityTagsDeleteFamilyVersion == null) {
142           visibilityTagsDeleteFamilyVersion = new ArrayList<Triple<List<Tag>, Byte, Long>>();
143         }
144         delTags = new ArrayList<Tag>();
145         deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
146         if (!delTags.isEmpty()) {
147           visibilityTagsDeleteFamilyVersion.add(new Triple<List<Tag>, Byte, Long>(delTags,
148               deleteCellVisTagsFormat, delCell.getTimestamp()));
149           hasVisTag = true;
150         } else {
151           visibilityTagsDeleteFamilyVersion.add(new Triple<>(EMPTY_TAG, deleteCellVisTagsFormat, delCell.getTimestamp()));
152         }
153         break;
154       case DeleteColumn:
155         if (visibilityTagsDeleteColumns == null) {
156           visibilityTagsDeleteColumns = new ArrayList<Pair<List<Tag>, Byte>>();
157         }
158         delTags = new ArrayList<Tag>();
159         deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
160         if (!delTags.isEmpty()) {
161           visibilityTagsDeleteColumns.add(new Pair<List<Tag>, Byte>(delTags,
162               deleteCellVisTagsFormat));
163           hasVisTag = true;
164         } else {
165           visibilityTagsDeleteColumns.add(new Pair<>(EMPTY_TAG, deleteCellVisTagsFormat));
166         }
167         break;
168       case Delete:
169         if (visiblityTagsDeleteColumnVersion == null) {
170           visiblityTagsDeleteColumnVersion = new ArrayList<Pair<List<Tag>, Byte>>();
171         }
172         delTags = new ArrayList<Tag>();
173         deleteCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(delCell, delTags);
174         if (!delTags.isEmpty()) {
175           visiblityTagsDeleteColumnVersion.add(new Pair<List<Tag>, Byte>(delTags,
176               deleteCellVisTagsFormat));
177           hasVisTag = true;
178         } else {
179           visiblityTagsDeleteColumnVersion.add(new Pair<>(EMPTY_TAG, deleteCellVisTagsFormat));
180         }
181         break;
182       default:
183         throw new IllegalArgumentException("Invalid delete type");
184     }
185     return hasVisTag;
186   }
187 
188   @Override
189   public DeleteResult isDeleted(Cell cell) {
190     long timestamp = cell.getTimestamp();
191     int qualifierOffset = cell.getQualifierOffset();
192     int qualifierLength = cell.getQualifierLength();
193     try {
194       if (hasFamilyStamp) {
195         if (visibilityTagsDeleteFamily != null) {
196           if (!visibilityTagsDeleteFamily.isEmpty()) {
197             for (int i = 0; i < visibilityTagsDeleteFamily.size(); i++) {
198               // visibilityTagsDeleteFamily is ArrayList
199               Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamily.get(i);
200               if (timestamp <= triple.getThird()) {
201                 List<Tag> putVisTags = new ArrayList<Tag>();
202                 Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
203                 boolean matchFound = VisibilityLabelServiceManager.getInstance()
204                     .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
205                       triple.getFirst(), triple.getSecond());
206                 if (matchFound) {
207                   // A return type of FAMILY_DELETED will cause skip for all remaining cells from
208                   // this
209                   // family. We would like to match visibility expression on every put cells after
210                   // this and only remove those matching with the family delete visibility. So we
211                   // are
212                   // returning FAMILY_VERSION_DELETED from here.
213                   return DeleteResult.FAMILY_VERSION_DELETED;
214                 }
215               }
216             }
217           } else {
218             if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
219               // No tags
220               return DeleteResult.FAMILY_VERSION_DELETED;
221             }
222           }
223         } else {
224           if (!VisibilityUtils.isVisibilityTagsPresent(cell) && timestamp <= familyStamp) {
225             // No tags
226             return DeleteResult.FAMILY_VERSION_DELETED;
227           }
228         }
229       }
230       if (familyVersionStamps.contains(Long.valueOf(timestamp))) {
231         if (visibilityTagsDeleteFamilyVersion != null) {
232           if (!visibilityTagsDeleteFamilyVersion.isEmpty()) {
233             for (int i = 0; i < visibilityTagsDeleteFamilyVersion.size(); i++) {
234               // visibilityTagsDeleteFamilyVersion is ArrayList
235               Triple<List<Tag>, Byte, Long> triple = visibilityTagsDeleteFamilyVersion.get(i);
236               if (timestamp == triple.getThird()) {
237                 List<Tag> putVisTags = new ArrayList<Tag>();
238                 Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
239                 boolean matchFound = VisibilityLabelServiceManager.getInstance()
240                     .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
241                       triple.getFirst(), triple.getSecond());
242                 if (matchFound) {
243                   return DeleteResult.FAMILY_VERSION_DELETED;
244                 }
245               }
246             }
247           } else {
248             if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
249               // No tags
250               return DeleteResult.FAMILY_VERSION_DELETED;
251             }
252           }
253         } else {
254           if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
255             // No tags
256             return DeleteResult.FAMILY_VERSION_DELETED;
257           }
258         }
259       }
260       if (deleteBuffer != null) {
261         int ret = Bytes.compareTo(deleteBuffer, deleteOffset, deleteLength,
262             cell.getQualifierArray(), qualifierOffset, qualifierLength);
263 
264         if (ret == 0) {
265           if (deleteType == KeyValue.Type.DeleteColumn.getCode()) {
266             if (visibilityTagsDeleteColumns != null) {
267               if (!visibilityTagsDeleteColumns.isEmpty()) {
268                 for (Pair<List<Tag>, Byte> tags : visibilityTagsDeleteColumns) {
269                   List<Tag> putVisTags = new ArrayList<Tag>();
270                   Byte putCellVisTagsFormat =
271                       VisibilityUtils.extractVisibilityTags(cell, putVisTags);
272                   boolean matchFound = VisibilityLabelServiceManager.getInstance()
273                       .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
274                         tags.getFirst(), tags.getSecond());
275                   if (matchFound) {
276                     return DeleteResult.VERSION_DELETED;
277                   }
278                 }
279               } else {
280                 if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
281                   // No tags
282                   return DeleteResult.VERSION_DELETED;
283                 }
284               }
285             } else {
286               if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
287                 // No tags
288                 return DeleteResult.VERSION_DELETED;
289               }
290             }
291           }
292           // Delete (aka DeleteVersion)
293           // If the timestamp is the same, keep this one
294           if (timestamp == deleteTimestamp) {
295             if (visiblityTagsDeleteColumnVersion != null) {
296               if (!visiblityTagsDeleteColumnVersion.isEmpty()) {
297                 for (Pair<List<Tag>, Byte> tags : visiblityTagsDeleteColumnVersion) {
298                   List<Tag> putVisTags = new ArrayList<Tag>();
299                   Byte putCellVisTagsFormat =
300                       VisibilityUtils.extractVisibilityTags(cell, putVisTags);
301                   boolean matchFound = VisibilityLabelServiceManager.getInstance()
302                       .getVisibilityLabelService().matchVisibility(putVisTags, putCellVisTagsFormat,
303                         tags.getFirst(), tags.getSecond());
304                   if (matchFound) {
305                     return DeleteResult.VERSION_DELETED;
306                   }
307                 }
308               } else {
309                 if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
310                   // No tags
311                   return DeleteResult.VERSION_DELETED;
312                 }
313               }
314             } else {
315               if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
316                 // No tags
317                 return DeleteResult.VERSION_DELETED;
318               }
319             }
320           }
321         } else if (ret < 0) {
322           // Next column case.
323           deleteBuffer = null;
324           // Can nullify this because we are moving to the next column
325           visibilityTagsDeleteColumns = null;
326           visiblityTagsDeleteColumnVersion = null;
327         } else {
328           throw new IllegalStateException("isDeleted failed: deleteBuffer="
329               + Bytes.toStringBinary(deleteBuffer, deleteOffset, deleteLength) + ", qualifier="
330               + Bytes.toStringBinary(cell.getQualifierArray(), qualifierOffset, qualifierLength)
331               + ", timestamp=" + timestamp + ", comparison result: " + ret);
332         }
333       }
334     } catch (IOException e) {
335       LOG.error("Error in isDeleted() check! Will treat cell as not deleted", e);
336     }
337     return DeleteResult.NOT_DELETED;
338   }
339 
340   @Override
341   public void reset() {
342     super.reset();
343     // clear only here
344     visibilityTagsDeleteColumns = null;
345     visibilityTagsDeleteFamily = null;
346     visibilityTagsDeleteFamilyVersion = null;
347     visiblityTagsDeleteColumnVersion = null;
348   }
349 }