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