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