View Javadoc

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