View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security.visibility;
20  
21  import static org.apache.hadoop.hbase.HConstants.OperationStatusCode.SANITY_CHECK_FAILURE;
22  import static org.apache.hadoop.hbase.HConstants.OperationStatusCode.SUCCESS;
23  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
24  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
25  
26  import java.io.IOException;
27  import java.net.InetAddress;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Objects;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.hbase.AuthUtil;
39  import org.apache.hadoop.hbase.Cell;
40  import org.apache.hadoop.hbase.CellScanner;
41  import org.apache.hadoop.hbase.CellUtil;
42  import org.apache.hadoop.hbase.CoprocessorEnvironment;
43  import org.apache.hadoop.hbase.DoNotRetryIOException;
44  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
45  import org.apache.hadoop.hbase.HColumnDescriptor;
46  import org.apache.hadoop.hbase.HConstants;
47  import org.apache.hadoop.hbase.HTableDescriptor;
48  import org.apache.hadoop.hbase.MetaTableAccessor;
49  import org.apache.hadoop.hbase.TableName;
50  import org.apache.hadoop.hbase.Tag;
51  import org.apache.hadoop.hbase.TagRewriteCell;
52  import org.apache.hadoop.hbase.TagType;
53  import org.apache.hadoop.hbase.classification.InterfaceAudience;
54  import org.apache.hadoop.hbase.client.Admin;
55  import org.apache.hadoop.hbase.client.Append;
56  import org.apache.hadoop.hbase.client.Delete;
57  import org.apache.hadoop.hbase.client.Get;
58  import org.apache.hadoop.hbase.client.Increment;
59  import org.apache.hadoop.hbase.client.Mutation;
60  import org.apache.hadoop.hbase.client.Put;
61  import org.apache.hadoop.hbase.client.Result;
62  import org.apache.hadoop.hbase.client.Scan;
63  import org.apache.hadoop.hbase.constraint.ConstraintException;
64  import org.apache.hadoop.hbase.coprocessor.BaseMasterAndRegionObserver;
65  import org.apache.hadoop.hbase.coprocessor.BaseRegionServerObserver;
66  import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
67  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
68  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
69  import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
70  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
71  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
72  import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
73  import org.apache.hadoop.hbase.exceptions.DeserializationException;
74  import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException;
75  import org.apache.hadoop.hbase.filter.Filter;
76  import org.apache.hadoop.hbase.filter.FilterBase;
77  import org.apache.hadoop.hbase.filter.FilterList;
78  import org.apache.hadoop.hbase.io.hfile.HFile;
79  import org.apache.hadoop.hbase.ipc.RpcServer;
80  import org.apache.hadoop.hbase.master.MasterServices;
81  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
82  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResult;
83  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos;
84  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsRequest;
85  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse;
86  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.ListLabelsRequest;
87  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.ListLabelsResponse;
88  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.SetAuthsRequest;
89  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabel;
90  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsRequest;
91  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
92  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsService;
93  import org.apache.hadoop.hbase.regionserver.BloomType;
94  import org.apache.hadoop.hbase.regionserver.DeleteTracker;
95  import org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy;
96  import org.apache.hadoop.hbase.regionserver.InternalScanner;
97  import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
98  import org.apache.hadoop.hbase.regionserver.OperationStatus;
99  import org.apache.hadoop.hbase.regionserver.Region;
100 import org.apache.hadoop.hbase.regionserver.RegionScanner;
101 import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
102 import org.apache.hadoop.hbase.security.AccessDeniedException;
103 import org.apache.hadoop.hbase.security.Superusers;
104 import org.apache.hadoop.hbase.security.User;
105 import org.apache.hadoop.hbase.security.access.AccessChecker;
106 import org.apache.hadoop.hbase.security.access.AccessController;
107 import org.apache.hadoop.hbase.util.ByteStringer;
108 import org.apache.hadoop.hbase.util.Bytes;
109 import org.apache.hadoop.hbase.util.Pair;
110 
111 import com.google.common.collect.Lists;
112 import com.google.common.collect.MapMaker;
113 import com.google.protobuf.ByteString;
114 import com.google.protobuf.RpcCallback;
115 import com.google.protobuf.RpcController;
116 import com.google.protobuf.Service;
117 
118 /**
119  * Coprocessor that has both the MasterObserver and RegionObserver implemented that supports in
120  * visibility labels
121  */
122 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
123 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MT_CORRECTNESS",
124   justification="FIX visibilityLabelService; Make Synchronized!!!")
125 public class VisibilityController extends BaseMasterAndRegionObserver implements
126     VisibilityLabelsService.Interface, CoprocessorService {
127 
128   private static final Log LOG = LogFactory.getLog(VisibilityController.class);
129   private static final Log AUDITLOG = LogFactory.getLog("SecurityLogger."
130       + VisibilityController.class.getName());
131   // flags if we are running on a region of the 'labels' table
132   private boolean labelsRegion = false;
133   // Flag denoting whether AcessController is available or not.
134   private boolean accessControllerAvailable = false;
135   private Configuration conf;
136   private volatile boolean initialized = false;
137   private boolean checkAuths = false;
138   /** Mapping of scanner instances to the user who created them */
139   private Map<InternalScanner,String> scannerOwners =
140       new MapMaker().weakKeys().makeMap();
141 
142   private VisibilityLabelService visibilityLabelService; // FindBugs: MT_CORRECTNESS FIX!!!
143 
144   /** if we are active, usually false, only true if "hbase.security.authorization"
145     has been set to true in site configuration */
146   private boolean authorizationEnabled;
147 
148   // Add to this list if there are any reserved tag types
149   private static ArrayList<Byte> RESERVED_VIS_TAG_TYPES = new ArrayList<Byte>();
150   static {
151     RESERVED_VIS_TAG_TYPES.add(TagType.VISIBILITY_TAG_TYPE);
152     RESERVED_VIS_TAG_TYPES.add(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE);
153     RESERVED_VIS_TAG_TYPES.add(TagType.STRING_VIS_TAG_TYPE);
154   }
155 
156   public static boolean isAuthorizationSupported(Configuration conf) {
157     return AccessChecker.isAuthorizationSupported(conf);
158   }
159 
160   public static boolean isCellAuthorizationSupported(Configuration conf) {
161     return AccessChecker.isAuthorizationSupported(conf);
162   }
163 
164   @Override
165   public void start(CoprocessorEnvironment env) throws IOException {
166     this.conf = env.getConfiguration();
167 
168     authorizationEnabled = isAuthorizationSupported(conf);
169     if (!authorizationEnabled) {
170       LOG.warn("The VisibilityController has been loaded with authorization checks disabled.");
171     }
172 
173     if (HFile.getFormatVersion(conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) {
174       throw new RuntimeException("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
175         + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY
176         + " accordingly.");
177     }
178 
179     if (env instanceof RegionServerCoprocessorEnvironment) {
180       throw new RuntimeException("Visibility controller should not be configured as "
181           + "'hbase.coprocessor.regionserver.classes'.");
182     }
183     // Do not create for master CPs
184     if (!(env instanceof MasterCoprocessorEnvironment)) {
185       visibilityLabelService = VisibilityLabelServiceManager.getInstance()
186           .getVisibilityLabelService(this.conf);
187     }
188   }
189 
190   @Override
191   public void stop(CoprocessorEnvironment env) throws IOException {
192 
193   }
194 
195   /********************************* Master related hooks **********************************/
196 
197   @Override
198   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
199     // Need to create the new system table for labels here
200     MasterServices master = ctx.getEnvironment().getMasterServices();
201     if (!MetaTableAccessor.tableExists(master.getConnection(), LABELS_TABLE_NAME)) {
202       HTableDescriptor labelsTable = new HTableDescriptor(LABELS_TABLE_NAME);
203       HColumnDescriptor labelsColumn = new HColumnDescriptor(LABELS_TABLE_FAMILY);
204       labelsColumn.setBloomFilterType(BloomType.NONE);
205       labelsColumn.setBlockCacheEnabled(false); // We will cache all the labels. No need of normal
206                                                  // table block cache.
207       labelsTable.addFamily(labelsColumn);
208       // Let the "labels" table having only one region always. We are not expecting too many labels in
209       // the system.
210       labelsTable.setValue(HTableDescriptor.SPLIT_POLICY,
211           DisabledRegionSplitPolicy.class.getName());
212       labelsTable.setValue(Bytes.toBytes(HConstants.DISALLOW_WRITES_IN_RECOVERING),
213           Bytes.toBytes(true));
214       master.createSystemTable(labelsTable);
215     }
216   }
217 
218   @Override
219   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
220       TableName tableName, HTableDescriptor htd) throws IOException {
221     if (!authorizationEnabled) {
222       return;
223     }
224     if (LABELS_TABLE_NAME.equals(tableName)) {
225       throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME);
226     }
227   }
228 
229   @Override
230   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName,
231       HColumnDescriptor column) throws IOException {
232     if (!authorizationEnabled) {
233       return;
234     }
235     if (LABELS_TABLE_NAME.equals(tableName)) {
236       throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME);
237     }
238   }
239 
240   @Override
241   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> ctx,
242       TableName tableName, HColumnDescriptor descriptor) throws IOException {
243     if (!authorizationEnabled) {
244       return;
245     }
246     if (LABELS_TABLE_NAME.equals(tableName)) {
247       throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME);
248     }
249   }
250 
251   @Override
252   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> ctx,
253       TableName tableName, byte[] c) throws IOException {
254     if (!authorizationEnabled) {
255       return;
256     }
257     if (LABELS_TABLE_NAME.equals(tableName)) {
258       throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME);
259     }
260   }
261 
262   @Override
263   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName)
264       throws IOException {
265     if (!authorizationEnabled) {
266       return;
267     }
268     if (LABELS_TABLE_NAME.equals(tableName)) {
269       throw new ConstraintException("Cannot disable " + LABELS_TABLE_NAME);
270     }
271   }
272 
273   /****************************** Region related hooks ******************************/
274 
275   @Override
276   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
277     // Read the entire labels table and populate the zk
278     if (e.getEnvironment().getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
279       this.labelsRegion = true;
280       synchronized (this) {
281         this.accessControllerAvailable = CoprocessorHost.getLoadedCoprocessors()
282           .contains(AccessController.class.getName());
283       }
284       // Defer the init of VisibilityLabelService on labels region until it is in recovering state.
285       if (!e.getEnvironment().getRegion().isRecovering()) {
286         initVisibilityLabelService(e.getEnvironment());
287       }
288     } else {
289       checkAuths = e.getEnvironment().getConfiguration()
290           .getBoolean(VisibilityConstants.CHECK_AUTHS_FOR_MUTATION, false);
291       initVisibilityLabelService(e.getEnvironment());
292     }
293   }
294 
295   @Override
296   public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> e) {
297     if (this.labelsRegion) {
298       initVisibilityLabelService(e.getEnvironment());
299       LOG.debug("post labels region log replay");
300     }
301   }
302 
303   private void initVisibilityLabelService(RegionCoprocessorEnvironment env) {
304     try {
305       this.visibilityLabelService.init(env);
306       this.initialized = true;
307     } catch (IOException ioe) {
308       LOG.error("Error while initializing VisibilityLabelService..", ioe);
309       throw new RuntimeException(ioe);
310     }
311   }
312 
313   @Override
314   public boolean preSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
315       final boolean newValue, final Admin.MasterSwitchType switchType) throws IOException {
316     return false;
317   }
318 
319   @Override
320   public void postSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
321       final boolean newValue, final Admin.MasterSwitchType switchType) throws IOException {
322   }
323 
324   @Override
325   public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
326       MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
327     if (c.getEnvironment().getRegion().getRegionInfo().getTable().isSystemTable()) {
328       return;
329     }
330     // TODO this can be made as a global LRU cache at HRS level?
331     Map<String, List<Tag>> labelCache = new HashMap<String, List<Tag>>();
332     for (int i = 0; i < miniBatchOp.size(); i++) {
333       Mutation m = miniBatchOp.getOperation(i);
334       CellVisibility cellVisibility = null;
335       try {
336         cellVisibility = m.getCellVisibility();
337       } catch (DeserializationException de) {
338         miniBatchOp.setOperationStatus(i,
339             new OperationStatus(SANITY_CHECK_FAILURE, de.getMessage()));
340         continue;
341       }
342       boolean sanityFailure = false;
343       boolean modifiedTagFound = false;
344       Pair<Boolean, Tag> pair = new Pair<Boolean, Tag>(false, null);
345       for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
346         pair = checkForReservedVisibilityTagPresence(cellScanner.current(), pair);
347         if (!pair.getFirst()) {
348           // Don't disallow reserved tags if authorization is disabled
349           if (authorizationEnabled) {
350             miniBatchOp.setOperationStatus(i, new OperationStatus(SANITY_CHECK_FAILURE,
351               "Mutation contains cell with reserved type tag"));
352             sanityFailure = true;
353           }
354           break;
355         } else {
356           // Indicates that the cell has a the tag which was modified in the src replication cluster
357           Tag tag = pair.getSecond();
358           if (cellVisibility == null && tag != null) {
359             // May need to store only the first one
360             cellVisibility = new CellVisibility(Bytes.toString(tag.getBuffer(), tag.getTagOffset(),
361                 tag.getTagLength()));
362             modifiedTagFound = true;
363           }
364         }
365       }
366       if (!sanityFailure) {
367         if (cellVisibility != null) {
368           String labelsExp = cellVisibility.getExpression();
369           List<Tag> visibilityTags = labelCache.get(labelsExp);
370           if (visibilityTags == null) {
371             // Don't check user auths for labels with Mutations when the user is super user
372             boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser());
373             try {
374               visibilityTags = this.visibilityLabelService.createVisibilityExpTags(labelsExp, true,
375                   authCheck);
376             } catch (InvalidLabelException e) {
377               miniBatchOp.setOperationStatus(i,
378                   new OperationStatus(SANITY_CHECK_FAILURE, e.getMessage()));
379             }
380             if (visibilityTags != null) {
381               labelCache.put(labelsExp, visibilityTags);
382             }
383           }
384           if (visibilityTags != null) {
385             List<Cell> updatedCells = new ArrayList<Cell>();
386             for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
387               Cell cell = cellScanner.current();
388               List<Tag> tags = Tag.asList(cell.getTagsArray(), cell.getTagsOffset(),
389                   cell.getTagsLength());
390               if (modifiedTagFound) {
391                 // Rewrite the tags by removing the modified tags.
392                 removeReplicationVisibilityTag(tags);
393               }
394               tags.addAll(visibilityTags);
395               Cell updatedCell = new TagRewriteCell(cell, Tag.fromList(tags));
396               updatedCells.add(updatedCell);
397             }
398             m.getFamilyCellMap().clear();
399             // Clear and add new Cells to the Mutation.
400             for (Cell cell : updatedCells) {
401               if (m instanceof Put) {
402                 Put p = (Put) m;
403                 p.add(cell);
404               } else if (m instanceof Delete) {
405                 Delete d = (Delete) m;
406                 d.addDeleteMarker(cell);
407               }
408             }
409           }
410         }
411       }
412     }
413   }
414 
415   @Override
416   public void prePrepareTimeStampForDeleteVersion(
417       ObserverContext<RegionCoprocessorEnvironment> ctx, Mutation delete, Cell cell,
418       byte[] byteNow, Get get) throws IOException {
419     // Nothing to do if we are not filtering by visibility
420     if (!authorizationEnabled) {
421       return;
422     }
423 
424     CellVisibility cellVisibility = null;
425     try {
426       cellVisibility = delete.getCellVisibility();
427     } catch (DeserializationException de) {
428       throw new IOException("Invalid cell visibility specified " + delete, de);
429     }
430     // The check for checkForReservedVisibilityTagPresence happens in preBatchMutate happens.
431     // It happens for every mutation and that would be enough.
432     List<Tag> visibilityTags = new ArrayList<Tag>();
433     if (cellVisibility != null) {
434       String labelsExp = cellVisibility.getExpression();
435       try {
436         visibilityTags = this.visibilityLabelService.createVisibilityExpTags(labelsExp, false,
437             false);
438       } catch (InvalidLabelException e) {
439         throw new IOException("Invalid cell visibility specified " + labelsExp, e);
440       }
441     }
442     get.setFilter(new DeleteVersionVisibilityExpressionFilter(visibilityTags,
443         VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT));
444     List<Cell> result = ctx.getEnvironment().getRegion().get(get, false);
445 
446     if (result.size() < get.getMaxVersions()) {
447       // Nothing to delete
448       CellUtil.updateLatestStamp(cell, byteNow, 0);
449       return;
450     }
451     if (result.size() > get.getMaxVersions()) {
452       throw new RuntimeException("Unexpected size: " + result.size()
453           + ". Results more than the max versions obtained.");
454     }
455     Cell getCell = result.get(get.getMaxVersions() - 1);
456     CellUtil.setTimestamp(cell, getCell.getTimestamp());
457 
458     // We are bypassing here because in the HRegion.updateDeleteLatestVersionTimeStamp we would
459     // update with the current timestamp after again doing a get. As the hook as already determined
460     // the needed timestamp we need to bypass here.
461     // TODO : See if HRegion.updateDeleteLatestVersionTimeStamp() could be
462     // called only if the hook is not called.
463     ctx.bypass();
464   }
465 
466   /**
467    * Checks whether cell contains any tag with type as VISIBILITY_TAG_TYPE. This
468    * tag type is reserved and should not be explicitly set by user.
469    *
470    * @param cell
471    *          - the cell under consideration
472    * @param pair - an optional pair of type <Boolean, Tag> which would be reused
473    *               if already set and new one will be created if null is passed
474    * @return a pair<Boolean, Tag> - if the boolean is false then it indicates
475    *         that the cell has a RESERVERD_VIS_TAG and with boolean as true, not
476    *         null tag indicates that a string modified tag was found.
477    */
478   private Pair<Boolean, Tag> checkForReservedVisibilityTagPresence(Cell cell,
479       Pair<Boolean, Tag> pair) throws IOException {
480     if (pair == null) {
481       pair = new Pair<Boolean, Tag>(false, null);
482     } else {
483       pair.setFirst(false);
484       pair.setSecond(null);
485     }
486     // Bypass this check when the operation is done by a system/super user.
487     // This is done because, while Replication, the Cells coming to the peer cluster with reserved
488     // typed tags and this is fine and should get added to the peer cluster table
489     if (isSystemOrSuperUser()) {
490       // Does the cell contain special tag which indicates that the replicated
491       // cell visiblilty tags
492       // have been modified
493       Tag modifiedTag = null;
494       if (cell.getTagsLength() > 0) {
495         Iterator<Tag> tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(),
496             cell.getTagsOffset(), cell.getTagsLength());
497         while (tagsIterator.hasNext()) {
498           Tag tag = tagsIterator.next();
499           if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) {
500             modifiedTag = tag;
501             break;
502           }
503         }
504       }
505       pair.setFirst(true);
506       pair.setSecond(modifiedTag);
507       return pair;
508     }
509     if (cell.getTagsLength() > 0) {
510       Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
511           cell.getTagsLength());
512       while (tagsItr.hasNext()) {
513         if (RESERVED_VIS_TAG_TYPES.contains(tagsItr.next().getType())) {
514           return pair;
515         }
516       }
517     }
518     pair.setFirst(true);
519     return pair;
520   }
521 
522   /**
523    * Checks whether cell contains any tag with type as VISIBILITY_TAG_TYPE. This
524    * tag type is reserved and should not be explicitly set by user. There are
525    * two versions of this method one that accepts pair and other without pair.
526    * In case of preAppend and preIncrement the additional operations are not
527    * needed like checking for STRING_VIS_TAG_TYPE and hence the API without pair
528    * could be used.
529    *
530    * @param cell
531    * @return true or false
532    * @throws IOException
533    */
534   private boolean checkForReservedVisibilityTagPresence(Cell cell) throws IOException {
535     // Bypass this check when the operation is done by a system/super user.
536     // This is done because, while Replication, the Cells coming to the peer
537     // cluster with reserved
538     // typed tags and this is fine and should get added to the peer cluster
539     // table
540     if (isSystemOrSuperUser()) {
541       return true;
542     }
543     if (cell.getTagsLength() > 0) {
544       Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
545           cell.getTagsLength());
546       while (tagsItr.hasNext()) {
547         if (RESERVED_VIS_TAG_TYPES.contains(tagsItr.next().getType())) {
548           return false;
549         }
550       }
551     }
552     return true;
553   }
554 
555   private void removeReplicationVisibilityTag(List<Tag> tags) throws IOException {
556     Iterator<Tag> iterator = tags.iterator();
557     while (iterator.hasNext()) {
558       Tag tag = iterator.next();
559       if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) {
560         iterator.remove();
561         break;
562       }
563     }
564   }
565 
566   @Override
567   public RegionScanner preScannerOpen(ObserverContext<RegionCoprocessorEnvironment> e, Scan scan,
568       RegionScanner s) throws IOException {
569     if (!initialized) {
570       throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized!");
571     }
572     // Nothing to do if authorization is not enabled
573     if (!authorizationEnabled) {
574       return s;
575     }
576     Region region = e.getEnvironment().getRegion();
577     Authorizations authorizations = null;
578     try {
579       authorizations = scan.getAuthorizations();
580     } catch (DeserializationException de) {
581       throw new IOException(de);
582     }
583     if (authorizations == null) {
584       // No Authorizations present for this scan/Get!
585       // In case of system tables other than "labels" just scan with out visibility check and
586       // filtering. Checking visibility labels for META and NAMESPACE table is not needed.
587       TableName table = region.getRegionInfo().getTable();
588       if (table.isSystemTable() && !table.equals(LABELS_TABLE_NAME)) {
589         return s;
590       }
591     }
592 
593     Filter visibilityLabelFilter = VisibilityUtils.createVisibilityLabelFilter(region,
594         authorizations);
595     if (visibilityLabelFilter != null) {
596       Filter filter = scan.getFilter();
597       if (filter != null) {
598         scan.setFilter(new FilterList(filter, visibilityLabelFilter));
599       } else {
600         scan.setFilter(visibilityLabelFilter);
601       }
602     }
603     return s;
604   }
605 
606   @Override
607   public DeleteTracker postInstantiateDeleteTracker(
608       ObserverContext<RegionCoprocessorEnvironment> ctx, DeleteTracker delTracker)
609       throws IOException {
610     // Nothing to do if we are not filtering by visibility
611     if (!authorizationEnabled) {
612       return delTracker;
613     }
614     Region region = ctx.getEnvironment().getRegion();
615     TableName table = region.getRegionInfo().getTable();
616     if (table.isSystemTable()) {
617       return delTracker;
618     }
619     // We are creating a new type of delete tracker here which is able to track
620     // the timestamps and also the
621     // visibility tags per cell. The covering cells are determined not only
622     // based on the delete type and ts
623     // but also on the visibility expression matching.
624     return new VisibilityScanDeleteTracker();
625   }
626 
627   @Override
628   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
629       final Scan scan, final RegionScanner s) throws IOException {
630     User user = VisibilityUtils.getActiveUser();
631     if (user != null && user.getShortName() != null) {
632       scannerOwners.put(s, user.getShortName());
633     }
634     return s;
635   }
636 
637   @Override
638   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
639       final InternalScanner s, final List<Result> result, final int limit, final boolean hasNext)
640       throws IOException {
641     requireScannerOwner(s);
642     return hasNext;
643   }
644 
645   @Override
646   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
647       final InternalScanner s) throws IOException {
648     requireScannerOwner(s);
649   }
650 
651   @Override
652   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
653       final InternalScanner s) throws IOException {
654     // clean up any associated owner mapping
655     scannerOwners.remove(s);
656   }
657 
658   /**
659    * Verify, when servicing an RPC, that the caller is the scanner owner. If so, we assume that
660    * access control is correctly enforced based on the checks performed in preScannerOpen()
661    */
662   private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
663     if (!RpcServer.isInRpcCallContext())
664       return;
665     String requestUName = RpcServer.getRequestUserName();
666     String owner = scannerOwners.get(s);
667     if (authorizationEnabled && owner != null && !owner.equals(requestUName)) {
668       throw new AccessDeniedException("User '" + requestUName + "' is not the scanner owner!");
669     }
670   }
671 
672   @Override
673   public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
674       List<Cell> results) throws IOException {
675     if (!initialized) {
676       throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized");
677     }
678     // Nothing useful to do if authorization is not enabled
679     if (!authorizationEnabled) {
680       return;
681     }
682     Region region = e.getEnvironment().getRegion();
683     Authorizations authorizations = null;
684     try {
685       authorizations = get.getAuthorizations();
686     } catch (DeserializationException de) {
687       throw new IOException(de);
688     }
689     if (authorizations == null) {
690       // No Authorizations present for this scan/Get!
691       // In case of system tables other than "labels" just scan with out visibility check and
692       // filtering. Checking visibility labels for META and NAMESPACE table is not needed.
693       TableName table = region.getRegionInfo().getTable();
694       if (table.isSystemTable() && !table.equals(LABELS_TABLE_NAME)) {
695         return;
696       }
697     }
698     Filter visibilityLabelFilter = VisibilityUtils.createVisibilityLabelFilter(e.getEnvironment()
699         .getRegion(), authorizations);
700     if (visibilityLabelFilter != null) {
701       Filter filter = get.getFilter();
702       if (filter != null) {
703         get.setFilter(new FilterList(filter, visibilityLabelFilter));
704       } else {
705         get.setFilter(visibilityLabelFilter);
706       }
707     }
708   }
709 
710   private boolean isSystemOrSuperUser() throws IOException {
711     return Superusers.isSuperUser(VisibilityUtils.getActiveUser());
712   }
713 
714   @Override
715   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> e, Append append)
716       throws IOException {
717     // If authorization is not enabled, we don't care about reserved tags
718     if (!authorizationEnabled) {
719       return null;
720     }
721     for (CellScanner cellScanner = append.cellScanner(); cellScanner.advance();) {
722       if (!checkForReservedVisibilityTagPresence(cellScanner.current())) {
723         throw new FailedSanityCheckException("Append contains cell with reserved type tag");
724       }
725     }
726     return null;
727   }
728 
729   @Override
730   public Result preIncrement(ObserverContext<RegionCoprocessorEnvironment> e, Increment increment)
731       throws IOException {
732     // If authorization is not enabled, we don't care about reserved tags
733     if (!authorizationEnabled) {
734       return null;
735     }
736     for (CellScanner cellScanner = increment.cellScanner(); cellScanner.advance();) {
737       if (!checkForReservedVisibilityTagPresence(cellScanner.current())) {
738         throw new FailedSanityCheckException("Increment contains cell with reserved type tag");
739       }
740     }
741     return null;
742   }
743 
744   @Override
745   public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
746       MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
747     List<Tag> tags = Lists.newArrayList();
748     CellVisibility cellVisibility = null;
749     try {
750       cellVisibility = mutation.getCellVisibility();
751     } catch (DeserializationException e) {
752       throw new IOException(e);
753     }
754     if (cellVisibility == null) {
755       return newCell;
756     }
757     // Prepend new visibility tags to a new list of tags for the cell
758     // Don't check user auths for labels with Mutations when the user is super user
759     boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser());
760     tags.addAll(this.visibilityLabelService.createVisibilityExpTags(cellVisibility.getExpression(),
761         true, authCheck));
762     // Save an object allocation where we can
763     if (newCell.getTagsLength() > 0) {
764       // Carry forward all other tags
765       Iterator<Tag> tagsItr = CellUtil.tagsIterator(newCell.getTagsArray(),
766           newCell.getTagsOffset(), newCell.getTagsLength());
767       while (tagsItr.hasNext()) {
768         Tag tag = tagsItr.next();
769         if (tag.getType() != TagType.VISIBILITY_TAG_TYPE
770             && tag.getType() != TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) {
771           tags.add(tag);
772         }
773       }
774     }
775 
776     Cell rewriteCell = new TagRewriteCell(newCell, Tag.fromList(tags));
777     return rewriteCell;
778   }
779 
780   @Override
781   public Service getService() {
782     return VisibilityLabelsProtos.VisibilityLabelsService.newReflectiveService(this);
783   }
784 
785   /****************************** VisibilityEndpoint service related methods ******************************/
786   @Override
787   public synchronized void addLabels(RpcController controller, VisibilityLabelsRequest request,
788       RpcCallback<VisibilityLabelsResponse> done) {
789     VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
790     List<VisibilityLabel> visLabels = request.getVisLabelList();
791     if (!initialized) {
792       setExceptionResults(visLabels.size(),
793         new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"),
794         response);
795     } else {
796       List<byte[]> labels = new ArrayList<byte[]>(visLabels.size());
797       try {
798         if (authorizationEnabled) {
799           checkCallingUserAuth();
800         }
801         RegionActionResult successResult = RegionActionResult.newBuilder().build();
802         for (VisibilityLabel visLabel : visLabels) {
803           byte[] label = visLabel.getLabel().toByteArray();
804           labels.add(label);
805           response.addResult(successResult); // Just mark as success. Later it will get reset
806                                              // based on the result from
807                                              // visibilityLabelService.addLabels ()
808         }
809         if (!labels.isEmpty()) {
810           OperationStatus[] opStatus = this.visibilityLabelService.addLabels(labels);
811           logResult(true, "addLabels", "Adding labels allowed", null, labels, null);
812           int i = 0;
813           for (OperationStatus status : opStatus) {
814             while (response.getResult(i) != successResult)
815               i++;
816             if (status.getOperationStatusCode() != SUCCESS) {
817               RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
818               failureResultBuilder.setException(ResponseConverter
819                   .buildException(new DoNotRetryIOException(status.getExceptionMsg())));
820               response.setResult(i, failureResultBuilder.build());
821             }
822             i++;
823           }
824         }
825       } catch (AccessDeniedException e) {
826         logResult(false, "addLabels", e.getMessage(), null, labels, null);
827         LOG.error("User is not having required permissions to add labels", e);
828         setExceptionResults(visLabels.size(), e, response);
829       } catch (IOException e) {
830         LOG.error(e);
831         setExceptionResults(visLabels.size(), e, response);
832       }
833     }
834     done.run(response.build());
835   }
836 
837   private void setExceptionResults(int size, IOException e,
838       VisibilityLabelsResponse.Builder response) {
839     RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
840     failureResultBuilder.setException(ResponseConverter.buildException(e));
841     RegionActionResult failureResult = failureResultBuilder.build();
842     for (int i = 0; i < size; i++) {
843       response.addResult(i, failureResult);
844     }
845   }
846 
847   @Override
848   public synchronized void setAuths(RpcController controller, SetAuthsRequest request,
849       RpcCallback<VisibilityLabelsResponse> done) {
850     VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
851     List<ByteString> auths = request.getAuthList();
852     if (!initialized) {
853       setExceptionResults(auths.size(),
854         new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"),
855         response);
856     } else {
857       byte[] user = request.getUser().toByteArray();
858       List<byte[]> labelAuths = new ArrayList<byte[]>(auths.size());
859       try {
860         if (authorizationEnabled) {
861           checkCallingUserAuth();
862         }
863         for (ByteString authBS : auths) {
864           labelAuths.add(authBS.toByteArray());
865         }
866         OperationStatus[] opStatus = this.visibilityLabelService.setAuths(user, labelAuths);
867         logResult(true, "setAuths", "Setting authorization for labels allowed", user, labelAuths,
868           null);
869         RegionActionResult successResult = RegionActionResult.newBuilder().build();
870         for (OperationStatus status : opStatus) {
871           if (status.getOperationStatusCode() == SUCCESS) {
872             response.addResult(successResult);
873           } else {
874             RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
875             failureResultBuilder.setException(ResponseConverter
876                 .buildException(new DoNotRetryIOException(status.getExceptionMsg())));
877             response.addResult(failureResultBuilder.build());
878           }
879         }
880       } catch (AccessDeniedException e) {
881         logResult(false, "setAuths", e.getMessage(), user, labelAuths, null);
882         LOG.error("User is not having required permissions to set authorization", e);
883         setExceptionResults(auths.size(), e, response);
884       } catch (IOException e) {
885         LOG.error(e);
886         setExceptionResults(auths.size(), e, response);
887       }
888     }
889     done.run(response.build());
890   }
891 
892   private void logResult(boolean isAllowed, String request, String reason, byte[] user,
893       List<byte[]> labelAuths, String regex) {
894     if (AUDITLOG.isTraceEnabled()) {
895       // This is more duplicated code!
896       InetAddress remoteAddr = RpcServer.getRemoteAddress();
897       List<String> labelAuthsStr = new ArrayList<>();
898       if (labelAuths != null) {
899         int labelAuthsSize = labelAuths.size();
900         labelAuthsStr = new ArrayList<>(labelAuthsSize);
901         for (int i = 0; i < labelAuthsSize; i++) {
902           labelAuthsStr.add(Bytes.toString(labelAuths.get(i)));
903         }
904       }
905 
906       User requestingUser = null;
907       try {
908         requestingUser = VisibilityUtils.getActiveUser();
909       } catch (IOException e) {
910         LOG.warn("Failed to get active system user.");
911         LOG.debug("Details on failure to get active system user.", e);
912       }
913       AUDITLOG.trace("Access " + (isAllowed ? "allowed" : "denied") + " for user "
914           + (requestingUser != null ? requestingUser.getShortName() : "UNKNOWN") + "; reason: "
915           + reason + "; remote address: " + (remoteAddr != null ? remoteAddr : "") + "; request: "
916           + request + "; user: " + (user != null ? Bytes.toShort(user) : "null") + "; labels: "
917           + labelAuthsStr + "; regex: " + regex);
918     }
919   }
920 
921   @Override
922   public synchronized void getAuths(RpcController controller, GetAuthsRequest request,
923       RpcCallback<GetAuthsResponse> done) {
924     GetAuthsResponse.Builder response = GetAuthsResponse.newBuilder();
925     if (!initialized) {
926       controller.setFailed("VisibilityController not yet initialized");
927     } else {
928       byte[] user = request.getUser().toByteArray();
929       List<String> labels = null;
930       try {
931         // We do ACL check here as we create scanner directly on region. It will not make calls to
932         // AccessController CP methods.
933         if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
934           User requestingUser = VisibilityUtils.getActiveUser();
935           throw new AccessDeniedException("User '"
936               + (requestingUser != null ? requestingUser.getShortName() : "null")
937               + "' is not authorized to perform this action.");
938         }
939         if (AuthUtil.isGroupPrincipal(Bytes.toString(user))) {
940           // For backward compatibility. Previous custom visibilityLabelService
941           // implementation may not have getGroupAuths
942           try {
943             this.visibilityLabelService.getClass().getDeclaredMethod("getGroupAuths",
944                 new Class[] { String[].class, Boolean.TYPE });
945           } catch (SecurityException e) {
946             throw new AccessDeniedException("Failed to obtain getGroupAuths implementation");
947           } catch (NoSuchMethodException e) {
948             throw new AccessDeniedException(
949                 "Get group auth is not supported in this implementation");
950           }
951           String group = AuthUtil.getGroupName(Bytes.toString(user));
952           labels = this.visibilityLabelService.getGroupAuths(new String[] { group }, false);
953         } else {
954           labels = this.visibilityLabelService.getAuths(user, false);
955         }
956         logResult(true, "getAuths", "Get authorizations for user allowed", user, null, null);
957       } catch (AccessDeniedException e) {
958         logResult(false, "getAuths", e.getMessage(), user, null, null);
959         ResponseConverter.setControllerException(controller, e);
960       } catch (IOException e) {
961         ResponseConverter.setControllerException(controller, e);
962       }
963       response.setUser(request.getUser());
964       if (labels != null) {
965         for (String label : labels) {
966           response.addAuth(ByteStringer.wrap(Bytes.toBytes(label)));
967         }
968       }
969     }
970     done.run(response.build());
971   }
972 
973   @Override
974   public synchronized void clearAuths(RpcController controller, SetAuthsRequest request,
975       RpcCallback<VisibilityLabelsResponse> done) {
976     VisibilityLabelsResponse.Builder response = VisibilityLabelsResponse.newBuilder();
977     List<ByteString> auths = request.getAuthList();
978     if (!initialized) {
979       setExceptionResults(auths.size(), new CoprocessorException(
980           "VisibilityController not yet initialized"), response);
981     } else {
982       byte[] requestUser = request.getUser().toByteArray();
983       List<byte[]> labelAuths = new ArrayList<byte[]>(auths.size());
984       try {
985         // When AC is ON, do AC based user auth check
986         if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
987           User user = VisibilityUtils.getActiveUser();
988           throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null")
989               + " is not authorized to perform this action.");
990         }
991         if (authorizationEnabled) {
992           checkCallingUserAuth(); // When AC is not in place the calling user should have
993                                   // SYSTEM_LABEL auth to do this action.
994         }
995         for (ByteString authBS : auths) {
996           labelAuths.add(authBS.toByteArray());
997         }
998 
999         OperationStatus[] opStatus =
1000             this.visibilityLabelService.clearAuths(requestUser, labelAuths);
1001         logResult(true, "clearAuths", "Removing authorization for labels allowed", requestUser,
1002           labelAuths, null);
1003         RegionActionResult successResult = RegionActionResult.newBuilder().build();
1004         for (OperationStatus status : opStatus) {
1005           if (status.getOperationStatusCode() == SUCCESS) {
1006             response.addResult(successResult);
1007           } else {
1008             RegionActionResult.Builder failureResultBuilder = RegionActionResult.newBuilder();
1009             failureResultBuilder.setException(ResponseConverter
1010                 .buildException(new DoNotRetryIOException(status.getExceptionMsg())));
1011             response.addResult(failureResultBuilder.build());
1012           }
1013         }
1014       } catch (AccessDeniedException e) {
1015         logResult(false, "clearAuths", e.getMessage(), requestUser, labelAuths, null);
1016         LOG.error("User is not having required permissions to clear authorization", e);
1017         setExceptionResults(auths.size(), e, response);
1018       } catch (IOException e) {
1019         LOG.error(e);
1020         setExceptionResults(auths.size(), e, response);
1021       }
1022     }
1023     done.run(response.build());
1024   }
1025 
1026   @Override
1027   public synchronized void listLabels(RpcController controller, ListLabelsRequest request,
1028       RpcCallback<ListLabelsResponse> done) {
1029     ListLabelsResponse.Builder response = ListLabelsResponse.newBuilder();
1030     if (!initialized) {
1031       controller.setFailed("VisibilityController not yet initialized");
1032     } else {
1033       List<String> labels = null;
1034       String regex = request.hasRegex() ? request.getRegex() : null;
1035       try {
1036         // We do ACL check here as we create scanner directly on region. It will not make calls to
1037         // AccessController CP methods.
1038         if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) {
1039           User requestingUser = VisibilityUtils.getActiveUser();
1040           throw new AccessDeniedException("User '"
1041               + (requestingUser != null ? requestingUser.getShortName() : "null")
1042               + "' is not authorized to perform this action.");
1043         }
1044         labels = this.visibilityLabelService.listLabels(regex);
1045         logResult(false, "listLabels", "Listing labels allowed", null, null, regex);
1046       } catch (AccessDeniedException e) {
1047         logResult(false, "listLabels", e.getMessage(), null, null, regex);
1048         ResponseConverter.setControllerException(controller, e);
1049       } catch (IOException e) {
1050         ResponseConverter.setControllerException(controller, e);
1051       }
1052       if (labels != null && !labels.isEmpty()) {
1053         for (String label : labels) {
1054           response.addLabel(ByteStringer.wrap(Bytes.toBytes(label)));
1055         }
1056       }
1057     }
1058     done.run(response.build());
1059   }
1060 
1061   private void checkCallingUserAuth() throws IOException {
1062     if (!authorizationEnabled) { // Redundant, but just in case
1063       return;
1064     }
1065     if (!accessControllerAvailable) {
1066       User user = VisibilityUtils.getActiveUser();
1067       if (user == null) {
1068         throw new IOException("Unable to retrieve calling user");
1069       }
1070       boolean havingSystemAuth = false;
1071       try {
1072         this.visibilityLabelService.getClass().getDeclaredMethod("havingSystemAuth",
1073             new Class[] { User.class });
1074         havingSystemAuth = this.visibilityLabelService.havingSystemAuth(user);
1075       } catch (SecurityException e) {
1076         // Just consider this as AccessDeniedException
1077       } catch (NoSuchMethodException e) {
1078         // VLS not having havingSystemAuth(User) method. Go with deprecated havingSystemAuth(byte[])
1079         // method invoke
1080         havingSystemAuth = this.visibilityLabelService.havingSystemAuth(Bytes.toBytes(user
1081             .getShortName()));
1082       }
1083       if (!havingSystemAuth) {
1084         throw new AccessDeniedException("User '" + user.getShortName()
1085             + "' is not authorized to perform this action.");
1086       }
1087     }
1088   }
1089 
1090   private static class DeleteVersionVisibilityExpressionFilter extends FilterBase {
1091     private List<Tag> deleteCellVisTags;
1092     private Byte deleteCellVisTagsFormat;
1093 
1094     public DeleteVersionVisibilityExpressionFilter(List<Tag> deleteCellVisTags,
1095         Byte deleteCellVisTagsFormat) {
1096       this.deleteCellVisTags = deleteCellVisTags;
1097       this.deleteCellVisTagsFormat = deleteCellVisTagsFormat;
1098     }
1099 
1100     @Override
1101     public ReturnCode filterKeyValue(Cell cell) throws IOException {
1102       List<Tag> putVisTags = new ArrayList<Tag>();
1103       Byte putCellVisTagsFormat = VisibilityUtils.extractVisibilityTags(cell, putVisTags);
1104       if (putVisTags.isEmpty() && deleteCellVisTags.isEmpty()) {
1105         // Early out if there are no tags in the cell
1106         return ReturnCode.INCLUDE;
1107       }
1108       boolean matchFound = VisibilityLabelServiceManager
1109           .getInstance().getVisibilityLabelService()
1110           .matchVisibility(putVisTags, putCellVisTagsFormat, deleteCellVisTags,
1111               deleteCellVisTagsFormat);
1112       return matchFound ? ReturnCode.INCLUDE : ReturnCode.SKIP;
1113     }
1114 
1115     // Override here explicitly as the method in super class FilterBase might do a KeyValue recreate.
1116     // See HBASE-12068
1117     @Override
1118     public Cell transformCell(Cell v) {
1119       return v;
1120     }
1121 
1122     @Override
1123     public boolean equals(Object obj) {
1124       if (!(obj instanceof DeleteVersionVisibilityExpressionFilter)) {
1125         return false;
1126       }
1127       if (this == obj){
1128         return true;
1129       }
1130       DeleteVersionVisibilityExpressionFilter f = (DeleteVersionVisibilityExpressionFilter)obj;
1131       return this.deleteCellVisTags.equals(f.deleteCellVisTags) &&
1132           this.deleteCellVisTagsFormat.equals(f.deleteCellVisTagsFormat);
1133     }
1134 
1135     @Override
1136     public int hashCode() {
1137       return Objects.hash(this.deleteCellVisTags, this.deleteCellVisTagsFormat);
1138     }
1139   }
1140 
1141   /**
1142    * A RegionServerObserver impl that provides the custom
1143    * VisibilityReplicationEndpoint. This class should be configured as the
1144    * 'hbase.coprocessor.regionserver.classes' for the visibility tags to be
1145    * replicated as string.  The value for the configuration should be
1146    * 'org.apache.hadoop.hbase.security.visibility.VisibilityController$VisibilityReplication'.
1147    */
1148   public static class VisibilityReplication extends BaseRegionServerObserver {
1149     private Configuration conf;
1150     private VisibilityLabelService visibilityLabelService;
1151 
1152     @Override
1153     public void start(CoprocessorEnvironment env) throws IOException {
1154       this.conf = env.getConfiguration();
1155       visibilityLabelService = VisibilityLabelServiceManager.getInstance()
1156           .getVisibilityLabelService(this.conf);
1157     }
1158 
1159     @Override
1160     public void stop(CoprocessorEnvironment env) throws IOException {
1161     }
1162 
1163     @Override
1164     public ReplicationEndpoint postCreateReplicationEndPoint(
1165         ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
1166       return new VisibilityReplicationEndpoint(endpoint, visibilityLabelService);
1167     }
1168   }
1169 }