001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security.visibility; 019 020import static org.apache.hadoop.hbase.TagType.VISIBILITY_TAG_TYPE; 021import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY; 022import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME; 023import static org.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL; 024 025import java.io.ByteArrayOutputStream; 026import java.io.DataOutputStream; 027import java.io.IOException; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.List; 033import java.util.Set; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.hbase.ArrayBackedTag; 036import org.apache.hadoop.hbase.AuthUtil; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.CellBuilder; 039import org.apache.hadoop.hbase.CellBuilderFactory; 040import org.apache.hadoop.hbase.CellBuilderType; 041import org.apache.hadoop.hbase.HConstants.OperationStatusCode; 042import org.apache.hadoop.hbase.PrivateCellUtil; 043import org.apache.hadoop.hbase.Tag; 044import org.apache.hadoop.hbase.TagType; 045import org.apache.hadoop.hbase.client.Connection; 046import org.apache.hadoop.hbase.client.ConnectionFactory; 047import org.apache.hadoop.hbase.client.Delete; 048import org.apache.hadoop.hbase.client.Get; 049import org.apache.hadoop.hbase.client.Put; 050import org.apache.hadoop.hbase.client.Result; 051import org.apache.hadoop.hbase.client.Scan; 052import org.apache.hadoop.hbase.client.Table; 053import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 054import org.apache.hadoop.hbase.regionserver.OperationStatus; 055import org.apache.hadoop.hbase.regionserver.Region; 056import org.apache.hadoop.hbase.regionserver.RegionScanner; 057import org.apache.hadoop.hbase.security.Superusers; 058import org.apache.hadoop.hbase.security.User; 059import org.apache.hadoop.hbase.security.visibility.expression.ExpressionNode; 060import org.apache.hadoop.hbase.security.visibility.expression.LeafExpressionNode; 061import org.apache.hadoop.hbase.security.visibility.expression.NonLeafExpressionNode; 062import org.apache.hadoop.hbase.security.visibility.expression.Operator; 063import org.apache.hadoop.hbase.util.ByteBufferUtils; 064import org.apache.hadoop.hbase.util.Bytes; 065import org.apache.yetus.audience.InterfaceAudience; 066import org.slf4j.Logger; 067import org.slf4j.LoggerFactory; 068 069/** 070 * This is a VisibilityLabelService where labels in Mutation's visibility expression will be 071 * persisted as Strings itself rather than ordinals in 'labels' table. Also there is no need to add 072 * labels to the system, prior to using them in Mutations/Authorizations. 073 */ 074@InterfaceAudience.Private 075public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelService { 076 private static final Logger LOG = 077 LoggerFactory.getLogger(ExpAsStringVisibilityLabelServiceImpl.class); 078 079 private static final byte[] DUMMY_VALUE = new byte[0]; 080 private static final byte STRING_SERIALIZATION_FORMAT = 2; 081 private static final Tag STRING_SERIALIZATION_FORMAT_TAG = 082 new ArrayBackedTag(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE, 083 new byte[] { STRING_SERIALIZATION_FORMAT }); 084 private final ExpressionParser expressionParser = new ExpressionParser(); 085 private final ExpressionExpander expressionExpander = new ExpressionExpander(); 086 private Configuration conf; 087 private Region labelsRegion; 088 private List<ScanLabelGenerator> scanLabelGenerators; 089 090 @Override 091 public OperationStatus[] addLabels(List<byte[]> labels) throws IOException { 092 // Not doing specific label add. We will just add labels in Mutation 093 // visibility expression as it 094 // is along with every cell. 095 OperationStatus[] status = new OperationStatus[labels.size()]; 096 for (int i = 0; i < labels.size(); i++) { 097 status[i] = new OperationStatus(OperationStatusCode.SUCCESS); 098 } 099 return status; 100 } 101 102 @Override 103 public OperationStatus[] setAuths(byte[] user, List<byte[]> authLabels) throws IOException { 104 assert labelsRegion != null; 105 OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()]; 106 Put p = new Put(user); 107 CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY); 108 for (byte[] auth : authLabels) { 109 p.add(builder.clear().setRow(p.getRow()).setFamily(LABELS_TABLE_FAMILY).setQualifier(auth) 110 .setTimestamp(p.getTimestamp()).setType(Cell.Type.Put).setValue(DUMMY_VALUE).build()); 111 } 112 this.labelsRegion.put(p); 113 // This is a testing impl and so not doing any caching 114 for (int i = 0; i < authLabels.size(); i++) { 115 finalOpStatus[i] = new OperationStatus(OperationStatusCode.SUCCESS); 116 } 117 return finalOpStatus; 118 } 119 120 @Override 121 public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException { 122 assert labelsRegion != null; 123 OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()]; 124 List<String> currentAuths; 125 if (AuthUtil.isGroupPrincipal(Bytes.toString(user))) { 126 String group = AuthUtil.getGroupName(Bytes.toString(user)); 127 currentAuths = this.getGroupAuths(new String[] { group }, true); 128 } else { 129 currentAuths = this.getUserAuths(user, true); 130 } 131 Delete d = new Delete(user); 132 int i = 0; 133 for (byte[] authLabel : authLabels) { 134 String authLabelStr = Bytes.toString(authLabel); 135 if (currentAuths.contains(authLabelStr)) { 136 d.addColumns(LABELS_TABLE_FAMILY, authLabel); 137 } else { 138 // This label is not set for the user. 139 finalOpStatus[i] = 140 new OperationStatus(OperationStatusCode.FAILURE, new InvalidLabelException( 141 "Label '" + authLabelStr + "' is not set for the user " + Bytes.toString(user))); 142 } 143 i++; 144 } 145 this.labelsRegion.delete(d); 146 // This is a testing impl and so not doing any caching 147 for (i = 0; i < authLabels.size(); i++) { 148 if (finalOpStatus[i] == null) { 149 finalOpStatus[i] = new OperationStatus(OperationStatusCode.SUCCESS); 150 } 151 } 152 return finalOpStatus; 153 } 154 155 @Override 156 public List<String> getUserAuths(byte[] user, boolean systemCall) throws IOException { 157 assert (labelsRegion != null || systemCall); 158 List<String> auths = new ArrayList<>(); 159 Get get = new Get(user); 160 getAuths(get, auths); 161 return auths; 162 } 163 164 @Override 165 public List<String> getGroupAuths(String[] groups, boolean systemCall) throws IOException { 166 assert (labelsRegion != null || systemCall); 167 List<String> auths = new ArrayList<>(); 168 if (groups != null && groups.length > 0) { 169 for (String group : groups) { 170 Get get = new Get(Bytes.toBytes(AuthUtil.toGroupEntry(group))); 171 getAuths(get, auths); 172 } 173 } 174 return auths; 175 } 176 177 private void getAuths(Get get, List<String> auths) throws IOException { 178 List<Cell> cells = new ArrayList<>(); 179 RegionScanner scanner = null; 180 try { 181 if (labelsRegion == null) { 182 Table table = null; 183 Connection connection = null; 184 try { 185 connection = ConnectionFactory.createConnection(conf); 186 table = connection.getTable(VisibilityConstants.LABELS_TABLE_NAME); 187 Result result = table.get(get); 188 cells = result.listCells(); 189 } finally { 190 if (table != null) { 191 table.close(); 192 } 193 if (connection != null) { 194 connection.close(); 195 } 196 } 197 } else { 198 // NOTE: Please don't use HRegion.get() instead, 199 // because it will copy cells to heap. See HBASE-26036 200 scanner = this.labelsRegion.getScanner(new Scan(get)); 201 scanner.next(cells); 202 } 203 for (Cell cell : cells) { 204 String auth = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), 205 cell.getQualifierLength()); 206 auths.add(auth); 207 } 208 } finally { 209 if (scanner != null) { 210 scanner.close(); 211 } 212 } 213 } 214 215 @Override 216 public List<String> listLabels(String regex) throws IOException { 217 // return an empty list for this implementation. 218 return new ArrayList<>(); 219 } 220 221 @Override 222 public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat, 223 boolean checkAuths) throws IOException { 224 ExpressionNode node = null; 225 try { 226 node = this.expressionParser.parse(visExpression); 227 } catch (ParseException e) { 228 throw new IOException(e); 229 } 230 node = this.expressionExpander.expand(node); 231 List<Tag> tags = new ArrayList<>(); 232 if (withSerializationFormat) { 233 tags.add(STRING_SERIALIZATION_FORMAT_TAG); 234 } 235 if ( 236 node instanceof NonLeafExpressionNode 237 && ((NonLeafExpressionNode) node).getOperator() == Operator.OR 238 ) { 239 for (ExpressionNode child : ((NonLeafExpressionNode) node).getChildExps()) { 240 tags.add(createTag(child)); 241 } 242 } else { 243 tags.add(createTag(node)); 244 } 245 return tags; 246 } 247 248 @Override 249 public VisibilityExpEvaluator getVisibilityExpEvaluator(Authorizations authorizations) 250 throws IOException { 251 // If a super user issues a get/scan, he should be able to scan the cells 252 // irrespective of the Visibility labels 253 if (isReadFromSystemAuthUser()) { 254 return new VisibilityExpEvaluator() { 255 @Override 256 public boolean evaluate(Cell cell) throws IOException { 257 return true; 258 } 259 }; 260 } 261 List<String> authLabels = null; 262 for (ScanLabelGenerator scanLabelGenerator : scanLabelGenerators) { 263 try { 264 // null authorizations to be handled inside SLG impl. 265 authLabels = scanLabelGenerator.getLabels(VisibilityUtils.getActiveUser(), authorizations); 266 authLabels = (authLabels == null) ? new ArrayList<>() : authLabels; 267 authorizations = new Authorizations(authLabels); 268 } catch (Throwable t) { 269 LOG.error(t.toString(), t); 270 throw new IOException(t); 271 } 272 } 273 final List<String> authLabelsFinal = authLabels; 274 return new VisibilityExpEvaluator() { 275 @Override 276 public boolean evaluate(Cell cell) throws IOException { 277 boolean visibilityTagPresent = false; 278 // Save an object allocation where we can 279 if (cell.getTagsLength() > 0) { 280 Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(cell); 281 while (tagsItr.hasNext()) { 282 boolean includeKV = true; 283 Tag tag = tagsItr.next(); 284 if (tag.getType() == VISIBILITY_TAG_TYPE) { 285 visibilityTagPresent = true; 286 int offset = tag.getValueOffset(); 287 int endOffset = offset + tag.getValueLength(); 288 while (offset < endOffset) { 289 short len = getTagValuePartAsShort(tag, offset); 290 offset += 2; 291 if (len < 0) { 292 // This is a NOT label. 293 len = (short) (-1 * len); 294 String label = getTagValuePartAsString(tag, offset, len); 295 if (authLabelsFinal.contains(label)) { 296 includeKV = false; 297 break; 298 } 299 } else { 300 String label = getTagValuePartAsString(tag, offset, len); 301 if (!authLabelsFinal.contains(label)) { 302 includeKV = false; 303 break; 304 } 305 } 306 offset += len; 307 } 308 if (includeKV) { 309 // We got one visibility expression getting evaluated to true. 310 // Good to include this 311 // KV in the result then. 312 return true; 313 } 314 } 315 } 316 } 317 return !(visibilityTagPresent); 318 } 319 }; 320 } 321 322 protected boolean isReadFromSystemAuthUser() throws IOException { 323 User user = VisibilityUtils.getActiveUser(); 324 return havingSystemAuth(user); 325 } 326 327 private Tag createTag(ExpressionNode node) throws IOException { 328 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 329 DataOutputStream dos = new DataOutputStream(baos); 330 List<String> labels = new ArrayList<>(); 331 List<String> notLabels = new ArrayList<>(); 332 extractLabels(node, labels, notLabels); 333 Collections.sort(labels); 334 Collections.sort(notLabels); 335 // We will write the NOT labels 1st followed by normal labels 336 // Each of the label we will write with label length (as short 1st) followed 337 // by the label bytes. 338 // For a NOT node we will write the label length as -ve. 339 for (String label : notLabels) { 340 byte[] bLabel = Bytes.toBytes(label); 341 short length = (short) bLabel.length; 342 length = (short) (-1 * length); 343 dos.writeShort(length); 344 dos.write(bLabel); 345 } 346 for (String label : labels) { 347 byte[] bLabel = Bytes.toBytes(label); 348 dos.writeShort(bLabel.length); 349 dos.write(bLabel); 350 } 351 return new ArrayBackedTag(VISIBILITY_TAG_TYPE, baos.toByteArray()); 352 } 353 354 private void extractLabels(ExpressionNode node, List<String> labels, List<String> notLabels) { 355 if (node.isSingleNode()) { 356 if (node instanceof NonLeafExpressionNode) { 357 // This is a NOT node. 358 LeafExpressionNode lNode = 359 (LeafExpressionNode) ((NonLeafExpressionNode) node).getChildExps().get(0); 360 notLabels.add(lNode.getIdentifier()); 361 } else { 362 labels.add(((LeafExpressionNode) node).getIdentifier()); 363 } 364 } else { 365 // A non leaf expression of labels with & operator. 366 NonLeafExpressionNode nlNode = (NonLeafExpressionNode) node; 367 assert nlNode.getOperator() == Operator.AND; 368 List<ExpressionNode> childExps = nlNode.getChildExps(); 369 for (ExpressionNode child : childExps) { 370 extractLabels(child, labels, notLabels); 371 } 372 } 373 } 374 375 @Override 376 public Configuration getConf() { 377 return this.conf; 378 } 379 380 @Override 381 public void setConf(Configuration conf) { 382 this.conf = conf; 383 } 384 385 @Override 386 public void init(RegionCoprocessorEnvironment e) throws IOException { 387 this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf); 388 if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) { 389 this.labelsRegion = e.getRegion(); 390 } 391 } 392 393 @Override 394 public boolean havingSystemAuth(User user) throws IOException { 395 if (Superusers.isSuperUser(user)) { 396 return true; 397 } 398 Set<String> auths = new HashSet<>(); 399 auths.addAll(this.getUserAuths(Bytes.toBytes(user.getShortName()), true)); 400 auths.addAll(this.getGroupAuths(user.getGroupNames(), true)); 401 return auths.contains(SYSTEM_LABEL); 402 } 403 404 @Override 405 public boolean matchVisibility(List<Tag> putTags, Byte putTagsFormat, List<Tag> deleteTags, 406 Byte deleteTagsFormat) throws IOException { 407 assert putTagsFormat == STRING_SERIALIZATION_FORMAT; 408 assert deleteTagsFormat == STRING_SERIALIZATION_FORMAT; 409 return checkForMatchingVisibilityTagsWithSortedOrder(putTags, deleteTags); 410 } 411 412 private static boolean checkForMatchingVisibilityTagsWithSortedOrder(List<Tag> putVisTags, 413 List<Tag> deleteVisTags) { 414 // Early out if there are no tags in both of cell and delete 415 if (putVisTags.isEmpty() && deleteVisTags.isEmpty()) { 416 return true; 417 } 418 boolean matchFound = false; 419 // If the size does not match. Definitely we are not comparing the equal 420 // tags. 421 if ((deleteVisTags.size()) == putVisTags.size()) { 422 for (Tag tag : deleteVisTags) { 423 matchFound = false; 424 for (Tag givenTag : putVisTags) { 425 if (Tag.matchingValue(tag, givenTag)) { 426 matchFound = true; 427 break; 428 } 429 } 430 if (!matchFound) break; 431 } 432 } 433 return matchFound; 434 } 435 436 @Override 437 public byte[] encodeVisibilityForReplication(final List<Tag> tags, final Byte serializationFormat) 438 throws IOException { 439 if ( 440 tags.size() > 0 441 && (serializationFormat == null || serializationFormat == STRING_SERIALIZATION_FORMAT) 442 ) { 443 return createModifiedVisExpression(tags); 444 } 445 return null; 446 } 447 448 /** 449 * @param tags - all the tags associated with the current Cell 450 * @return - the modified visibility expression as byte[] 451 */ 452 private byte[] createModifiedVisExpression(final List<Tag> tags) throws IOException { 453 StringBuilder visibilityString = new StringBuilder(); 454 for (Tag tag : tags) { 455 if (tag.getType() == TagType.VISIBILITY_TAG_TYPE) { 456 if (visibilityString.length() != 0) { 457 visibilityString 458 .append(VisibilityConstants.CLOSED_PARAN + VisibilityConstants.OR_OPERATOR); 459 } 460 int offset = tag.getValueOffset(); 461 int endOffset = offset + tag.getValueLength(); 462 boolean expressionStart = true; 463 while (offset < endOffset) { 464 short len = getTagValuePartAsShort(tag, offset); 465 offset += 2; 466 if (len < 0) { 467 len = (short) (-1 * len); 468 String label = getTagValuePartAsString(tag, offset, len); 469 if (expressionStart) { 470 visibilityString.append(VisibilityConstants.OPEN_PARAN 471 + VisibilityConstants.NOT_OPERATOR + CellVisibility.quote(label)); 472 } else { 473 visibilityString.append(VisibilityConstants.AND_OPERATOR 474 + VisibilityConstants.NOT_OPERATOR + CellVisibility.quote(label)); 475 } 476 } else { 477 String label = getTagValuePartAsString(tag, offset, len); 478 if (expressionStart) { 479 visibilityString.append(VisibilityConstants.OPEN_PARAN + CellVisibility.quote(label)); 480 } else { 481 visibilityString 482 .append(VisibilityConstants.AND_OPERATOR + CellVisibility.quote(label)); 483 } 484 } 485 expressionStart = false; 486 offset += len; 487 } 488 } 489 } 490 if (visibilityString.length() != 0) { 491 visibilityString.append(VisibilityConstants.CLOSED_PARAN); 492 // Return the string formed as byte[] 493 return Bytes.toBytes(visibilityString.toString()); 494 } 495 return null; 496 } 497 498 private static short getTagValuePartAsShort(Tag t, int offset) { 499 if (t.hasArray()) { 500 return Bytes.toShort(t.getValueArray(), offset); 501 } 502 return ByteBufferUtils.toShort(t.getValueByteBuffer(), offset); 503 } 504 505 private static String getTagValuePartAsString(Tag t, int offset, int length) { 506 if (t.hasArray()) { 507 return Bytes.toString(t.getValueArray(), offset, length); 508 } 509 byte[] b = new byte[length]; 510 ByteBufferUtils.copyFromBufferToArray(b, t.getValueByteBuffer(), offset, 0, length); 511 return Bytes.toString(b); 512 } 513}