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