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