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