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