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