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