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