View Javadoc

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