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