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  package org.apache.hadoop.hbase.security.visibility;
19  
20  import static org.apache.hadoop.hbase.TagType.VISIBILITY_TAG_TYPE;
21  
22  import java.io.ByteArrayOutputStream;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  import java.util.Set;
33  
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.hbase.Cell;
39  import org.apache.hadoop.hbase.CellUtil;
40  import org.apache.hadoop.hbase.HColumnDescriptor;
41  import org.apache.hadoop.hbase.Tag;
42  import org.apache.hadoop.hbase.TagType;
43  import org.apache.hadoop.hbase.classification.InterfaceAudience;
44  import org.apache.hadoop.hbase.exceptions.DeserializationException;
45  import org.apache.hadoop.hbase.filter.Filter;
46  import org.apache.hadoop.hbase.io.util.StreamUtils;
47  import org.apache.hadoop.hbase.ipc.RpcServer;
48  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
49  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.MultiUserAuthorizations;
50  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.UserAuthorizations;
51  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabel;
52  import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsRequest;
53  import org.apache.hadoop.hbase.regionserver.Region;
54  import org.apache.hadoop.hbase.security.AccessDeniedException;
55  import org.apache.hadoop.hbase.security.User;
56  import org.apache.hadoop.hbase.security.visibility.expression.ExpressionNode;
57  import org.apache.hadoop.hbase.security.visibility.expression.LeafExpressionNode;
58  import org.apache.hadoop.hbase.security.visibility.expression.NonLeafExpressionNode;
59  import org.apache.hadoop.hbase.security.visibility.expression.Operator;
60  import org.apache.hadoop.hbase.util.ByteRange;
61  import org.apache.hadoop.hbase.util.ByteStringer;
62  import org.apache.hadoop.hbase.util.Bytes;
63  import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
64  import org.apache.hadoop.util.ReflectionUtils;
65  
66  /**
67   * Utility method to support visibility
68   */
69  @InterfaceAudience.Private
70  public class VisibilityUtils {
71  
72    private static final Log LOG = LogFactory.getLog(VisibilityUtils.class);
73  
74    public static final String VISIBILITY_LABEL_GENERATOR_CLASS =
75        "hbase.regionserver.scan.visibility.label.generator.class";
76    public static final String SYSTEM_LABEL = "system";
77    public static final Tag SORTED_ORDINAL_SERIALIZATION_FORMAT_TAG = new Tag(
78        TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE,
79        VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT_TAG_VAL);
80    private static final String COMMA = ",";
81  
82    private static final ExpressionParser EXP_PARSER = new ExpressionParser();
83    private static final ExpressionExpander EXP_EXPANDER = new ExpressionExpander();
84  
85    /**
86     * Creates the labels data to be written to zookeeper.
87     * @param existingLabels
88     * @return Bytes form of labels and their ordinal details to be written to zookeeper.
89     */
90    public static byte[] getDataToWriteToZooKeeper(Map<String, Integer> existingLabels) {
91      VisibilityLabelsRequest.Builder visReqBuilder = VisibilityLabelsRequest.newBuilder();
92      for (Entry<String, Integer> entry : existingLabels.entrySet()) {
93        VisibilityLabel.Builder visLabBuilder = VisibilityLabel.newBuilder();
94        visLabBuilder.setLabel(ByteStringer.wrap(Bytes.toBytes(entry.getKey())));
95        visLabBuilder.setOrdinal(entry.getValue());
96        visReqBuilder.addVisLabel(visLabBuilder.build());
97      }
98      return ProtobufUtil.prependPBMagic(visReqBuilder.build().toByteArray());
99    }
100 
101   /**
102    * Creates the user auth data to be written to zookeeper.
103    * @param userAuths
104    * @return Bytes form of user auths details to be written to zookeeper.
105    */
106   public static byte[] getUserAuthsDataToWriteToZooKeeper(Map<String, List<Integer>> userAuths) {
107     MultiUserAuthorizations.Builder builder = MultiUserAuthorizations.newBuilder();
108     for (Entry<String, List<Integer>> entry : userAuths.entrySet()) {
109       UserAuthorizations.Builder userAuthsBuilder = UserAuthorizations.newBuilder();
110       userAuthsBuilder.setUser(ByteStringer.wrap(Bytes.toBytes(entry.getKey())));
111       for (Integer label : entry.getValue()) {
112         userAuthsBuilder.addAuth(label);
113       }
114       builder.addUserAuths(userAuthsBuilder.build());
115     }
116     return ProtobufUtil.prependPBMagic(builder.build().toByteArray());
117   }
118 
119   /**
120    * Reads back from the zookeeper. The data read here is of the form written by
121    * writeToZooKeeper(Map<byte[], Integer> entries).
122    * 
123    * @param data
124    * @return Labels and their ordinal details
125    * @throws DeserializationException
126    */
127   public static List<VisibilityLabel> readLabelsFromZKData(byte[] data)
128       throws DeserializationException {
129     if (ProtobufUtil.isPBMagicPrefix(data)) {
130       int pblen = ProtobufUtil.lengthOfPBMagic();
131       try {
132         VisibilityLabelsRequest.Builder builder = VisibilityLabelsRequest.newBuilder();
133         ProtobufUtil.mergeFrom(builder, data, pblen, data.length - pblen);
134         return builder.getVisLabelList();
135       } catch (IOException e) {
136         throw new DeserializationException(e);
137       }
138     }
139     return null;
140   }
141 
142   /**
143    * Reads back User auth data written to zookeeper.
144    * @param data
145    * @return User auth details
146    * @throws DeserializationException
147    */
148   public static MultiUserAuthorizations readUserAuthsFromZKData(byte[] data) 
149       throws DeserializationException {
150     if (ProtobufUtil.isPBMagicPrefix(data)) {
151       int pblen = ProtobufUtil.lengthOfPBMagic();
152       try {
153         MultiUserAuthorizations.Builder builder = MultiUserAuthorizations.newBuilder();
154         ProtobufUtil.mergeFrom(builder, data, pblen, data.length - pblen);
155         return builder.build();
156       } catch (IOException e) {
157         throw new DeserializationException(e);
158       }
159     }
160     return null;
161   }
162 
163   /**
164    * @param conf The configuration to use
165    * @return Stack of ScanLabelGenerator instances. ScanLabelGenerator classes can be specified in
166    *         Configuration as comma separated list using key
167    *         "hbase.regionserver.scan.visibility.label.generator.class"
168    * @throws IllegalArgumentException
169    *           when any of the specified ScanLabelGenerator class can not be loaded.
170    */
171   public static List<ScanLabelGenerator> getScanLabelGenerators(Configuration conf) {
172     // There can be n SLG specified as comma separated in conf
173     String slgClassesCommaSeparated = conf.get(VISIBILITY_LABEL_GENERATOR_CLASS);
174     // We have only System level SLGs now. The order of execution will be same as the order in the
175     // comma separated config value
176     List<ScanLabelGenerator> slgs = new ArrayList<ScanLabelGenerator>();
177     if (StringUtils.isNotEmpty(slgClassesCommaSeparated)) {
178       String[] slgClasses = slgClassesCommaSeparated.split(COMMA);
179       for (String slgClass : slgClasses) {
180         Class<? extends ScanLabelGenerator> slgKlass;
181         try {
182           slgKlass = (Class<? extends ScanLabelGenerator>) conf.getClassByName(slgClass.trim());
183           slgs.add(ReflectionUtils.newInstance(slgKlass, conf));
184         } catch (ClassNotFoundException e) {
185           throw new IllegalArgumentException("Unable to find " + slgClass, e);
186         }
187       }
188     }
189     // If no SLG is specified in conf, by default we'll add two SLGs
190     // 1. FeedUserAuthScanLabelGenerator
191     // 2. DefinedSetFilterScanLabelGenerator
192     // This stacking will achieve the following default behavior:
193     // 1. If there is no Auths in the scan, we will obtain the global defined set for the user
194     //    from the labels table.
195     // 2. If there is Auths in the scan, we will examine the passed in Auths and filter out the
196     //    labels that the user is not entitled to. Then use the resulting label set.
197     if (slgs.isEmpty()) {
198       slgs.add(ReflectionUtils.newInstance(FeedUserAuthScanLabelGenerator.class, conf));
199       slgs.add(ReflectionUtils.newInstance(DefinedSetFilterScanLabelGenerator.class, conf));
200     }
201     return slgs;
202   }
203 
204   /**
205    * Extract the visibility tags of the given Cell into the given List
206    * @param cell - the cell
207    * @param tags - the array that will be populated if visibility tags are present
208    * @return The visibility tags serialization format
209    */
210   public static Byte extractVisibilityTags(Cell cell, List<Tag> tags) {
211     Byte serializationFormat = null;
212     if (cell.getTagsLength() > 0) {
213       Iterator<Tag> tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
214           cell.getTagsLength());
215       while (tagsIterator.hasNext()) {
216         Tag tag = tagsIterator.next();
217         if (tag.getType() == TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) {
218           serializationFormat = tag.getBuffer()[tag.getTagOffset()];
219         } else if (tag.getType() == VISIBILITY_TAG_TYPE) {
220           tags.add(tag);
221         }
222       }
223     }
224     return serializationFormat;
225   }
226 
227   /**
228    * Extracts and partitions the visibility tags and nonVisibility Tags
229    *
230    * @param cell - the cell for which we would extract and partition the
231    * visibility and non visibility tags
232    * @param visTags
233    *          - all the visibilty tags of type TagType.VISIBILITY_TAG_TYPE would
234    *          be added to this list
235    * @param nonVisTags - all the non visibility tags would be added to this list
236    * @return - the serailization format of the tag. Can be null if no tags are found or
237    * if there is no visibility tag found
238    */
239   public static Byte extractAndPartitionTags(Cell cell, List<Tag> visTags,
240       List<Tag> nonVisTags) {
241     Byte serializationFormat = null;
242     if (cell.getTagsLength() > 0) {
243       Iterator<Tag> tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
244           cell.getTagsLength());
245       while (tagsIterator.hasNext()) {
246         Tag tag = tagsIterator.next();
247         if (tag.getType() == TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) {
248           serializationFormat = tag.getBuffer()[tag.getTagOffset()];
249         } else if (tag.getType() == VISIBILITY_TAG_TYPE) {
250           visTags.add(tag);
251         } else {
252           // ignore string encoded visibility expressions, will be added in replication handling
253           nonVisTags.add(tag);
254         }
255       }
256     }
257     return serializationFormat;
258   }
259 
260   public static boolean isVisibilityTagsPresent(Cell cell) {
261     if (cell.getTagsLength() == 0) {
262       return false;
263     }
264     Iterator<Tag> tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
265         cell.getTagsLength());
266     while (tagsIterator.hasNext()) {
267       Tag tag = tagsIterator.next();
268       if (tag.getType() == VISIBILITY_TAG_TYPE) {
269         return true;
270       }
271     }
272     return false;
273   }
274 
275   public static Filter createVisibilityLabelFilter(Region region, Authorizations authorizations)
276       throws IOException {
277     Map<ByteRange, Integer> cfVsMaxVersions = new HashMap<ByteRange, Integer>();
278     for (HColumnDescriptor hcd : region.getTableDesc().getFamilies()) {
279       cfVsMaxVersions.put(new SimpleMutableByteRange(hcd.getName()), hcd.getMaxVersions());
280     }
281     VisibilityLabelService vls = VisibilityLabelServiceManager.getInstance()
282         .getVisibilityLabelService();
283     Filter visibilityLabelFilter = new VisibilityLabelFilter(
284         vls.getVisibilityExpEvaluator(authorizations), cfVsMaxVersions);
285     return visibilityLabelFilter;
286   }
287 
288   /**
289    * @return User who called RPC method. For non-RPC handling, falls back to system user
290    * @throws IOException When there is IOE in getting the system user (During non-RPC handling).
291    */
292   public static User getActiveUser() throws IOException {
293     User user = RpcServer.getRequestUser();
294     if (user == null) {
295       user = User.getCurrent();
296     }
297     if (LOG.isTraceEnabled()) {
298       LOG.trace("Current active user name is " + user.getShortName());
299     }
300     return user;
301   }
302 
303   public static List<Tag> createVisibilityExpTags(String visExpression,
304       boolean withSerializationFormat, boolean checkAuths, Set<Integer> auths,
305       VisibilityLabelOrdinalProvider ordinalProvider) throws IOException {
306     ExpressionNode node = null;
307     try {
308       node = EXP_PARSER.parse(visExpression);
309     } catch (ParseException e) {
310       throw new IOException(e);
311     }
312     node = EXP_EXPANDER.expand(node);
313     List<Tag> tags = new ArrayList<Tag>();
314     ByteArrayOutputStream baos = new ByteArrayOutputStream();
315     DataOutputStream dos = new DataOutputStream(baos);
316     List<Integer> labelOrdinals = new ArrayList<Integer>();
317     // We will be adding this tag before the visibility tags and the presence of this
318     // tag indicates we are supporting deletes with cell visibility
319     if (withSerializationFormat) {
320       tags.add(VisibilityUtils.SORTED_ORDINAL_SERIALIZATION_FORMAT_TAG);
321     }
322     if (node.isSingleNode()) {
323       getLabelOrdinals(node, labelOrdinals, auths, checkAuths, ordinalProvider);
324       writeLabelOrdinalsToStream(labelOrdinals, dos);
325       tags.add(new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray()));
326       baos.reset();
327     } else {
328       NonLeafExpressionNode nlNode = (NonLeafExpressionNode) node;
329       if (nlNode.getOperator() == Operator.OR) {
330         for (ExpressionNode child : nlNode.getChildExps()) {
331           getLabelOrdinals(child, labelOrdinals, auths, checkAuths, ordinalProvider);
332           writeLabelOrdinalsToStream(labelOrdinals, dos);
333           tags.add(new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray()));
334           baos.reset();
335           labelOrdinals.clear();
336         }
337       } else {
338         getLabelOrdinals(nlNode, labelOrdinals, auths, checkAuths, ordinalProvider);
339         writeLabelOrdinalsToStream(labelOrdinals, dos);
340         tags.add(new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray()));
341         baos.reset();
342       }
343     }
344     return tags;
345   }
346 
347   private static void getLabelOrdinals(ExpressionNode node, List<Integer> labelOrdinals,
348       Set<Integer> auths, boolean checkAuths, VisibilityLabelOrdinalProvider ordinalProvider)
349       throws IOException, InvalidLabelException {
350     if (node.isSingleNode()) {
351       String identifier = null;
352       int labelOrdinal = 0;
353       if (node instanceof LeafExpressionNode) {
354         identifier = ((LeafExpressionNode) node).getIdentifier();
355         if (LOG.isTraceEnabled()) {
356           LOG.trace("The identifier is " + identifier);
357         }
358         labelOrdinal = ordinalProvider.getLabelOrdinal(identifier);
359         checkAuths(auths, labelOrdinal, identifier, checkAuths);
360       } else {
361         // This is a NOT node.
362         LeafExpressionNode lNode = (LeafExpressionNode) ((NonLeafExpressionNode) node)
363             .getChildExps().get(0);
364         identifier = lNode.getIdentifier();
365         labelOrdinal = ordinalProvider.getLabelOrdinal(identifier);
366         checkAuths(auths, labelOrdinal, identifier, checkAuths);
367         labelOrdinal = -1 * labelOrdinal; // Store NOT node as -ve ordinal.
368       }
369       if (labelOrdinal == 0) {
370         throw new InvalidLabelException("Invalid visibility label " + identifier);
371       }
372       labelOrdinals.add(labelOrdinal);
373     } else {
374       List<ExpressionNode> childExps = ((NonLeafExpressionNode) node).getChildExps();
375       for (ExpressionNode child : childExps) {
376         getLabelOrdinals(child, labelOrdinals, auths, checkAuths, ordinalProvider);
377       }
378     }
379   }
380 
381   /**
382    * This will sort the passed labels in ascending oder and then will write one after the other to
383    * the passed stream.
384    * @param labelOrdinals
385    *          Unsorted label ordinals
386    * @param dos
387    *          Stream where to write the labels.
388    * @throws IOException
389    *           When IOE during writes to Stream.
390    */
391   private static void writeLabelOrdinalsToStream(List<Integer> labelOrdinals, DataOutputStream dos)
392       throws IOException {
393     Collections.sort(labelOrdinals);
394     for (Integer labelOrdinal : labelOrdinals) {
395       StreamUtils.writeRawVInt32(dos, labelOrdinal);
396     }
397   }
398 
399   private static void checkAuths(Set<Integer> auths, int labelOrdinal, String identifier,
400       boolean checkAuths) throws IOException {
401     if (checkAuths) {
402       if (auths == null || (!auths.contains(labelOrdinal))) {
403         throw new AccessDeniedException("Visibility label " + identifier
404             + " not authorized for the user " + VisibilityUtils.getActiveUser().getShortName());
405       }
406     }
407   }
408 }