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