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  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
22  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
23  import static org.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.DataOutputStream;
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Set;
34  
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.AuthUtil;
39  import org.apache.hadoop.hbase.Cell;
40  import org.apache.hadoop.hbase.CellUtil;
41  import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
42  import org.apache.hadoop.hbase.Tag;
43  import org.apache.hadoop.hbase.TagType;
44  import org.apache.hadoop.hbase.classification.InterfaceAudience;
45  import org.apache.hadoop.hbase.client.Delete;
46  import org.apache.hadoop.hbase.client.Get;
47  import org.apache.hadoop.hbase.client.HTable;
48  import org.apache.hadoop.hbase.client.Put;
49  import org.apache.hadoop.hbase.client.Result;
50  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
51  import org.apache.hadoop.hbase.regionserver.HRegion;
52  import org.apache.hadoop.hbase.regionserver.OperationStatus;
53  import org.apache.hadoop.hbase.security.Superusers;
54  import org.apache.hadoop.hbase.security.User;
55  import org.apache.hadoop.hbase.security.visibility.expression.ExpressionNode;
56  import org.apache.hadoop.hbase.security.visibility.expression.LeafExpressionNode;
57  import org.apache.hadoop.hbase.security.visibility.expression.NonLeafExpressionNode;
58  import org.apache.hadoop.hbase.security.visibility.expression.Operator;
59  import org.apache.hadoop.hbase.util.Bytes;
60  
61  /**
62   * This is a VisibilityLabelService where labels in Mutation's visibility
63   * expression will be persisted as Strings itself rather than ordinals in
64   * 'labels' table. Also there is no need to add labels to the system, prior to
65   * using them in Mutations/Authorizations.
66   */
67  @InterfaceAudience.Private
68  public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelService {
69  
70    private static final Log LOG = LogFactory.getLog(ExpAsStringVisibilityLabelServiceImpl.class);
71  
72    private static final byte[] DUMMY_VALUE = new byte[0];
73    private static final byte STRING_SERIALIZATION_FORMAT = 2;
74    private static final Tag STRING_SERIALIZATION_FORMAT_TAG = new Tag(
75        TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE,
76        new byte[] { STRING_SERIALIZATION_FORMAT });
77    private final ExpressionParser expressionParser = new ExpressionParser();
78    private final ExpressionExpander expressionExpander = new ExpressionExpander();
79    private Configuration conf;
80    private HRegion labelsRegion;
81    private List<ScanLabelGenerator> scanLabelGenerators;
82  
83    @Override
84    public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
85      // Not doing specific label add. We will just add labels in Mutation
86      // visibility expression as it
87      // is along with every cell.
88      OperationStatus[] status = new OperationStatus[labels.size()];
89      for (int i = 0; i < labels.size(); i++) {
90        status[i] = new OperationStatus(OperationStatusCode.SUCCESS);
91      }
92      return status;
93    }
94  
95    @Override
96    public OperationStatus[] setAuths(byte[] user, List<byte[]> authLabels) throws IOException {
97      assert labelsRegion != null;
98      OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
99      Put p = new Put(user);
100     for (byte[] auth : authLabels) {
101       p.addImmutable(LABELS_TABLE_FAMILY, auth, DUMMY_VALUE);
102     }
103     this.labelsRegion.put(p);
104     // This is a testing impl and so not doing any caching
105     for (int i = 0; i < authLabels.size(); i++) {
106       finalOpStatus[i] = new OperationStatus(OperationStatusCode.SUCCESS);
107     }
108     return finalOpStatus;
109   }
110 
111   @Override
112   public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
113     assert labelsRegion != null;
114     OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
115     List<String> currentAuths;
116     if (AuthUtil.isGroupPrincipal(Bytes.toString(user))) {
117       String group = AuthUtil.getGroupName(Bytes.toString(user));
118       currentAuths = this.getGroupAuths(new String[]{group}, true);
119     }
120     else {
121       currentAuths = this.getUserAuths(user, true);
122     }
123     Delete d = new Delete(user);
124     int i = 0;
125     for (byte[] authLabel : authLabels) {
126       String authLabelStr = Bytes.toString(authLabel);
127       if (currentAuths.contains(authLabelStr)) {
128         d.deleteColumns(LABELS_TABLE_FAMILY, authLabel);
129       } else {
130         // This label is not set for the user.
131         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
132             new InvalidLabelException("Label '" + authLabelStr + "' is not set for the user "
133                 + Bytes.toString(user)));
134       }
135       i++;
136     }
137     this.labelsRegion.delete(d);
138     // This is a testing impl and so not doing any caching
139     for (i = 0; i < authLabels.size(); i++) {
140       if (finalOpStatus[i] == null) {
141         finalOpStatus[i] = new OperationStatus(OperationStatusCode.SUCCESS);
142       }
143     }
144     return finalOpStatus;
145   }
146 
147   @Override
148   @Deprecated
149   public List<String> getAuths(byte[] user, boolean systemCall) throws IOException {
150     return getUserAuths(user, systemCall);
151   }
152 
153   @Override
154   public List<String> getUserAuths(byte[] user, boolean systemCall) throws IOException {
155     assert (labelsRegion != null || systemCall);
156     List<String> auths = new ArrayList<String>();
157     Get get = new Get(user);
158     List<Cell> cells = null;
159     if (labelsRegion == null) {
160       HTable table = null;
161       try {
162         table = new HTable(conf, VisibilityConstants.LABELS_TABLE_NAME);
163         Result result = table.get(get);
164         cells = result.listCells();
165       } finally {
166         if (table != null) {
167           table.close();
168         }
169       }
170     } else {
171       cells = this.labelsRegion.get(get, false);
172     }
173     if (cells != null) {
174       for (Cell cell : cells) {
175         String auth = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
176           cell.getQualifierLength());
177         auths.add(auth);
178       }
179     }
180     return auths;
181   }
182 
183   @Override
184   public List<String> getGroupAuths(String[] groups, boolean systemCall) throws IOException {
185     assert (labelsRegion != null || systemCall);
186     List<String> auths = new ArrayList<String>();
187     if (groups != null && groups.length > 0) {
188       for (String group : groups) {
189         Get get = new Get(Bytes.toBytes(AuthUtil.toGroupEntry(group)));
190         List<Cell> cells = null;
191         if (labelsRegion == null) {
192           HTable table = null;
193           try {
194             table = new HTable(conf, VisibilityConstants.LABELS_TABLE_NAME);
195             Result result = table.get(get);
196             cells = result.listCells();
197           } finally {
198             if (table != null) {
199               table.close();
200             }
201           }
202         } else {
203           cells = this.labelsRegion.get(get, false);
204         }
205         if (cells != null) {
206           for (Cell cell : cells) {
207             String auth = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
208               cell.getQualifierLength());
209             auths.add(auth);
210           }
211         }
212       }
213     }
214     return auths;
215   }
216 
217   @Override
218   public List<String> listLabels(String regex) throws IOException {
219     // return an empty list for this implementation.
220     return new ArrayList<String>();
221   }
222 
223   @Override
224   public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat,
225       boolean checkAuths) throws IOException {
226     ExpressionNode node = null;
227     try {
228       node = this.expressionParser.parse(visExpression);
229     } catch (ParseException e) {
230       throw new IOException(e);
231     }
232     node = this.expressionExpander.expand(node);
233     List<Tag> tags = new ArrayList<Tag>();
234     if (withSerializationFormat) {
235       tags.add(STRING_SERIALIZATION_FORMAT_TAG);
236     }
237     if (node instanceof NonLeafExpressionNode
238         && ((NonLeafExpressionNode) node).getOperator() == Operator.OR) {
239       for (ExpressionNode child : ((NonLeafExpressionNode) node).getChildExps()) {
240         tags.add(createTag(child));
241       }
242     } else {
243       tags.add(createTag(node));
244     }
245     return tags;
246   }
247 
248   @Override
249   public VisibilityExpEvaluator getVisibilityExpEvaluator(Authorizations authorizations)
250       throws IOException {
251     // If a super user issues a get/scan, he should be able to scan the cells
252     // irrespective of the Visibility labels
253     if (isReadFromSystemAuthUser()) {
254       return new VisibilityExpEvaluator() {
255         @Override
256         public boolean evaluate(Cell cell) throws IOException {
257           return true;
258         }
259       };
260     }
261     List<String> authLabels = null;
262     for (ScanLabelGenerator scanLabelGenerator : scanLabelGenerators) {
263       try {
264         // null authorizations to be handled inside SLG impl.
265         authLabels = scanLabelGenerator.getLabels(VisibilityUtils.getActiveUser(), authorizations);
266         authLabels = (authLabels == null) ? new ArrayList<String>() : authLabels;
267         authorizations = new Authorizations(authLabels);
268       } catch (Throwable t) {
269         LOG.error(t);
270         throw new IOException(t);
271       }
272     }
273     final List<String> authLabelsFinal = authLabels;
274     return new VisibilityExpEvaluator() {
275       @Override
276       public boolean evaluate(Cell cell) throws IOException {
277         boolean visibilityTagPresent = false;
278         // Save an object allocation where we can
279         if (cell.getTagsLengthUnsigned() > 0) {
280           Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
281               cell.getTagsLengthUnsigned());
282           while (tagsItr.hasNext()) {
283             boolean includeKV = true;
284             Tag tag = tagsItr.next();
285             if (tag.getType() == VISIBILITY_TAG_TYPE) {
286               visibilityTagPresent = true;
287               int offset = tag.getTagOffset();
288               int endOffset = offset + tag.getTagLength();
289               while (offset < endOffset) {
290                 short len = Bytes.toShort(tag.getBuffer(), offset);
291                 offset += 2;
292                 if (len < 0) {
293                   // This is a NOT label.
294                   len = (short) (-1 * len);
295                   String label = Bytes.toString(tag.getBuffer(), offset, len);
296                   if (authLabelsFinal.contains(label)) {
297                     includeKV = false;
298                     break;
299                   }
300                 } else {
301                   String label = Bytes.toString(tag.getBuffer(), offset, len);
302                   if (!authLabelsFinal.contains(label)) {
303                     includeKV = false;
304                     break;
305                   }
306                 }
307                 offset += len;
308               }
309               if (includeKV) {
310                 // We got one visibility expression getting evaluated to true.
311                 // Good to include this
312                 // KV in the result then.
313                 return true;
314               }
315             }
316           }
317         }
318         return !(visibilityTagPresent);
319       }
320     };
321   }
322 
323   protected boolean isReadFromSystemAuthUser() throws IOException {
324     User user = VisibilityUtils.getActiveUser();
325     return havingSystemAuth(user);
326   }
327 
328   private Tag createTag(ExpressionNode node) throws IOException {
329     ByteArrayOutputStream baos = new ByteArrayOutputStream();
330     DataOutputStream dos = new DataOutputStream(baos);
331     List<String> labels = new ArrayList<String>();
332     List<String> notLabels = new ArrayList<String>();
333     extractLabels(node, labels, notLabels);
334     Collections.sort(labels);
335     Collections.sort(notLabels);
336     // We will write the NOT labels 1st followed by normal labels
337     // Each of the label we will write with label length (as short 1st) followed
338     // by the label bytes.
339     // For a NOT node we will write the label length as -ve.
340     for (String label : notLabels) {
341       byte[] bLabel = Bytes.toBytes(label);
342       short length = (short) bLabel.length;
343       length = (short) (-1 * length);
344       dos.writeShort(length);
345       dos.write(bLabel);
346     }
347     for (String label : labels) {
348       byte[] bLabel = Bytes.toBytes(label);
349       dos.writeShort(bLabel.length);
350       dos.write(bLabel);
351     }
352     return new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray());
353   }
354 
355   private void extractLabels(ExpressionNode node, List<String> labels, List<String> notLabels) {
356     if (node.isSingleNode()) {
357       if (node instanceof NonLeafExpressionNode) {
358         // This is a NOT node.
359         LeafExpressionNode lNode = (LeafExpressionNode) ((NonLeafExpressionNode) node)
360             .getChildExps().get(0);
361         notLabels.add(lNode.getIdentifier());
362       } else {
363         labels.add(((LeafExpressionNode) node).getIdentifier());
364       }
365     } else {
366       // A non leaf expression of labels with & operator.
367       NonLeafExpressionNode nlNode = (NonLeafExpressionNode) node;
368       assert nlNode.getOperator() == Operator.AND;
369       List<ExpressionNode> childExps = nlNode.getChildExps();
370       for (ExpressionNode child : childExps) {
371         extractLabels(child, labels, notLabels);
372       }
373     }
374   }
375 
376   @Override
377   public Configuration getConf() {
378     return this.conf;
379   }
380 
381   @Override
382   public void setConf(Configuration conf) {
383     this.conf = conf;
384   }
385 
386   @Override
387   public void init(RegionCoprocessorEnvironment e) throws IOException {
388     this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
389     if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
390       this.labelsRegion = e.getRegion();
391     }
392   }
393 
394   @Override
395   @Deprecated
396   public boolean havingSystemAuth(byte[] user) throws IOException {
397     // Implementation for backward compatibility
398     if (Superusers.isSuperUser(Bytes.toString(user))) {
399       return true;
400     }
401     List<String> auths = this.getUserAuths(user, true);
402     if (LOG.isTraceEnabled()) {
403       LOG.trace("The auths for user " + Bytes.toString(user) + " are " + auths);
404     }
405     return auths.contains(SYSTEM_LABEL);
406   }
407 
408   @Override
409   public boolean havingSystemAuth(User user) throws IOException {
410     if (Superusers.isSuperUser(user)) {
411       return true;
412     }
413     Set<String> auths = new HashSet<String>();
414     auths.addAll(this.getUserAuths(Bytes.toBytes(user.getShortName()), true));
415     auths.addAll(this.getGroupAuths(user.getGroupNames(), true));
416     return auths.contains(SYSTEM_LABEL);
417   }
418 
419   @Override
420   public boolean matchVisibility(List<Tag> putTags, Byte putTagsFormat, List<Tag> deleteTags,
421       Byte deleteTagsFormat) throws IOException {
422     assert putTagsFormat == STRING_SERIALIZATION_FORMAT;
423     assert deleteTagsFormat == STRING_SERIALIZATION_FORMAT;
424     return checkForMatchingVisibilityTagsWithSortedOrder(putTags, deleteTags);
425   }
426 
427   private static boolean checkForMatchingVisibilityTagsWithSortedOrder(List<Tag> putVisTags,
428       List<Tag> deleteVisTags) {
429     boolean matchFound = false;
430     // If the size does not match. Definitely we are not comparing the equal
431     // tags.
432     if ((deleteVisTags.size()) == putVisTags.size()) {
433       for (Tag tag : deleteVisTags) {
434         matchFound = false;
435         for (Tag givenTag : putVisTags) {
436           if (Bytes.equals(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength(),
437               givenTag.getBuffer(), givenTag.getTagOffset(), givenTag.getTagLength())) {
438             matchFound = true;
439             break;
440           }
441         }
442         if (!matchFound)
443           break;
444       }
445     }
446     return matchFound;
447   }
448 
449   @Override
450   public byte[] encodeVisibilityForReplication(final List<Tag> tags, final Byte serializationFormat)
451       throws IOException {
452     if (tags.size() > 0 && (serializationFormat == null
453         || serializationFormat == STRING_SERIALIZATION_FORMAT)) {
454       return createModifiedVisExpression(tags);
455     }
456     return null;
457   }
458 
459   /**
460    * @param tags - all the tags associated with the current Cell
461    * @return - the modified visibility expression as byte[]
462    */
463   private byte[] createModifiedVisExpression(final List<Tag> tags)
464       throws IOException {
465     StringBuilder visibilityString = new StringBuilder();
466     for (Tag tag : tags) {
467       if (tag.getType() == TagType.VISIBILITY_TAG_TYPE) {
468         if (visibilityString.length() != 0) {
469           visibilityString.append(VisibilityConstants.CLOSED_PARAN
470               + VisibilityConstants.OR_OPERATOR);
471         }
472         int offset = tag.getTagOffset();
473         int endOffset = offset + tag.getTagLength();
474         boolean expressionStart = true;
475         while (offset < endOffset) {
476           short len = Bytes.toShort(tag.getBuffer(), offset);
477           offset += 2;
478           if (len < 0) {
479             len = (short) (-1 * len);
480             String label = Bytes.toString(tag.getBuffer(), offset, len);
481             if (expressionStart) {
482               visibilityString.append(VisibilityConstants.OPEN_PARAN
483                   + VisibilityConstants.NOT_OPERATOR + CellVisibility.quote(label));
484             } else {
485               visibilityString.append(VisibilityConstants.AND_OPERATOR
486                   + VisibilityConstants.NOT_OPERATOR + CellVisibility.quote(label));
487             }
488           } else {
489             String label = Bytes.toString(tag.getBuffer(), offset, len);
490             if (expressionStart) {
491               visibilityString.append(VisibilityConstants.OPEN_PARAN + CellVisibility.quote(label));
492             } else {
493               visibilityString.append(VisibilityConstants.AND_OPERATOR
494                   + CellVisibility.quote(label));
495             }
496           }
497           expressionStart = false;
498           offset += len;
499         }
500       }
501     }
502     if (visibilityString.length() != 0) {
503       visibilityString.append(VisibilityConstants.CLOSED_PARAN);
504       // Return the string formed as byte[]
505       return Bytes.toBytes(visibilityString.toString());
506     }
507     return null;
508   }
509 }