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.access; 019 020import java.io.IOException; 021import java.security.PrivilegedExceptionAction; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Optional; 031import java.util.Set; 032import java.util.TreeMap; 033import java.util.TreeSet; 034import java.util.stream.Collectors; 035import org.apache.hadoop.conf.Configuration; 036import org.apache.hadoop.hbase.ArrayBackedTag; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.CellUtil; 039import org.apache.hadoop.hbase.CompareOperator; 040import org.apache.hadoop.hbase.CompoundConfiguration; 041import org.apache.hadoop.hbase.CoprocessorEnvironment; 042import org.apache.hadoop.hbase.DoNotRetryIOException; 043import org.apache.hadoop.hbase.ExtendedCell; 044import org.apache.hadoop.hbase.ExtendedCellScanner; 045import org.apache.hadoop.hbase.HBaseInterfaceAudience; 046import org.apache.hadoop.hbase.HConstants; 047import org.apache.hadoop.hbase.KeyValue; 048import org.apache.hadoop.hbase.KeyValue.Type; 049import org.apache.hadoop.hbase.NamespaceDescriptor; 050import org.apache.hadoop.hbase.PrivateCellUtil; 051import org.apache.hadoop.hbase.ServerName; 052import org.apache.hadoop.hbase.TableName; 053import org.apache.hadoop.hbase.Tag; 054import org.apache.hadoop.hbase.client.Admin; 055import org.apache.hadoop.hbase.client.Append; 056import org.apache.hadoop.hbase.client.BalanceRequest; 057import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 058import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 059import org.apache.hadoop.hbase.client.Delete; 060import org.apache.hadoop.hbase.client.Durability; 061import org.apache.hadoop.hbase.client.Get; 062import org.apache.hadoop.hbase.client.Increment; 063import org.apache.hadoop.hbase.client.MasterSwitchType; 064import org.apache.hadoop.hbase.client.Mutation; 065import org.apache.hadoop.hbase.client.Put; 066import org.apache.hadoop.hbase.client.Query; 067import org.apache.hadoop.hbase.client.RegionInfo; 068import org.apache.hadoop.hbase.client.Result; 069import org.apache.hadoop.hbase.client.Scan; 070import org.apache.hadoop.hbase.client.SnapshotDescription; 071import org.apache.hadoop.hbase.client.Table; 072import org.apache.hadoop.hbase.client.TableDescriptor; 073import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 074import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver; 075import org.apache.hadoop.hbase.coprocessor.CoprocessorException; 076import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor; 077import org.apache.hadoop.hbase.coprocessor.EndpointObserver; 078import org.apache.hadoop.hbase.coprocessor.HasMasterServices; 079import org.apache.hadoop.hbase.coprocessor.HasRegionServerServices; 080import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 081import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 082import org.apache.hadoop.hbase.coprocessor.MasterObserver; 083import org.apache.hadoop.hbase.coprocessor.ObserverContext; 084import org.apache.hadoop.hbase.coprocessor.ObserverRpcCallContext; 085import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; 086import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 087import org.apache.hadoop.hbase.coprocessor.RegionObserver; 088import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor; 089import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; 090import org.apache.hadoop.hbase.coprocessor.RegionServerObserver; 091import org.apache.hadoop.hbase.filter.ByteArrayComparable; 092import org.apache.hadoop.hbase.filter.Filter; 093import org.apache.hadoop.hbase.filter.FilterList; 094import org.apache.hadoop.hbase.io.hfile.HFile; 095import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils; 096import org.apache.hadoop.hbase.ipc.RpcServer; 097import org.apache.hadoop.hbase.master.MasterServices; 098import org.apache.hadoop.hbase.net.Address; 099import org.apache.hadoop.hbase.quotas.GlobalQuotaSettings; 100import org.apache.hadoop.hbase.regionserver.BloomType; 101import org.apache.hadoop.hbase.regionserver.FlushLifeCycleTracker; 102import org.apache.hadoop.hbase.regionserver.InternalScanner; 103import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; 104import org.apache.hadoop.hbase.regionserver.Region; 105import org.apache.hadoop.hbase.regionserver.RegionScanner; 106import org.apache.hadoop.hbase.regionserver.RegionServerServices; 107import org.apache.hadoop.hbase.regionserver.ScanType; 108import org.apache.hadoop.hbase.regionserver.ScannerContext; 109import org.apache.hadoop.hbase.regionserver.Store; 110import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker; 111import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest; 112import org.apache.hadoop.hbase.replication.ReplicationEndpoint; 113import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; 114import org.apache.hadoop.hbase.replication.SyncReplicationState; 115import org.apache.hadoop.hbase.security.AccessDeniedException; 116import org.apache.hadoop.hbase.security.Superusers; 117import org.apache.hadoop.hbase.security.User; 118import org.apache.hadoop.hbase.security.UserProvider; 119import org.apache.hadoop.hbase.security.access.Permission.Action; 120import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 121import org.apache.hadoop.hbase.util.ByteRange; 122import org.apache.hadoop.hbase.util.Bytes; 123import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 124import org.apache.hadoop.hbase.util.Pair; 125import org.apache.hadoop.hbase.util.SimpleMutableByteRange; 126import org.apache.hadoop.hbase.wal.WALEdit; 127import org.apache.yetus.audience.InterfaceAudience; 128import org.slf4j.Logger; 129import org.slf4j.LoggerFactory; 130 131import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 132import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableSet; 133import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 134import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 135import org.apache.hbase.thirdparty.com.google.common.collect.MapMaker; 136import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 137import org.apache.hbase.thirdparty.com.google.protobuf.Message; 138import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback; 139import org.apache.hbase.thirdparty.com.google.protobuf.RpcController; 140import org.apache.hbase.thirdparty.com.google.protobuf.Service; 141 142import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 143import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter; 144import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos; 145import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.AccessControlService; 146import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.HasPermissionRequest; 147import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.HasPermissionResponse; 148 149/** 150 * Provides basic authorization checks for data access and administrative operations. 151 * <p> 152 * {@code AccessController} performs authorization checks for HBase operations based on: 153 * </p> 154 * <ul> 155 * <li>the identity of the user performing the operation</li> 156 * <li>the scope over which the operation is performed, in increasing specificity: global, table, 157 * column family, or qualifier</li> 158 * <li>the type of action being performed (as mapped to {@link Permission.Action} values)</li> 159 * </ul> 160 * <p> 161 * If the authorization check fails, an {@link AccessDeniedException} will be thrown for the 162 * operation. 163 * </p> 164 * <p> 165 * To perform authorization checks, {@code AccessController} relies on the RpcServerEngine being 166 * loaded to provide the user identities for remote requests. 167 * </p> 168 * <p> 169 * The access control lists used for authorization can be manipulated via the exposed 170 * {@link AccessControlService} Interface implementation, and the associated {@code grant}, 171 * {@code revoke}, and {@code user_permission} HBase shell commands. 172 * </p> 173 */ 174@CoreCoprocessor 175@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 176public class AccessController implements MasterCoprocessor, RegionCoprocessor, 177 RegionServerCoprocessor, AccessControlService.Interface, MasterObserver, RegionObserver, 178 RegionServerObserver, EndpointObserver, BulkLoadObserver { 179 // TODO: encapsulate observer functions into separate class/sub-class. 180 181 private static final Logger LOG = LoggerFactory.getLogger(AccessController.class); 182 183 private static final Logger AUDITLOG = 184 LoggerFactory.getLogger("SecurityLogger." + AccessController.class.getName()); 185 private static final String CHECK_COVERING_PERM = "check_covering_perm"; 186 private static final String TAG_CHECK_PASSED = "tag_check_passed"; 187 private static final byte[] TRUE = Bytes.toBytes(true); 188 189 private AccessChecker accessChecker; 190 private ZKPermissionWatcher zkPermissionWatcher; 191 192 /** flags if we are running on a region of the _acl_ table */ 193 private boolean aclRegion = false; 194 195 /** 196 * defined only for Endpoint implementation, so it can have way to access region services 197 */ 198 private RegionCoprocessorEnvironment regionEnv; 199 200 /** Mapping of scanner instances to the user who created them */ 201 private Map<InternalScanner, String> scannerOwners = new MapMaker().weakKeys().makeMap(); 202 203 private Map<TableName, List<UserPermission>> tableAcls; 204 205 /** Provider for mapping principal names to Users */ 206 private UserProvider userProvider; 207 208 /** 209 * if we are active, usually false, only true if "hbase.security.authorization" has been set to 210 * true in site configuration 211 */ 212 private boolean authorizationEnabled; 213 214 /** if we are able to support cell ACLs */ 215 private boolean cellFeaturesEnabled; 216 217 /** if we should check EXEC permissions */ 218 private boolean shouldCheckExecPermission; 219 220 /** 221 * if we should terminate access checks early as soon as table or CF grants allow access; pre-0.98 222 * compatible behavior 223 */ 224 private boolean compatibleEarlyTermination; 225 226 /** if we have been successfully initialized */ 227 private volatile boolean initialized = false; 228 229 /** if the ACL table is available, only relevant in the master */ 230 private volatile boolean aclTabAvailable = false; 231 232 public static boolean isCellAuthorizationSupported(Configuration conf) { 233 return AccessChecker.isAuthorizationSupported(conf) 234 && (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS); 235 } 236 237 public Region getRegion() { 238 return regionEnv != null ? regionEnv.getRegion() : null; 239 } 240 241 public AuthManager getAuthManager() { 242 return accessChecker.getAuthManager(); 243 } 244 245 private void initialize(RegionCoprocessorEnvironment e) throws IOException { 246 final Region region = e.getRegion(); 247 Configuration conf = e.getConfiguration(); 248 Map<byte[], ListMultimap<String, UserPermission>> tables = PermissionStorage.loadAll(region); 249 // For each table, write out the table's permissions to the respective 250 // znode for that table. 251 for (Map.Entry<byte[], ListMultimap<String, UserPermission>> t : tables.entrySet()) { 252 byte[] entry = t.getKey(); 253 ListMultimap<String, UserPermission> perms = t.getValue(); 254 byte[] serialized = PermissionStorage.writePermissionsAsBytes(perms, conf); 255 zkPermissionWatcher.writeToZookeeper(entry, serialized); 256 } 257 initialized = true; 258 } 259 260 /** 261 * Writes all table ACLs for the tables in the given Map up into ZooKeeper znodes. This is called 262 * to synchronize ACL changes following {@code _acl_} table updates. 263 */ 264 private void updateACL(RegionCoprocessorEnvironment e, final Map<byte[], List<Cell>> familyMap) { 265 Set<byte[]> entries = new TreeSet<>(Bytes.BYTES_RAWCOMPARATOR); 266 for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) { 267 List<Cell> cells = f.getValue(); 268 for (Cell cell : cells) { 269 if (CellUtil.matchingFamily(cell, PermissionStorage.ACL_LIST_FAMILY)) { 270 entries.add(CellUtil.cloneRow(cell)); 271 } 272 } 273 } 274 Configuration conf = regionEnv.getConfiguration(); 275 byte[] currentEntry = null; 276 // TODO: Here we are already on the ACL region. (And it is single 277 // region) We can even just get the region from the env and do get 278 // directly. The short circuit connection would avoid the RPC overhead 279 // so no socket communication, req write/read .. But we have the PB 280 // to and fro conversion overhead. get req is converted to PB req 281 // and results are converted to PB results 1st and then to POJOs 282 // again. We could have avoided such at least in ACL table context.. 283 try (Table t = e.getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 284 for (byte[] entry : entries) { 285 currentEntry = entry; 286 ListMultimap<String, UserPermission> perms = 287 PermissionStorage.getPermissions(conf, entry, t, null, null, null, false); 288 byte[] serialized = PermissionStorage.writePermissionsAsBytes(perms, conf); 289 zkPermissionWatcher.writeToZookeeper(entry, serialized); 290 } 291 } catch (IOException ex) { 292 LOG.error("Failed updating permissions mirror for '" 293 + (currentEntry == null ? "null" : Bytes.toString(currentEntry)) + "'", ex); 294 } 295 } 296 297 /** 298 * Check the current user for authorization to perform a specific action against the given set of 299 * row data. 300 * @param opType the operation type 301 * @param user the user 302 * @param e the coprocessor environment 303 * @param families the map of column families to qualifiers present in the request 304 * @param actions the desired actions 305 * @return an authorization result 306 */ 307 private AuthResult permissionGranted(OpType opType, User user, RegionCoprocessorEnvironment e, 308 Map<byte[], ? extends Collection<?>> families, Action... actions) { 309 AuthResult result = null; 310 for (Action action : actions) { 311 result = accessChecker.permissionGranted(opType.toString(), user, action, 312 e.getRegion().getRegionInfo().getTable(), families); 313 if (!result.isAllowed()) { 314 return result; 315 } 316 } 317 return result; 318 } 319 320 public void requireAccess(ObserverContext<?> ctx, String request, TableName tableName, 321 Action... permissions) throws IOException { 322 accessChecker.requireAccess(getActiveUser(ctx), request, tableName, permissions); 323 } 324 325 public void requirePermission(ObserverContext<?> ctx, String request, Action perm) 326 throws IOException { 327 accessChecker.requirePermission(getActiveUser(ctx), request, null, perm); 328 } 329 330 public void requireGlobalPermission(ObserverContext<?> ctx, String request, Action perm, 331 TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException { 332 accessChecker.requireGlobalPermission(getActiveUser(ctx), request, perm, tableName, familyMap, 333 null); 334 } 335 336 public void requireGlobalPermission(ObserverContext<?> ctx, String request, Action perm, 337 String namespace) throws IOException { 338 accessChecker.requireGlobalPermission(getActiveUser(ctx), request, perm, namespace); 339 } 340 341 public void requireNamespacePermission(ObserverContext<?> ctx, String request, String namespace, 342 Action... permissions) throws IOException { 343 accessChecker.requireNamespacePermission(getActiveUser(ctx), request, namespace, null, 344 permissions); 345 } 346 347 public void requireNamespacePermission(ObserverContext<?> ctx, String request, String namespace, 348 TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions) 349 throws IOException { 350 accessChecker.requireNamespacePermission(getActiveUser(ctx), request, namespace, tableName, 351 familyMap, permissions); 352 } 353 354 public void requirePermission(ObserverContext<?> ctx, String request, TableName tableName, 355 byte[] family, byte[] qualifier, Action... permissions) throws IOException { 356 accessChecker.requirePermission(getActiveUser(ctx), request, tableName, family, qualifier, null, 357 permissions); 358 } 359 360 public void requireTablePermission(ObserverContext<?> ctx, String request, TableName tableName, 361 byte[] family, byte[] qualifier, Action... permissions) throws IOException { 362 accessChecker.requireTablePermission(getActiveUser(ctx), request, tableName, family, qualifier, 363 permissions); 364 } 365 366 public void checkLockPermissions(ObserverContext<?> ctx, String namespace, TableName tableName, 367 RegionInfo[] regionInfos, String reason) throws IOException { 368 accessChecker.checkLockPermissions(getActiveUser(ctx), namespace, tableName, regionInfos, 369 reason); 370 } 371 372 /** 373 * Returns <code>true</code> if the current user is allowed the given action over at least one of 374 * the column qualifiers in the given column families. 375 */ 376 private boolean hasFamilyQualifierPermission(User user, Action perm, 377 RegionCoprocessorEnvironment env, Map<byte[], ? extends Collection<byte[]>> familyMap) 378 throws IOException { 379 RegionInfo hri = env.getRegion().getRegionInfo(); 380 TableName tableName = hri.getTable(); 381 382 if (user == null) { 383 return false; 384 } 385 386 if (familyMap != null && familyMap.size() > 0) { 387 // at least one family must be allowed 388 for (Map.Entry<byte[], ? extends Collection<byte[]>> family : familyMap.entrySet()) { 389 if (family.getValue() != null && !family.getValue().isEmpty()) { 390 for (byte[] qualifier : family.getValue()) { 391 if ( 392 getAuthManager().authorizeUserTable(user, tableName, family.getKey(), qualifier, perm) 393 ) { 394 return true; 395 } 396 } 397 } else { 398 if (getAuthManager().authorizeUserFamily(user, tableName, family.getKey(), perm)) { 399 return true; 400 } 401 } 402 } 403 } else if (LOG.isDebugEnabled()) { 404 LOG.debug("Empty family map passed for permission check"); 405 } 406 407 return false; 408 } 409 410 private enum OpType { 411 GET("get"), 412 EXISTS("exists"), 413 SCAN("scan"), 414 PUT("put"), 415 DELETE("delete"), 416 CHECK_AND_PUT("checkAndPut"), 417 CHECK_AND_DELETE("checkAndDelete"), 418 APPEND("append"), 419 INCREMENT("increment"); 420 421 private String type; 422 423 private OpType(String type) { 424 this.type = type; 425 } 426 427 @Override 428 public String toString() { 429 return type; 430 } 431 } 432 433 /** 434 * Determine if cell ACLs covered by the operation grant access. This is expensive. 435 * @return false if cell ACLs failed to grant access, true otherwise 436 */ 437 private boolean checkCoveringPermission(User user, OpType request, RegionCoprocessorEnvironment e, 438 byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions) 439 throws IOException { 440 if (!cellFeaturesEnabled) { 441 return false; 442 } 443 long cellGrants = 0; 444 long latestCellTs = 0; 445 Get get = new Get(row); 446 // Only in case of Put/Delete op, consider TS within cell (if set for individual cells). 447 // When every cell, within a Mutation, can be linked with diff TS we can not rely on only one 448 // version. We have to get every cell version and check its TS against the TS asked for in 449 // Mutation and skip those Cells which is outside this Mutation TS.In case of Put, we have to 450 // consider only one such passing cell. In case of Delete we have to consider all the cell 451 // versions under this passing version. When Delete Mutation contains columns which are a 452 // version delete just consider only one version for those column cells. 453 boolean considerCellTs = (request == OpType.PUT || request == OpType.DELETE); 454 if (considerCellTs) { 455 get.readAllVersions(); 456 } else { 457 get.readVersions(1); 458 } 459 boolean diffCellTsFromOpTs = false; 460 for (Map.Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) { 461 byte[] col = entry.getKey(); 462 // TODO: HBASE-7114 could possibly unify the collection type in family 463 // maps so we would not need to do this 464 if (entry.getValue() instanceof Set) { 465 Set<byte[]> set = (Set<byte[]>) entry.getValue(); 466 if (set == null || set.isEmpty()) { 467 get.addFamily(col); 468 } else { 469 for (byte[] qual : set) { 470 get.addColumn(col, qual); 471 } 472 } 473 } else if (entry.getValue() instanceof List) { 474 List<ExtendedCell> list = (List<ExtendedCell>) entry.getValue(); 475 if (list == null || list.isEmpty()) { 476 get.addFamily(col); 477 } else { 478 // In case of family delete, a Cell will be added into the list with Qualifier as null. 479 for (ExtendedCell cell : list) { 480 if ( 481 cell.getQualifierLength() == 0 && (cell.getTypeByte() == Type.DeleteFamily.getCode() 482 || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode()) 483 ) { 484 get.addFamily(col); 485 } else { 486 get.addColumn(col, CellUtil.cloneQualifier(cell)); 487 } 488 if (considerCellTs) { 489 long cellTs = cell.getTimestamp(); 490 latestCellTs = Math.max(latestCellTs, cellTs); 491 diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs); 492 } 493 } 494 } 495 } else if (entry.getValue() == null) { 496 get.addFamily(col); 497 } else { 498 throw new RuntimeException( 499 "Unhandled collection type " + entry.getValue().getClass().getName()); 500 } 501 } 502 // We want to avoid looking into the future. So, if the cells of the 503 // operation specify a timestamp, or the operation itself specifies a 504 // timestamp, then we use the maximum ts found. Otherwise, we bound 505 // the Get to the current server time. We add 1 to the timerange since 506 // the upper bound of a timerange is exclusive yet we need to examine 507 // any cells found there inclusively. 508 long latestTs = Math.max(opTs, latestCellTs); 509 if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) { 510 latestTs = EnvironmentEdgeManager.currentTime(); 511 } 512 get.setTimeRange(0, latestTs + 1); 513 // In case of Put operation we set to read all versions. This was done to consider the case 514 // where columns are added with TS other than the Mutation TS. But normally this wont be the 515 // case with Put. There no need to get all versions but get latest version only. 516 if (!diffCellTsFromOpTs && request == OpType.PUT) { 517 get.readVersions(1); 518 } 519 if (LOG.isTraceEnabled()) { 520 LOG.trace("Scanning for cells with " + get); 521 } 522 // This Map is identical to familyMap. The key is a BR rather than byte[]. 523 // It will be easy to do gets over this new Map as we can create get keys over the Cell cf by 524 // new SimpleByteRange(cell.familyArray, cell.familyOffset, cell.familyLen) 525 Map<ByteRange, List<Cell>> familyMap1 = new HashMap<>(); 526 for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) { 527 if (entry.getValue() instanceof List) { 528 familyMap1.put(new SimpleMutableByteRange(entry.getKey()), (List<Cell>) entry.getValue()); 529 } 530 } 531 RegionScanner scanner = getRegion(e).getScanner(new Scan(get)); 532 List<Cell> cells = Lists.newArrayList(); 533 Cell prevCell = null; 534 ByteRange curFam = new SimpleMutableByteRange(); 535 boolean curColAllVersions = (request == OpType.DELETE); 536 long curColCheckTs = opTs; 537 boolean foundColumn = false; 538 try { 539 boolean more = false; 540 ScannerContext scannerContext = ScannerContext.newBuilder().setBatchLimit(1).build(); 541 542 do { 543 cells.clear(); 544 // scan with limit as 1 to hold down memory use on wide rows 545 more = scanner.next(cells, scannerContext); 546 for (Cell cell : cells) { 547 if (LOG.isTraceEnabled()) { 548 LOG.trace("Found cell " + cell); 549 } 550 boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell); 551 if (colChange) foundColumn = false; 552 prevCell = cell; 553 if (!curColAllVersions && foundColumn) { 554 continue; 555 } 556 if (colChange && considerCellTs) { 557 curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength()); 558 List<Cell> cols = familyMap1.get(curFam); 559 for (Cell col : cols) { 560 // null/empty qualifier is used to denote a Family delete. The TS and delete type 561 // associated with this is applicable for all columns within the family. That is 562 // why the below (col.getQualifierLength() == 0) check. 563 if ( 564 (col.getQualifierLength() == 0 && request == OpType.DELETE) 565 || CellUtil.matchingQualifier(cell, col) 566 ) { 567 byte type = PrivateCellUtil.getTypeByte(col); 568 if (considerCellTs) { 569 curColCheckTs = col.getTimestamp(); 570 } 571 // For a Delete op we pass allVersions as true. When a Delete Mutation contains 572 // a version delete for a column no need to check all the covering cells within 573 // that column. Check all versions when Type is DeleteColumn or DeleteFamily 574 // One version delete types are Delete/DeleteFamilyVersion 575 curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type) 576 || (KeyValue.Type.DeleteFamily.getCode() == type); 577 break; 578 } 579 } 580 } 581 if (cell.getTimestamp() > curColCheckTs) { 582 // Just ignore this cell. This is not a covering cell. 583 continue; 584 } 585 foundColumn = true; 586 for (Action action : actions) { 587 // Are there permissions for this user for the cell? 588 if (!getAuthManager().authorizeCell(user, getTableName(e), cell, action)) { 589 // We can stop if the cell ACL denies access 590 return false; 591 } 592 } 593 cellGrants++; 594 } 595 } while (more); 596 } catch (AccessDeniedException ex) { 597 throw ex; 598 } catch (IOException ex) { 599 LOG.error("Exception while getting cells to calculate covering permission", ex); 600 } finally { 601 scanner.close(); 602 } 603 // We should not authorize unless we have found one or more cell ACLs that 604 // grant access. This code is used to check for additional permissions 605 // after no table or CF grants are found. 606 return cellGrants > 0; 607 } 608 609 private static void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) { 610 // Iterate over the entries in the familyMap, replacing the cells therein 611 // with new cells including the ACL data 612 for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) { 613 List<Cell> newCells = Lists.newArrayList(); 614 for (Cell c : e.getValue()) { 615 assert c instanceof ExtendedCell; 616 ExtendedCell cell = (ExtendedCell) c; 617 // Prepend the supplied perms in a new ACL tag to an update list of tags for the cell 618 List<Tag> tags = new ArrayList<>(); 619 tags.add(new ArrayBackedTag(PermissionStorage.ACL_TAG_TYPE, perms)); 620 Iterator<Tag> tagIterator = PrivateCellUtil.tagsIterator(cell); 621 while (tagIterator.hasNext()) { 622 tags.add(tagIterator.next()); 623 } 624 newCells.add(PrivateCellUtil.createCell(cell, tags)); 625 } 626 // This is supposed to be safe, won't CME 627 e.setValue(newCells); 628 } 629 } 630 631 // Checks whether incoming cells contain any tag with type as ACL_TAG_TYPE. This tag 632 // type is reserved and should not be explicitly set by user. 633 private void checkForReservedTagPresence(User user, Mutation m) throws IOException { 634 // No need to check if we're not going to throw 635 if (!authorizationEnabled) { 636 m.setAttribute(TAG_CHECK_PASSED, TRUE); 637 return; 638 } 639 // Superusers are allowed to store cells unconditionally. 640 if (Superusers.isSuperUser(user)) { 641 m.setAttribute(TAG_CHECK_PASSED, TRUE); 642 return; 643 } 644 // We already checked (prePut vs preBatchMutation) 645 if (m.getAttribute(TAG_CHECK_PASSED) != null) { 646 return; 647 } 648 for (ExtendedCellScanner cellScanner = m.cellScanner(); cellScanner.advance();) { 649 Iterator<Tag> tagsItr = PrivateCellUtil.tagsIterator(cellScanner.current()); 650 while (tagsItr.hasNext()) { 651 if (tagsItr.next().getType() == PermissionStorage.ACL_TAG_TYPE) { 652 throw new AccessDeniedException("Mutation contains cell with reserved type tag"); 653 } 654 } 655 } 656 m.setAttribute(TAG_CHECK_PASSED, TRUE); 657 } 658 659 /* ---- MasterObserver implementation ---- */ 660 @Override 661 public void start(CoprocessorEnvironment env) throws IOException { 662 CompoundConfiguration conf = new CompoundConfiguration(); 663 conf.add(env.getConfiguration()); 664 665 authorizationEnabled = AccessChecker.isAuthorizationSupported(conf); 666 if (!authorizationEnabled) { 667 LOG.warn("AccessController has been loaded with authorization checks DISABLED!"); 668 } 669 670 shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY, 671 AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS); 672 673 cellFeaturesEnabled = (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS); 674 if (!cellFeaturesEnabled) { 675 LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS 676 + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY 677 + " accordingly."); 678 } 679 680 if (env instanceof MasterCoprocessorEnvironment) { 681 // if running on HMaster 682 MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env; 683 if (mEnv instanceof HasMasterServices) { 684 MasterServices masterServices = ((HasMasterServices) mEnv).getMasterServices(); 685 zkPermissionWatcher = masterServices.getZKPermissionWatcher(); 686 accessChecker = masterServices.getAccessChecker(); 687 } 688 } else if (env instanceof RegionServerCoprocessorEnvironment) { 689 RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env; 690 if (rsEnv instanceof HasRegionServerServices) { 691 RegionServerServices rsServices = 692 ((HasRegionServerServices) rsEnv).getRegionServerServices(); 693 zkPermissionWatcher = rsServices.getZKPermissionWatcher(); 694 accessChecker = rsServices.getAccessChecker(); 695 } 696 } else if (env instanceof RegionCoprocessorEnvironment) { 697 // if running at region 698 regionEnv = (RegionCoprocessorEnvironment) env; 699 conf.addBytesMap(regionEnv.getRegion().getTableDescriptor().getValues()); 700 compatibleEarlyTermination = conf.getBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, 701 AccessControlConstants.DEFAULT_ATTRIBUTE_EARLY_OUT); 702 if (regionEnv instanceof HasRegionServerServices) { 703 RegionServerServices rsServices = 704 ((HasRegionServerServices) regionEnv).getRegionServerServices(); 705 zkPermissionWatcher = rsServices.getZKPermissionWatcher(); 706 accessChecker = rsServices.getAccessChecker(); 707 } 708 } 709 710 Preconditions.checkState(zkPermissionWatcher != null, "ZKPermissionWatcher is null"); 711 Preconditions.checkState(accessChecker != null, "AccessChecker is null"); 712 713 // set the user-provider. 714 this.userProvider = UserProvider.instantiate(env.getConfiguration()); 715 tableAcls = new MapMaker().weakValues().makeMap(); 716 } 717 718 @Override 719 public void stop(CoprocessorEnvironment env) { 720 } 721 722 /*********************************** Observer/Service Getters ***********************************/ 723 @Override 724 public Optional<RegionObserver> getRegionObserver() { 725 return Optional.of(this); 726 } 727 728 @Override 729 public Optional<MasterObserver> getMasterObserver() { 730 return Optional.of(this); 731 } 732 733 @Override 734 public Optional<EndpointObserver> getEndpointObserver() { 735 return Optional.of(this); 736 } 737 738 @Override 739 public Optional<BulkLoadObserver> getBulkLoadObserver() { 740 return Optional.of(this); 741 } 742 743 @Override 744 public Optional<RegionServerObserver> getRegionServerObserver() { 745 return Optional.of(this); 746 } 747 748 @Override 749 public Iterable<Service> getServices() { 750 return Collections 751 .singleton(AccessControlProtos.AccessControlService.newReflectiveService(this)); 752 } 753 754 /*********************************** Observer implementations ***********************************/ 755 756 @Override 757 public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c, TableDescriptor desc, 758 RegionInfo[] regions) throws IOException { 759 Set<byte[]> families = desc.getColumnFamilyNames(); 760 Map<byte[], Set<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 761 for (byte[] family : families) { 762 familyMap.put(family, null); 763 } 764 requireNamespacePermission(c, "createTable", desc.getTableName().getNamespaceAsString(), 765 desc.getTableName(), familyMap, Action.ADMIN, Action.CREATE); 766 } 767 768 @Override 769 public void postCompletedCreateTableAction(final ObserverContext<MasterCoprocessorEnvironment> c, 770 final TableDescriptor desc, final RegionInfo[] regions) throws IOException { 771 // When AC is used, it should be configured as the 1st CP. 772 // In Master, the table operations like create, are handled by a Thread pool but the max size 773 // for this pool is 1. So if multiple CPs create tables on startup, these creations will happen 774 // sequentially only. 775 // Related code in HMaster#startServiceThreads 776 // {code} 777 // // We depend on there being only one instance of this executor running 778 // // at a time. To do concurrency, would need fencing of enable/disable of 779 // // tables. 780 // this.service.startExecutorService(ExecutorType.MASTER_TABLE_OPERATIONS, 1); 781 // {code} 782 // In future if we change this pool to have more threads, then there is a chance for thread, 783 // creating acl table, getting delayed and by that time another table creation got over and 784 // this hook is getting called. In such a case, we will need a wait logic here which will 785 // wait till the acl table is created. 786 if (PermissionStorage.isAclTable(desc)) { 787 this.aclTabAvailable = true; 788 } else { 789 if (!aclTabAvailable) { 790 LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". " 791 + PermissionStorage.ACL_TABLE_NAME + " is not yet created. " + getClass().getSimpleName() 792 + " should be configured as the first Coprocessor"); 793 } else { 794 String owner = getActiveUser(c).getShortName(); 795 final UserPermission userPermission = new UserPermission(owner, 796 Permission.newBuilder(desc.getTableName()).withActions(Action.values()).build()); 797 // switch to the real hbase master user for doing the RPC on the ACL table 798 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 799 @Override 800 public Void run() throws Exception { 801 try (Table table = 802 c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 803 PermissionStorage.addUserPermission(c.getEnvironment().getConfiguration(), 804 userPermission, table); 805 } 806 return null; 807 } 808 }); 809 } 810 } 811 } 812 813 @Override 814 public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName) 815 throws IOException { 816 requirePermission(c, "deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE); 817 } 818 819 @Override 820 public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, 821 final TableName tableName) throws IOException { 822 final Configuration conf = c.getEnvironment().getConfiguration(); 823 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 824 @Override 825 public Void run() throws Exception { 826 try (Table table = 827 c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 828 PermissionStorage.removeTablePermissions(conf, tableName, table); 829 } 830 return null; 831 } 832 }); 833 zkPermissionWatcher.deleteTableACLNode(tableName); 834 } 835 836 @Override 837 public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c, 838 final TableName tableName) throws IOException { 839 requirePermission(c, "truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE); 840 841 final Configuration conf = c.getEnvironment().getConfiguration(); 842 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 843 @Override 844 public Void run() throws Exception { 845 List<UserPermission> acls = 846 PermissionStorage.getUserTablePermissions(conf, tableName, null, null, null, false); 847 if (acls != null) { 848 tableAcls.put(tableName, acls); 849 } 850 return null; 851 } 852 }); 853 } 854 855 @Override 856 public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 857 final TableName tableName) throws IOException { 858 final Configuration conf = ctx.getEnvironment().getConfiguration(); 859 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 860 @Override 861 public Void run() throws Exception { 862 List<UserPermission> perms = tableAcls.get(tableName); 863 if (perms != null) { 864 for (UserPermission perm : perms) { 865 try (Table table = 866 ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 867 PermissionStorage.addUserPermission(conf, perm, table); 868 } 869 } 870 } 871 tableAcls.remove(tableName); 872 return null; 873 } 874 }); 875 } 876 877 @Override 878 public void preTruncateRegion(final ObserverContext<MasterCoprocessorEnvironment> ctx, 879 final RegionInfo regionInfo) throws IOException { 880 requirePermission(ctx, "truncateRegion", regionInfo.getTable(), null, null, Action.ADMIN, 881 Action.CREATE); 882 } 883 884 @Override 885 public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, 886 TableName tableName, TableDescriptor currentDesc, TableDescriptor newDesc) throws IOException { 887 // TODO: potentially check if this is a add/modify/delete column operation 888 requirePermission(c, "modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE); 889 return newDesc; 890 } 891 892 @Override 893 public String preModifyTableStoreFileTracker(ObserverContext<MasterCoprocessorEnvironment> c, 894 TableName tableName, String dstSFT) throws IOException { 895 requirePermission(c, "modifyTableStoreFileTracker", tableName, null, null, Action.ADMIN, 896 Action.CREATE); 897 return dstSFT; 898 } 899 900 @Override 901 public String preModifyColumnFamilyStoreFileTracker( 902 ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName, byte[] family, 903 String dstSFT) throws IOException { 904 requirePermission(c, "modifyColumnFamilyStoreFileTracker", tableName, family, null, 905 Action.ADMIN, Action.CREATE); 906 return dstSFT; 907 } 908 909 @Override 910 public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName, 911 TableDescriptor oldDesc, TableDescriptor currentDesc) throws IOException { 912 final Configuration conf = c.getEnvironment().getConfiguration(); 913 // default the table owner to current user, if not specified. 914 final String owner = getActiveUser(c).getShortName(); 915 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 916 @Override 917 public Void run() throws Exception { 918 UserPermission userperm = new UserPermission(owner, 919 Permission.newBuilder(currentDesc.getTableName()).withActions(Action.values()).build()); 920 try (Table table = 921 c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 922 PermissionStorage.addUserPermission(conf, userperm, table); 923 } 924 return null; 925 } 926 }); 927 } 928 929 @Override 930 public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName) 931 throws IOException { 932 requirePermission(c, "enableTable", tableName, null, null, Action.ADMIN, Action.CREATE); 933 } 934 935 @Override 936 public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName) 937 throws IOException { 938 if (Bytes.equals(tableName.getName(), PermissionStorage.ACL_GLOBAL_NAME)) { 939 // We have to unconditionally disallow disable of the ACL table when we are installed, 940 // even if not enforcing authorizations. We are still allowing grants and revocations, 941 // checking permissions and logging audit messages, etc. If the ACL table is not 942 // available we will fail random actions all over the place. 943 throw new AccessDeniedException("Not allowed to disable " + PermissionStorage.ACL_TABLE_NAME 944 + " table with AccessController installed"); 945 } 946 requirePermission(c, "disableTable", tableName, null, null, Action.ADMIN, Action.CREATE); 947 } 948 949 @Override 950 public void preAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx, 951 final long procId) throws IOException { 952 requirePermission(ctx, "abortProcedure", Action.ADMIN); 953 } 954 955 @Override 956 public void postAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx) 957 throws IOException { 958 // There is nothing to do at this time after the procedure abort request was sent. 959 } 960 961 @Override 962 public void preGetProcedures(ObserverContext<MasterCoprocessorEnvironment> ctx) 963 throws IOException { 964 requirePermission(ctx, "getProcedure", Action.ADMIN); 965 } 966 967 @Override 968 public void preGetLocks(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException { 969 User user = getActiveUser(ctx); 970 accessChecker.requirePermission(user, "getLocks", null, Action.ADMIN); 971 } 972 973 @Override 974 public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo region, 975 ServerName srcServer, ServerName destServer) throws IOException { 976 requirePermission(c, "move", region.getTable(), null, null, Action.ADMIN); 977 } 978 979 @Override 980 public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo regionInfo) 981 throws IOException { 982 requirePermission(c, "assign", regionInfo.getTable(), null, null, Action.ADMIN); 983 } 984 985 @Override 986 public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, RegionInfo regionInfo) 987 throws IOException { 988 requirePermission(c, "unassign", regionInfo.getTable(), null, null, Action.ADMIN); 989 } 990 991 @Override 992 public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c, 993 RegionInfo regionInfo) throws IOException { 994 requirePermission(c, "regionOffline", regionInfo.getTable(), null, null, Action.ADMIN); 995 } 996 997 @Override 998 public void preSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx, 999 final boolean newValue, final MasterSwitchType switchType) throws IOException { 1000 requirePermission(ctx, "setSplitOrMergeEnabled", Action.ADMIN); 1001 } 1002 1003 @Override 1004 public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c, BalanceRequest request) 1005 throws IOException { 1006 requirePermission(c, "balance", Action.ADMIN); 1007 } 1008 1009 @Override 1010 public void preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c, boolean newValue) 1011 throws IOException { 1012 requirePermission(c, "balanceSwitch", Action.ADMIN); 1013 } 1014 1015 @Override 1016 public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException { 1017 requirePermission(c, "shutdown", Action.ADMIN); 1018 } 1019 1020 @Override 1021 public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException { 1022 requirePermission(c, "stopMaster", Action.ADMIN); 1023 } 1024 1025 @Override 1026 public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) 1027 throws IOException { 1028 try (Admin admin = ctx.getEnvironment().getConnection().getAdmin()) { 1029 if (!admin.tableExists(PermissionStorage.ACL_TABLE_NAME)) { 1030 createACLTable(admin); 1031 } else { 1032 this.aclTabAvailable = true; 1033 } 1034 } 1035 } 1036 1037 /** 1038 * Create the ACL table 1039 */ 1040 private static void createACLTable(Admin admin) throws IOException { 1041 /** Table descriptor for ACL table */ 1042 ColumnFamilyDescriptor cfd = 1043 ColumnFamilyDescriptorBuilder.newBuilder(PermissionStorage.ACL_LIST_FAMILY).setMaxVersions(1) 1044 .setInMemory(true).setBlockCacheEnabled(true).setBlocksize(8 * 1024) 1045 .setBloomFilterType(BloomType.NONE).setScope(HConstants.REPLICATION_SCOPE_LOCAL).build(); 1046 TableDescriptor td = TableDescriptorBuilder.newBuilder(PermissionStorage.ACL_TABLE_NAME) 1047 .setColumnFamily(cfd).build(); 1048 admin.createTable(td); 1049 } 1050 1051 @Override 1052 public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1053 final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException { 1054 // Move this ACL check to SnapshotManager#checkPermissions as part of AC deprecation. 1055 requirePermission(ctx, "snapshot " + snapshot.getName(), hTableDescriptor.getTableName(), null, 1056 null, Permission.Action.ADMIN); 1057 } 1058 1059 @Override 1060 public void preListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx, 1061 final SnapshotDescription snapshot) throws IOException { 1062 User user = getActiveUser(ctx); 1063 if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) { 1064 // list it, if user is the owner of snapshot 1065 AuthResult result = AuthResult.allow("listSnapshot " + snapshot.getName(), 1066 "Snapshot owner check allowed", user, null, null, null); 1067 AccessChecker.logResult(result); 1068 } else { 1069 accessChecker.requirePermission(user, "listSnapshot " + snapshot.getName(), null, 1070 Action.ADMIN); 1071 } 1072 } 1073 1074 @Override 1075 public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1076 final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException { 1077 User user = getActiveUser(ctx); 1078 if ( 1079 SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user) 1080 && hTableDescriptor.getTableName().getNameAsString().equals(snapshot.getTableNameAsString()) 1081 ) { 1082 // Snapshot owner is allowed to create a table with the same name as the snapshot he took 1083 AuthResult result = AuthResult.allow("cloneSnapshot " + snapshot.getName(), 1084 "Snapshot owner check allowed", user, null, hTableDescriptor.getTableName(), null); 1085 AccessChecker.logResult(result); 1086 } else if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) { 1087 requireNamespacePermission(ctx, "cloneSnapshot", 1088 hTableDescriptor.getTableName().getNamespaceAsString(), Action.ADMIN); 1089 } else { 1090 accessChecker.requirePermission(user, "cloneSnapshot " + snapshot.getName(), null, 1091 Action.ADMIN); 1092 } 1093 } 1094 1095 @Override 1096 public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1097 final SnapshotDescription snapshot, final TableDescriptor hTableDescriptor) throws IOException { 1098 User user = getActiveUser(ctx); 1099 if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) { 1100 accessChecker.requirePermission(user, "restoreSnapshot " + snapshot.getName(), 1101 hTableDescriptor.getTableName(), null, null, null, Permission.Action.ADMIN); 1102 } else { 1103 accessChecker.requirePermission(user, "restoreSnapshot " + snapshot.getName(), null, 1104 Action.ADMIN); 1105 } 1106 } 1107 1108 @Override 1109 public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1110 final SnapshotDescription snapshot) throws IOException { 1111 User user = getActiveUser(ctx); 1112 if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) { 1113 // Snapshot owner is allowed to delete the snapshot 1114 AuthResult result = AuthResult.allow("deleteSnapshot " + snapshot.getName(), 1115 "Snapshot owner check allowed", user, null, null, null); 1116 AccessChecker.logResult(result); 1117 } else { 1118 accessChecker.requirePermission(user, "deleteSnapshot " + snapshot.getName(), null, 1119 Action.ADMIN); 1120 } 1121 } 1122 1123 @Override 1124 public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, 1125 NamespaceDescriptor ns) throws IOException { 1126 requireGlobalPermission(ctx, "createNamespace", Action.ADMIN, ns.getName()); 1127 } 1128 1129 @Override 1130 public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, 1131 String namespace) throws IOException { 1132 requireGlobalPermission(ctx, "deleteNamespace", Action.ADMIN, namespace); 1133 } 1134 1135 @Override 1136 public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, 1137 final String namespace) throws IOException { 1138 final Configuration conf = ctx.getEnvironment().getConfiguration(); 1139 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { 1140 @Override 1141 public Void run() throws Exception { 1142 try (Table table = 1143 ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 1144 PermissionStorage.removeNamespacePermissions(conf, namespace, table); 1145 } 1146 return null; 1147 } 1148 }); 1149 zkPermissionWatcher.deleteNamespaceACLNode(namespace); 1150 LOG.info(namespace + " entry deleted in " + PermissionStorage.ACL_TABLE_NAME + " table."); 1151 } 1152 1153 @Override 1154 public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, 1155 NamespaceDescriptor currentNsDesc, NamespaceDescriptor newNsDesc) throws IOException { 1156 // We require only global permission so that 1157 // a user with NS admin cannot altering namespace configurations. i.e. namespace quota 1158 requireGlobalPermission(ctx, "modifyNamespace", Action.ADMIN, newNsDesc.getName()); 1159 } 1160 1161 @Override 1162 public void preGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx, 1163 String namespace) throws IOException { 1164 requireNamespacePermission(ctx, "getNamespaceDescriptor", namespace, Action.ADMIN); 1165 } 1166 1167 @Override 1168 public void postListNamespaces(ObserverContext<MasterCoprocessorEnvironment> ctx, 1169 List<String> namespaces) throws IOException { 1170 /* always allow namespace listing */ 1171 } 1172 1173 @Override 1174 public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, 1175 List<NamespaceDescriptor> descriptors) throws IOException { 1176 // Retains only those which passes authorization checks, as the checks weren't done as part 1177 // of preGetTableDescriptors. 1178 Iterator<NamespaceDescriptor> itr = descriptors.iterator(); 1179 User user = getActiveUser(ctx); 1180 while (itr.hasNext()) { 1181 NamespaceDescriptor desc = itr.next(); 1182 try { 1183 accessChecker.requireNamespacePermission(user, "listNamespaces", desc.getName(), null, 1184 Action.ADMIN); 1185 } catch (AccessDeniedException e) { 1186 itr.remove(); 1187 } 1188 } 1189 } 1190 1191 @Override 1192 public void preTableFlush(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1193 final TableName tableName) throws IOException { 1194 // Move this ACL check to MasterFlushTableProcedureManager#checkPermissions as part of AC 1195 // deprecation. 1196 requirePermission(ctx, "flushTable", tableName, null, null, Action.ADMIN, Action.CREATE); 1197 } 1198 1199 @Override 1200 public void preSplitRegion(final ObserverContext<MasterCoprocessorEnvironment> ctx, 1201 final TableName tableName, final byte[] splitRow) throws IOException { 1202 requirePermission(ctx, "split", tableName, null, null, Action.ADMIN); 1203 } 1204 1205 @Override 1206 public void preClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx) 1207 throws IOException { 1208 requirePermission(ctx, "clearDeadServers", Action.ADMIN); 1209 } 1210 1211 @Override 1212 public void preDecommissionRegionServers(ObserverContext<MasterCoprocessorEnvironment> ctx, 1213 List<ServerName> servers, boolean offload) throws IOException { 1214 requirePermission(ctx, "decommissionRegionServers", Action.ADMIN); 1215 } 1216 1217 @Override 1218 public void preListDecommissionedRegionServers(ObserverContext<MasterCoprocessorEnvironment> ctx) 1219 throws IOException { 1220 requirePermission(ctx, "listDecommissionedRegionServers", Action.READ); 1221 } 1222 1223 @Override 1224 public void preRecommissionRegionServer(ObserverContext<MasterCoprocessorEnvironment> ctx, 1225 ServerName server, List<byte[]> encodedRegionNames) throws IOException { 1226 requirePermission(ctx, "recommissionRegionServers", Action.ADMIN); 1227 } 1228 1229 /* ---- RegionObserver implementation ---- */ 1230 1231 @Override 1232 public void preOpen(ObserverContext<? extends RegionCoprocessorEnvironment> c) 1233 throws IOException { 1234 RegionCoprocessorEnvironment env = c.getEnvironment(); 1235 final Region region = env.getRegion(); 1236 if (region == null) { 1237 LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()"); 1238 } else { 1239 RegionInfo regionInfo = region.getRegionInfo(); 1240 if (regionInfo.getTable().isSystemTable()) { 1241 checkSystemOrSuperUser(getActiveUser(c)); 1242 } else { 1243 requirePermission(c, "preOpen", Action.ADMIN); 1244 } 1245 } 1246 } 1247 1248 @Override 1249 public void postOpen(ObserverContext<? extends RegionCoprocessorEnvironment> c) { 1250 RegionCoprocessorEnvironment env = c.getEnvironment(); 1251 final Region region = env.getRegion(); 1252 if (region == null) { 1253 LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()"); 1254 return; 1255 } 1256 if (PermissionStorage.isAclRegion(region)) { 1257 aclRegion = true; 1258 try { 1259 initialize(env); 1260 } catch (IOException ex) { 1261 // if we can't obtain permissions, it's better to fail 1262 // than perform checks incorrectly 1263 throw new RuntimeException("Failed to initialize permissions cache", ex); 1264 } 1265 } else { 1266 initialized = true; 1267 } 1268 } 1269 1270 @Override 1271 public void preFlush(ObserverContext<? extends RegionCoprocessorEnvironment> c, 1272 FlushLifeCycleTracker tracker) throws IOException { 1273 requirePermission(c, "flush", getTableName(c.getEnvironment()), null, null, Action.ADMIN, 1274 Action.CREATE); 1275 } 1276 1277 @Override 1278 public InternalScanner preCompact(ObserverContext<? extends RegionCoprocessorEnvironment> c, 1279 Store store, InternalScanner scanner, ScanType scanType, CompactionLifeCycleTracker tracker, 1280 CompactionRequest request) throws IOException { 1281 requirePermission(c, "compact", getTableName(c.getEnvironment()), null, null, Action.ADMIN, 1282 Action.CREATE); 1283 return scanner; 1284 } 1285 1286 private void internalPreRead(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1287 final Query query, OpType opType) throws IOException { 1288 Filter filter = query.getFilter(); 1289 // Don't wrap an AccessControlFilter 1290 if (filter != null && filter instanceof AccessControlFilter) { 1291 return; 1292 } 1293 User user = getActiveUser(c); 1294 RegionCoprocessorEnvironment env = c.getEnvironment(); 1295 Map<byte[], ? extends Collection<byte[]>> families = null; 1296 switch (opType) { 1297 case GET: 1298 case EXISTS: 1299 families = ((Get) query).getFamilyMap(); 1300 break; 1301 case SCAN: 1302 families = ((Scan) query).getFamilyMap(); 1303 break; 1304 default: 1305 throw new RuntimeException("Unhandled operation " + opType); 1306 } 1307 AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ); 1308 Region region = getRegion(env); 1309 TableName table = getTableName(region); 1310 Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap(); 1311 for (ColumnFamilyDescriptor hcd : region.getTableDescriptor().getColumnFamilies()) { 1312 cfVsMaxVersions.put(new SimpleMutableByteRange(hcd.getName()), hcd.getMaxVersions()); 1313 } 1314 if (!authResult.isAllowed()) { 1315 if (!cellFeaturesEnabled || compatibleEarlyTermination) { 1316 // Old behavior: Scan with only qualifier checks if we have partial 1317 // permission. Backwards compatible behavior is to throw an 1318 // AccessDeniedException immediately if there are no grants for table 1319 // or CF or CF+qual. Only proceed with an injected filter if there are 1320 // grants for qualifiers. Otherwise we will fall through below and log 1321 // the result and throw an ADE. We may end up checking qualifier 1322 // grants three times (permissionGranted above, here, and in the 1323 // filter) but that's the price of backwards compatibility. 1324 if (hasFamilyQualifierPermission(user, Action.READ, env, families)) { 1325 authResult.setAllowed(true); 1326 authResult.setReason("Access allowed with filter"); 1327 // Only wrap the filter if we are enforcing authorizations 1328 if (authorizationEnabled) { 1329 Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table, 1330 AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY, cfVsMaxVersions); 1331 // wrap any existing filter 1332 if (filter != null) { 1333 ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, 1334 Lists.newArrayList(ourFilter, filter)); 1335 } 1336 switch (opType) { 1337 case GET: 1338 case EXISTS: 1339 ((Get) query).setFilter(ourFilter); 1340 break; 1341 case SCAN: 1342 ((Scan) query).setFilter(ourFilter); 1343 break; 1344 default: 1345 throw new RuntimeException("Unhandled operation " + opType); 1346 } 1347 } 1348 } 1349 } else { 1350 // New behavior: Any access we might be granted is more fine-grained 1351 // than whole table or CF. Simply inject a filter and return what is 1352 // allowed. We will not throw an AccessDeniedException. This is a 1353 // behavioral change since 0.96. 1354 authResult.setAllowed(true); 1355 authResult.setReason("Access allowed with filter"); 1356 // Only wrap the filter if we are enforcing authorizations 1357 if (authorizationEnabled) { 1358 Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table, 1359 AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions); 1360 // wrap any existing filter 1361 if (filter != null) { 1362 ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, 1363 Lists.newArrayList(ourFilter, filter)); 1364 } 1365 switch (opType) { 1366 case GET: 1367 case EXISTS: 1368 ((Get) query).setFilter(ourFilter); 1369 break; 1370 case SCAN: 1371 ((Scan) query).setFilter(ourFilter); 1372 break; 1373 default: 1374 throw new RuntimeException("Unhandled operation " + opType); 1375 } 1376 } 1377 } 1378 } 1379 1380 AccessChecker.logResult(authResult); 1381 if (authorizationEnabled && !authResult.isAllowed()) { 1382 throw new AccessDeniedException("Insufficient permissions for user '" 1383 + (user != null ? user.getShortName() : "null") + "' (table=" + table + ", action=READ)"); 1384 } 1385 } 1386 1387 @Override 1388 public void preGetOp(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1389 final Get get, final List<Cell> result) throws IOException { 1390 internalPreRead(c, get, OpType.GET); 1391 } 1392 1393 @Override 1394 public boolean preExists(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1395 final Get get, final boolean exists) throws IOException { 1396 internalPreRead(c, get, OpType.EXISTS); 1397 return exists; 1398 } 1399 1400 @Override 1401 public void prePut(final ObserverContext<? extends RegionCoprocessorEnvironment> c, final Put put, 1402 final WALEdit edit, final Durability durability) throws IOException { 1403 User user = getActiveUser(c); 1404 checkForReservedTagPresence(user, put); 1405 1406 // Require WRITE permission to the table, CF, or top visible value, if any. 1407 // NOTE: We don't need to check the permissions for any earlier Puts 1408 // because we treat the ACLs in each Put as timestamped like any other 1409 // HBase value. A new ACL in a new Put applies to that Put. It doesn't 1410 // change the ACL of any previous Put. This allows simple evolution of 1411 // security policy over time without requiring expensive updates. 1412 RegionCoprocessorEnvironment env = c.getEnvironment(); 1413 Map<byte[], ? extends Collection<Cell>> families = put.getFamilyCellMap(); 1414 AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE); 1415 AccessChecker.logResult(authResult); 1416 if (!authResult.isAllowed()) { 1417 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1418 put.setAttribute(CHECK_COVERING_PERM, TRUE); 1419 } else if (authorizationEnabled) { 1420 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1421 } 1422 } 1423 1424 // Add cell ACLs from the operation to the cells themselves 1425 byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); 1426 if (bytes != null) { 1427 if (cellFeaturesEnabled) { 1428 addCellPermissions(bytes, put.getFamilyCellMap()); 1429 } else { 1430 throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); 1431 } 1432 } 1433 } 1434 1435 @Override 1436 public void postPut(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1437 final Put put, final WALEdit edit, final Durability durability) { 1438 if (aclRegion) { 1439 updateACL(c.getEnvironment(), put.getFamilyCellMap()); 1440 } 1441 } 1442 1443 @Override 1444 public void preDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1445 final Delete delete, final WALEdit edit, final Durability durability) throws IOException { 1446 // An ACL on a delete is useless, we shouldn't allow it 1447 if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) { 1448 throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString()); 1449 } 1450 // Require WRITE permissions on all cells covered by the delete. Unlike 1451 // for Puts we need to check all visible prior versions, because a major 1452 // compaction could remove them. If the user doesn't have permission to 1453 // overwrite any of the visible versions ('visible' defined as not covered 1454 // by a tombstone already) then we have to disallow this operation. 1455 RegionCoprocessorEnvironment env = c.getEnvironment(); 1456 Map<byte[], ? extends Collection<Cell>> families = delete.getFamilyCellMap(); 1457 User user = getActiveUser(c); 1458 AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE); 1459 AccessChecker.logResult(authResult); 1460 if (!authResult.isAllowed()) { 1461 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1462 delete.setAttribute(CHECK_COVERING_PERM, TRUE); 1463 } else if (authorizationEnabled) { 1464 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1465 } 1466 } 1467 } 1468 1469 @Override 1470 public void preBatchMutate(ObserverContext<? extends RegionCoprocessorEnvironment> c, 1471 MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException { 1472 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1473 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable(); 1474 User user = getActiveUser(c); 1475 for (int i = 0; i < miniBatchOp.size(); i++) { 1476 Mutation m = miniBatchOp.getOperation(i); 1477 if (m.getAttribute(CHECK_COVERING_PERM) != null) { 1478 // We have a failure with table, cf and q perm checks and now giving a chance for cell 1479 // perm check 1480 OpType opType; 1481 long timestamp; 1482 if (m instanceof Put) { 1483 checkForReservedTagPresence(user, m); 1484 opType = OpType.PUT; 1485 timestamp = m.getTimestamp(); 1486 } else if (m instanceof Delete) { 1487 opType = OpType.DELETE; 1488 timestamp = m.getTimestamp(); 1489 } else if (m instanceof Increment) { 1490 opType = OpType.INCREMENT; 1491 timestamp = ((Increment) m).getTimeRange().getMax(); 1492 } else if (m instanceof Append) { 1493 opType = OpType.APPEND; 1494 timestamp = ((Append) m).getTimeRange().getMax(); 1495 } else { 1496 // If the operation type is not Put/Delete/Increment/Append, do nothing 1497 continue; 1498 } 1499 AuthResult authResult = null; 1500 if ( 1501 checkCoveringPermission(user, opType, c.getEnvironment(), m.getRow(), 1502 m.getFamilyCellMap(), timestamp, Action.WRITE) 1503 ) { 1504 authResult = AuthResult.allow(opType.toString(), "Covering cell set", user, 1505 Action.WRITE, table, m.getFamilyCellMap()); 1506 } else { 1507 authResult = AuthResult.deny(opType.toString(), "Covering cell set", user, Action.WRITE, 1508 table, m.getFamilyCellMap()); 1509 } 1510 AccessChecker.logResult(authResult); 1511 if (authorizationEnabled && !authResult.isAllowed()) { 1512 throw new AccessDeniedException( 1513 "Insufficient permissions " + authResult.toContextString()); 1514 } 1515 } 1516 } 1517 } 1518 } 1519 1520 @Override 1521 public void postDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1522 final Delete delete, final WALEdit edit, final Durability durability) throws IOException { 1523 if (aclRegion) { 1524 updateACL(c.getEnvironment(), delete.getFamilyCellMap()); 1525 } 1526 } 1527 1528 @Override 1529 public boolean preCheckAndPut(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1530 final byte[] row, final byte[] family, final byte[] qualifier, final CompareOperator op, 1531 final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException { 1532 User user = getActiveUser(c); 1533 checkForReservedTagPresence(user, put); 1534 1535 // Require READ and WRITE permissions on the table, CF, and KV to update 1536 RegionCoprocessorEnvironment env = c.getEnvironment(); 1537 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier); 1538 AuthResult authResult = 1539 permissionGranted(OpType.CHECK_AND_PUT, user, env, families, Action.READ, Action.WRITE); 1540 AccessChecker.logResult(authResult); 1541 if (!authResult.isAllowed()) { 1542 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1543 put.setAttribute(CHECK_COVERING_PERM, TRUE); 1544 } else if (authorizationEnabled) { 1545 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1546 } 1547 } 1548 1549 byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); 1550 if (bytes != null) { 1551 if (cellFeaturesEnabled) { 1552 addCellPermissions(bytes, put.getFamilyCellMap()); 1553 } else { 1554 throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); 1555 } 1556 } 1557 return result; 1558 } 1559 1560 @Override 1561 public boolean preCheckAndPutAfterRowLock( 1562 final ObserverContext<? extends RegionCoprocessorEnvironment> c, final byte[] row, 1563 final byte[] family, final byte[] qualifier, final CompareOperator opp, 1564 final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException { 1565 if (put.getAttribute(CHECK_COVERING_PERM) != null) { 1566 // We had failure with table, cf and q perm checks and now giving a chance for cell 1567 // perm check 1568 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable(); 1569 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier); 1570 AuthResult authResult = null; 1571 User user = getActiveUser(c); 1572 if ( 1573 checkCoveringPermission(user, OpType.CHECK_AND_PUT, c.getEnvironment(), row, families, 1574 HConstants.LATEST_TIMESTAMP, Action.READ) 1575 ) { 1576 authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set", user, 1577 Action.READ, table, families); 1578 } else { 1579 authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set", user, 1580 Action.READ, table, families); 1581 } 1582 AccessChecker.logResult(authResult); 1583 if (authorizationEnabled && !authResult.isAllowed()) { 1584 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1585 } 1586 } 1587 return result; 1588 } 1589 1590 @Override 1591 public boolean preCheckAndDelete(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1592 final byte[] row, final byte[] family, final byte[] qualifier, final CompareOperator op, 1593 final ByteArrayComparable comparator, final Delete delete, final boolean result) 1594 throws IOException { 1595 // An ACL on a delete is useless, we shouldn't allow it 1596 if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) { 1597 throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " + delete.toString()); 1598 } 1599 // Require READ and WRITE permissions on the table, CF, and the KV covered 1600 // by the delete 1601 RegionCoprocessorEnvironment env = c.getEnvironment(); 1602 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier); 1603 User user = getActiveUser(c); 1604 AuthResult authResult = 1605 permissionGranted(OpType.CHECK_AND_DELETE, user, env, families, Action.READ, Action.WRITE); 1606 AccessChecker.logResult(authResult); 1607 if (!authResult.isAllowed()) { 1608 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1609 delete.setAttribute(CHECK_COVERING_PERM, TRUE); 1610 } else if (authorizationEnabled) { 1611 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1612 } 1613 } 1614 return result; 1615 } 1616 1617 @Override 1618 public boolean preCheckAndDeleteAfterRowLock( 1619 final ObserverContext<? extends RegionCoprocessorEnvironment> c, final byte[] row, 1620 final byte[] family, final byte[] qualifier, final CompareOperator op, 1621 final ByteArrayComparable comparator, final Delete delete, final boolean result) 1622 throws IOException { 1623 if (delete.getAttribute(CHECK_COVERING_PERM) != null) { 1624 // We had failure with table, cf and q perm checks and now giving a chance for cell 1625 // perm check 1626 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable(); 1627 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier); 1628 AuthResult authResult = null; 1629 User user = getActiveUser(c); 1630 if ( 1631 checkCoveringPermission(user, OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families, 1632 HConstants.LATEST_TIMESTAMP, Action.READ) 1633 ) { 1634 authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set", user, 1635 Action.READ, table, families); 1636 } else { 1637 authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set", user, 1638 Action.READ, table, families); 1639 } 1640 AccessChecker.logResult(authResult); 1641 if (authorizationEnabled && !authResult.isAllowed()) { 1642 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1643 } 1644 } 1645 return result; 1646 } 1647 1648 @Override 1649 public Result preAppend(ObserverContext<? extends RegionCoprocessorEnvironment> c, Append append) 1650 throws IOException { 1651 User user = getActiveUser(c); 1652 checkForReservedTagPresence(user, append); 1653 1654 // Require WRITE permission to the table, CF, and the KV to be appended 1655 RegionCoprocessorEnvironment env = c.getEnvironment(); 1656 Map<byte[], ? extends Collection<Cell>> families = append.getFamilyCellMap(); 1657 AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE); 1658 AccessChecker.logResult(authResult); 1659 if (!authResult.isAllowed()) { 1660 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1661 append.setAttribute(CHECK_COVERING_PERM, TRUE); 1662 } else if (authorizationEnabled) { 1663 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1664 } 1665 } 1666 1667 byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); 1668 if (bytes != null) { 1669 if (cellFeaturesEnabled) { 1670 addCellPermissions(bytes, append.getFamilyCellMap()); 1671 } else { 1672 throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); 1673 } 1674 } 1675 1676 return null; 1677 } 1678 1679 @Override 1680 public Result preIncrement(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1681 final Increment increment) throws IOException { 1682 User user = getActiveUser(c); 1683 checkForReservedTagPresence(user, increment); 1684 1685 // Require WRITE permission to the table, CF, and the KV to be replaced by 1686 // the incremented value 1687 RegionCoprocessorEnvironment env = c.getEnvironment(); 1688 Map<byte[], ? extends Collection<Cell>> families = increment.getFamilyCellMap(); 1689 AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families, Action.WRITE); 1690 AccessChecker.logResult(authResult); 1691 if (!authResult.isAllowed()) { 1692 if (cellFeaturesEnabled && !compatibleEarlyTermination) { 1693 increment.setAttribute(CHECK_COVERING_PERM, TRUE); 1694 } else if (authorizationEnabled) { 1695 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); 1696 } 1697 } 1698 1699 byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); 1700 if (bytes != null) { 1701 if (cellFeaturesEnabled) { 1702 addCellPermissions(bytes, increment.getFamilyCellMap()); 1703 } else { 1704 throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); 1705 } 1706 } 1707 1708 return null; 1709 } 1710 1711 @Override 1712 public List<Pair<Cell, Cell>> postIncrementBeforeWAL( 1713 ObserverContext<? extends RegionCoprocessorEnvironment> ctx, Mutation mutation, 1714 List<Pair<Cell, Cell>> cellPairs) throws IOException { 1715 // If the HFile version is insufficient to persist tags, we won't have any 1716 // work to do here 1717 if (!cellFeaturesEnabled || mutation.getACL() == null) { 1718 return cellPairs; 1719 } 1720 return cellPairs.stream() 1721 .map(pair -> new Pair<>(pair.getFirst(), 1722 createNewCellWithTags(mutation, pair.getFirst(), pair.getSecond()))) 1723 .collect(Collectors.toList()); 1724 } 1725 1726 @Override 1727 public List<Pair<Cell, Cell>> postAppendBeforeWAL( 1728 ObserverContext<? extends RegionCoprocessorEnvironment> ctx, Mutation mutation, 1729 List<Pair<Cell, Cell>> cellPairs) throws IOException { 1730 // If the HFile version is insufficient to persist tags, we won't have any 1731 // work to do here 1732 if (!cellFeaturesEnabled || mutation.getACL() == null) { 1733 return cellPairs; 1734 } 1735 return cellPairs.stream() 1736 .map(pair -> new Pair<>(pair.getFirst(), 1737 createNewCellWithTags(mutation, pair.getFirst(), pair.getSecond()))) 1738 .collect(Collectors.toList()); 1739 } 1740 1741 private Cell createNewCellWithTags(Mutation mutation, Cell oldCell, Cell newCell) { 1742 // As Increment and Append operations have already copied the tags of oldCell to the newCell, 1743 // there is no need to rewrite them again. Just extract non-acl tags of newCell if we need to 1744 // add a new acl tag for the cell. Actually, oldCell is useless here. 1745 List<Tag> tags = Lists.newArrayList(); 1746 ExtendedCell newExtendedCell = (ExtendedCell) newCell; 1747 if (newExtendedCell != null) { 1748 Iterator<Tag> tagIterator = PrivateCellUtil.tagsIterator(newExtendedCell); 1749 while (tagIterator.hasNext()) { 1750 Tag tag = tagIterator.next(); 1751 if (tag.getType() != PermissionStorage.ACL_TAG_TYPE) { 1752 // Not an ACL tag, just carry it through 1753 if (LOG.isTraceEnabled()) { 1754 LOG.trace("Carrying forward tag from " + newCell + ": type " + tag.getType() 1755 + " length " + tag.getValueLength()); 1756 } 1757 tags.add(tag); 1758 } 1759 } 1760 } 1761 1762 // We have checked the ACL tag of mutation is not null. 1763 // So that the tags could not be empty. 1764 tags.add(new ArrayBackedTag(PermissionStorage.ACL_TAG_TYPE, mutation.getACL())); 1765 return PrivateCellUtil.createCell(newExtendedCell, tags); 1766 } 1767 1768 @Override 1769 public void preScannerOpen(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1770 final Scan scan) throws IOException { 1771 internalPreRead(c, scan, OpType.SCAN); 1772 } 1773 1774 @Override 1775 public RegionScanner postScannerOpen( 1776 final ObserverContext<? extends RegionCoprocessorEnvironment> c, final Scan scan, 1777 final RegionScanner s) throws IOException { 1778 User user = getActiveUser(c); 1779 if (user != null && user.getShortName() != null) { 1780 // store reference to scanner owner for later checks 1781 scannerOwners.put(s, user.getShortName()); 1782 } 1783 return s; 1784 } 1785 1786 @Override 1787 public boolean preScannerNext(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1788 final InternalScanner s, final List<Result> result, final int limit, final boolean hasNext) 1789 throws IOException { 1790 requireScannerOwner(s); 1791 return hasNext; 1792 } 1793 1794 @Override 1795 public void preScannerClose(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1796 final InternalScanner s) throws IOException { 1797 requireScannerOwner(s); 1798 } 1799 1800 @Override 1801 public void postScannerClose(final ObserverContext<? extends RegionCoprocessorEnvironment> c, 1802 final InternalScanner s) throws IOException { 1803 // clean up any associated owner mapping 1804 scannerOwners.remove(s); 1805 } 1806 1807 /** 1808 * Verify, when servicing an RPC, that the caller is the scanner owner. If so, we assume that 1809 * access control is correctly enforced based on the checks performed in preScannerOpen() 1810 */ 1811 private void requireScannerOwner(InternalScanner s) throws AccessDeniedException { 1812 if (!RpcServer.isInRpcCallContext()) { 1813 return; 1814 } 1815 String requestUserName = RpcServer.getRequestUserName().orElse(null); 1816 String owner = scannerOwners.get(s); 1817 if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) { 1818 throw new AccessDeniedException("User '" + requestUserName + "' is not the scanner owner!"); 1819 } 1820 } 1821 1822 /** 1823 * Verifies user has CREATE or ADMIN privileges on the Column Families involved in the 1824 * bulkLoadHFile request. Specific Column Write privileges are presently ignored. 1825 */ 1826 @Override 1827 public void preBulkLoadHFile(ObserverContext<? extends RegionCoprocessorEnvironment> ctx, 1828 List<Pair<byte[], String>> familyPaths) throws IOException { 1829 User user = getActiveUser(ctx); 1830 for (Pair<byte[], String> el : familyPaths) { 1831 accessChecker.requirePermission(user, "preBulkLoadHFile", 1832 ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), el.getFirst(), null, 1833 null, Action.ADMIN, Action.CREATE); 1834 } 1835 } 1836 1837 /** 1838 * Authorization check for SecureBulkLoadProtocol.prepareBulkLoad() 1839 * @param ctx the context 1840 */ 1841 @Override 1842 public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx) 1843 throws IOException { 1844 requireAccess(ctx, "prePrepareBulkLoad", 1845 ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), Action.ADMIN, 1846 Action.CREATE); 1847 } 1848 1849 /** 1850 * Authorization security check for SecureBulkLoadProtocol.cleanupBulkLoad() 1851 * @param ctx the context 1852 */ 1853 @Override 1854 public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx) 1855 throws IOException { 1856 requireAccess(ctx, "preCleanupBulkLoad", 1857 ctx.getEnvironment().getRegion().getTableDescriptor().getTableName(), Action.ADMIN, 1858 Action.CREATE); 1859 } 1860 1861 /* ---- EndpointObserver implementation ---- */ 1862 1863 @Override 1864 public Message preEndpointInvocation(ObserverContext<? extends RegionCoprocessorEnvironment> ctx, 1865 Service service, String methodName, Message request) throws IOException { 1866 // Don't intercept calls to our own AccessControlService, we check for 1867 // appropriate permissions in the service handlers 1868 if (shouldCheckExecPermission && !(service instanceof AccessControlService)) { 1869 requirePermission(ctx, 1870 "invoke(" + service.getDescriptorForType().getName() + "." + methodName + ")", 1871 getTableName(ctx.getEnvironment()), null, null, Action.EXEC); 1872 } 1873 return request; 1874 } 1875 1876 @Override 1877 public void postEndpointInvocation(ObserverContext<? extends RegionCoprocessorEnvironment> ctx, 1878 Service service, String methodName, Message request, Message.Builder responseBuilder) 1879 throws IOException { 1880 } 1881 1882 /* ---- Protobuf AccessControlService implementation ---- */ 1883 1884 /** 1885 * @deprecated since 2.2.0 and will be removed in 4.0.0. Use 1886 * {@link Admin#grant(UserPermission, boolean)} instead. 1887 * @see Admin#grant(UserPermission, boolean) 1888 * @see <a href="https://issues.apache.org/jira/browse/HBASE-21739">HBASE-21739</a> 1889 */ 1890 @Deprecated 1891 @Override 1892 public void grant(RpcController controller, AccessControlProtos.GrantRequest request, 1893 RpcCallback<AccessControlProtos.GrantResponse> done) { 1894 final UserPermission perm = AccessControlUtil.toUserPermission(request.getUserPermission()); 1895 AccessControlProtos.GrantResponse response = null; 1896 try { 1897 // verify it's only running at .acl. 1898 if (aclRegion) { 1899 if (!initialized) { 1900 throw new CoprocessorException("AccessController not yet initialized"); 1901 } 1902 User caller = RpcServer.getRequestUser().orElse(null); 1903 if (LOG.isDebugEnabled()) { 1904 LOG.debug("Received request from {} to grant access permission {}", caller.getName(), 1905 perm.toString()); 1906 } 1907 preGrantOrRevoke(caller, "grant", perm); 1908 1909 // regionEnv is set at #start. Hopefully not null at this point. 1910 regionEnv.getConnection().getAdmin().grant( 1911 new UserPermission(perm.getUser(), perm.getPermission()), 1912 request.getMergeExistingPermissions()); 1913 if (AUDITLOG.isTraceEnabled()) { 1914 // audit log should store permission changes in addition to auth results 1915 AUDITLOG.trace("Granted permission " + perm.toString()); 1916 } 1917 } else { 1918 throw new CoprocessorException(AccessController.class, 1919 "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table."); 1920 } 1921 response = AccessControlProtos.GrantResponse.getDefaultInstance(); 1922 } catch (IOException ioe) { 1923 // pass exception back up 1924 CoprocessorRpcUtils.setControllerException(controller, ioe); 1925 } 1926 done.run(response); 1927 } 1928 1929 /** 1930 * @deprecated since 2.2.0 and will be removed in 4.0.0. Use {@link Admin#revoke(UserPermission)} 1931 * instead. 1932 * @see Admin#revoke(UserPermission) 1933 * @see <a href="https://issues.apache.org/jira/browse/HBASE-21739">HBASE-21739</a> 1934 */ 1935 @Deprecated 1936 @Override 1937 public void revoke(RpcController controller, AccessControlProtos.RevokeRequest request, 1938 RpcCallback<AccessControlProtos.RevokeResponse> done) { 1939 final UserPermission perm = AccessControlUtil.toUserPermission(request.getUserPermission()); 1940 AccessControlProtos.RevokeResponse response = null; 1941 try { 1942 // only allowed to be called on _acl_ region 1943 if (aclRegion) { 1944 if (!initialized) { 1945 throw new CoprocessorException("AccessController not yet initialized"); 1946 } 1947 User caller = RpcServer.getRequestUser().orElse(null); 1948 if (LOG.isDebugEnabled()) { 1949 LOG.debug("Received request from {} to revoke access permission {}", 1950 caller.getShortName(), perm.toString()); 1951 } 1952 preGrantOrRevoke(caller, "revoke", perm); 1953 // regionEnv is set at #start. Hopefully not null here. 1954 regionEnv.getConnection().getAdmin() 1955 .revoke(new UserPermission(perm.getUser(), perm.getPermission())); 1956 if (AUDITLOG.isTraceEnabled()) { 1957 // audit log should record all permission changes 1958 AUDITLOG.trace("Revoked permission " + perm.toString()); 1959 } 1960 } else { 1961 throw new CoprocessorException(AccessController.class, 1962 "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table."); 1963 } 1964 response = AccessControlProtos.RevokeResponse.getDefaultInstance(); 1965 } catch (IOException ioe) { 1966 // pass exception back up 1967 CoprocessorRpcUtils.setControllerException(controller, ioe); 1968 } 1969 done.run(response); 1970 } 1971 1972 /** 1973 * @deprecated since 2.2.0 and will be removed in 4.0.0. Use 1974 * {@link Admin#getUserPermissions(GetUserPermissionsRequest)} instead. 1975 * @see Admin#getUserPermissions(GetUserPermissionsRequest) 1976 * @see <a href="https://issues.apache.org/jira/browse/HBASE-21911">HBASE-21911</a> 1977 */ 1978 @Deprecated 1979 @Override 1980 public void getUserPermissions(RpcController controller, 1981 AccessControlProtos.GetUserPermissionsRequest request, 1982 RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) { 1983 AccessControlProtos.GetUserPermissionsResponse response = null; 1984 try { 1985 // only allowed to be called on _acl_ region 1986 if (aclRegion) { 1987 if (!initialized) { 1988 throw new CoprocessorException("AccessController not yet initialized"); 1989 } 1990 User caller = RpcServer.getRequestUser().orElse(null); 1991 final String userName = request.hasUserName() ? request.getUserName().toStringUtf8() : null; 1992 final String namespace = 1993 request.hasNamespaceName() ? request.getNamespaceName().toStringUtf8() : null; 1994 final TableName table = 1995 request.hasTableName() ? ProtobufUtil.toTableName(request.getTableName()) : null; 1996 final byte[] cf = 1997 request.hasColumnFamily() ? request.getColumnFamily().toByteArray() : null; 1998 final byte[] cq = 1999 request.hasColumnQualifier() ? request.getColumnQualifier().toByteArray() : null; 2000 AccessControlProtos.Permission.Type protoType = 2001 request.hasType() ? request.getType() : null; 2002 Permission.Scope permissionScope = ShadedAccessControlUtil.toPermissionScope(protoType); 2003 preGetUserPermissions(caller, userName, namespace, table, cf, cq, permissionScope); 2004 GetUserPermissionsRequest getUserPermissionsRequest = null; 2005 if (request.getType() == AccessControlProtos.Permission.Type.Table) { 2006 getUserPermissionsRequest = GetUserPermissionsRequest.newBuilder(table).withFamily(cf) 2007 .withQualifier(cq).withUserName(userName).build(); 2008 } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) { 2009 getUserPermissionsRequest = 2010 GetUserPermissionsRequest.newBuilder(namespace).withUserName(userName).build(); 2011 } else { 2012 getUserPermissionsRequest = 2013 GetUserPermissionsRequest.newBuilder().withUserName(userName).build(); 2014 } 2015 List<UserPermission> perms = 2016 regionEnv.getConnection().getAdmin().getUserPermissions(getUserPermissionsRequest); 2017 response = AccessControlUtil.buildGetUserPermissionsResponse(perms); 2018 } else { 2019 throw new CoprocessorException(AccessController.class, 2020 "This method " + "can only execute at " + PermissionStorage.ACL_TABLE_NAME + " table."); 2021 } 2022 } catch (IOException ioe) { 2023 // pass exception back up 2024 CoprocessorRpcUtils.setControllerException(controller, ioe); 2025 } 2026 done.run(response); 2027 } 2028 2029 /** 2030 * @deprecated since 2.2.0 and will be removed 4.0.0. Use {@link Admin#hasUserPermissions(List)} 2031 * instead. 2032 * @see Admin#hasUserPermissions(List) 2033 * @see <a href="https://issues.apache.org/jira/browse/HBASE-22117">HBASE-22117</a> 2034 */ 2035 @Deprecated 2036 @Override 2037 public void checkPermissions(RpcController controller, 2038 AccessControlProtos.CheckPermissionsRequest request, 2039 RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) { 2040 AccessControlProtos.CheckPermissionsResponse response = null; 2041 try { 2042 User user = RpcServer.getRequestUser().orElse(null); 2043 TableName tableName = regionEnv.getRegion().getTableDescriptor().getTableName(); 2044 List<Permission> permissions = new ArrayList<>(); 2045 for (int i = 0; i < request.getPermissionCount(); i++) { 2046 Permission permission = AccessControlUtil.toPermission(request.getPermission(i)); 2047 permissions.add(permission); 2048 if (permission instanceof TablePermission) { 2049 TablePermission tperm = (TablePermission) permission; 2050 if (!tperm.getTableName().equals(tableName)) { 2051 throw new CoprocessorException(AccessController.class, 2052 String.format( 2053 "This method can only execute at the table specified in " 2054 + "TablePermission. Table of the region:%s , requested table:%s", 2055 tableName, tperm.getTableName())); 2056 } 2057 } 2058 } 2059 for (Permission permission : permissions) { 2060 boolean hasPermission = 2061 accessChecker.hasUserPermission(user, "checkPermissions", permission); 2062 if (!hasPermission) { 2063 throw new AccessDeniedException("Insufficient permissions " + permission.toString()); 2064 } 2065 } 2066 response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance(); 2067 } catch (IOException ioe) { 2068 CoprocessorRpcUtils.setControllerException(controller, ioe); 2069 } 2070 done.run(response); 2071 } 2072 2073 private Region getRegion(RegionCoprocessorEnvironment e) { 2074 return e.getRegion(); 2075 } 2076 2077 private TableName getTableName(RegionCoprocessorEnvironment e) { 2078 Region region = e.getRegion(); 2079 if (region != null) { 2080 return getTableName(region); 2081 } 2082 return null; 2083 } 2084 2085 private TableName getTableName(Region region) { 2086 RegionInfo regionInfo = region.getRegionInfo(); 2087 if (regionInfo != null) { 2088 return regionInfo.getTable(); 2089 } 2090 return null; 2091 } 2092 2093 @Override 2094 public void preClose(ObserverContext<? extends RegionCoprocessorEnvironment> c, 2095 boolean abortRequested) throws IOException { 2096 requirePermission(c, "preClose", Action.ADMIN); 2097 } 2098 2099 private void checkSystemOrSuperUser(User activeUser) throws IOException { 2100 // No need to check if we're not going to throw 2101 if (!authorizationEnabled) { 2102 return; 2103 } 2104 if (!Superusers.isSuperUser(activeUser)) { 2105 throw new AccessDeniedException( 2106 "User '" + (activeUser != null ? activeUser.getShortName() : "null") 2107 + "' is not system or super user."); 2108 } 2109 } 2110 2111 @Override 2112 public void preStopRegionServer(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2113 throws IOException { 2114 requirePermission(ctx, "preStopRegionServer", Action.ADMIN); 2115 } 2116 2117 private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family, byte[] qualifier) { 2118 if (family == null) { 2119 return null; 2120 } 2121 2122 Map<byte[], Collection<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 2123 familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null); 2124 return familyMap; 2125 } 2126 2127 @Override 2128 public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, 2129 List<TableName> tableNamesList, List<TableDescriptor> descriptors, String regex) 2130 throws IOException { 2131 // We are delegating the authorization check to postGetTableDescriptors as we don't have 2132 // any concrete set of table names when a regex is present or the full list is requested. 2133 if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) { 2134 // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the 2135 // request can be granted. 2136 try (Admin admin = ctx.getEnvironment().getConnection().getAdmin()) { 2137 for (TableName tableName : tableNamesList) { 2138 // Skip checks for a table that does not exist 2139 if (!admin.tableExists(tableName)) { 2140 continue; 2141 } 2142 requirePermission(ctx, "getTableDescriptors", tableName, null, null, Action.ADMIN, 2143 Action.CREATE); 2144 } 2145 } 2146 } 2147 } 2148 2149 @Override 2150 public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, 2151 List<TableName> tableNamesList, List<TableDescriptor> descriptors, String regex) 2152 throws IOException { 2153 // Skipping as checks in this case are already done by preGetTableDescriptors. 2154 if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) { 2155 return; 2156 } 2157 2158 // Retains only those which passes authorization checks, as the checks weren't done as part 2159 // of preGetTableDescriptors. 2160 Iterator<TableDescriptor> itr = descriptors.iterator(); 2161 while (itr.hasNext()) { 2162 TableDescriptor htd = itr.next(); 2163 try { 2164 requirePermission(ctx, "getTableDescriptors", htd.getTableName(), null, null, Action.ADMIN, 2165 Action.CREATE); 2166 } catch (AccessDeniedException e) { 2167 itr.remove(); 2168 } 2169 } 2170 } 2171 2172 @Override 2173 public void postGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx, 2174 List<TableDescriptor> descriptors, String regex) throws IOException { 2175 // Retains only those which passes authorization checks. 2176 Iterator<TableDescriptor> itr = descriptors.iterator(); 2177 while (itr.hasNext()) { 2178 TableDescriptor htd = itr.next(); 2179 try { 2180 requireAccess(ctx, "getTableNames", htd.getTableName(), Action.values()); 2181 } catch (AccessDeniedException e) { 2182 itr.remove(); 2183 } 2184 } 2185 } 2186 2187 @Override 2188 public void preMergeRegions(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2189 final RegionInfo[] regionsToMerge) throws IOException { 2190 requirePermission(ctx, "mergeRegions", regionsToMerge[0].getTable(), null, null, Action.ADMIN); 2191 } 2192 2193 @Override 2194 public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2195 throws IOException { 2196 requirePermission(ctx, "preRollLogWriterRequest", Permission.Action.ADMIN); 2197 } 2198 2199 @Override 2200 public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2201 throws IOException { 2202 } 2203 2204 @Override 2205 public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2206 final String userName, final GlobalQuotaSettings quotas) throws IOException { 2207 requirePermission(ctx, "setUserQuota", Action.ADMIN); 2208 } 2209 2210 @Override 2211 public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2212 final String userName, final TableName tableName, final GlobalQuotaSettings quotas) 2213 throws IOException { 2214 requirePermission(ctx, "setUserTableQuota", tableName, null, null, Action.ADMIN); 2215 } 2216 2217 @Override 2218 public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2219 final String userName, final String namespace, final GlobalQuotaSettings quotas) 2220 throws IOException { 2221 requirePermission(ctx, "setUserNamespaceQuota", Action.ADMIN); 2222 } 2223 2224 @Override 2225 public void preSetTableQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2226 final TableName tableName, final GlobalQuotaSettings quotas) throws IOException { 2227 requirePermission(ctx, "setTableQuota", tableName, null, null, Action.ADMIN); 2228 } 2229 2230 @Override 2231 public void preSetNamespaceQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2232 final String namespace, final GlobalQuotaSettings quotas) throws IOException { 2233 requirePermission(ctx, "setNamespaceQuota", Action.ADMIN); 2234 } 2235 2236 @Override 2237 public void preSetRegionServerQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, 2238 final String regionServer, GlobalQuotaSettings quotas) throws IOException { 2239 requirePermission(ctx, "setRegionServerQuota", Action.ADMIN); 2240 } 2241 2242 @Override 2243 public ReplicationEndpoint postCreateReplicationEndPoint( 2244 ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) { 2245 return endpoint; 2246 } 2247 2248 @Override 2249 public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2250 throws IOException { 2251 requirePermission(ctx, "replicateLogEntries", Action.WRITE); 2252 } 2253 2254 @Override 2255 public void preClearCompactionQueues(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2256 throws IOException { 2257 requirePermission(ctx, "preClearCompactionQueues", Permission.Action.ADMIN); 2258 } 2259 2260 @Override 2261 public void preAddReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2262 String peerId, ReplicationPeerConfig peerConfig) throws IOException { 2263 requirePermission(ctx, "addReplicationPeer", Action.ADMIN); 2264 } 2265 2266 @Override 2267 public void preRemoveReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2268 String peerId) throws IOException { 2269 requirePermission(ctx, "removeReplicationPeer", Action.ADMIN); 2270 } 2271 2272 @Override 2273 public void preEnableReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2274 String peerId) throws IOException { 2275 requirePermission(ctx, "enableReplicationPeer", Action.ADMIN); 2276 } 2277 2278 @Override 2279 public void preDisableReplicationPeer(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2280 String peerId) throws IOException { 2281 requirePermission(ctx, "disableReplicationPeer", Action.ADMIN); 2282 } 2283 2284 @Override 2285 public void preGetReplicationPeerConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2286 String peerId) throws IOException { 2287 requirePermission(ctx, "getReplicationPeerConfig", Action.ADMIN); 2288 } 2289 2290 @Override 2291 public void preUpdateReplicationPeerConfig( 2292 final ObserverContext<MasterCoprocessorEnvironment> ctx, String peerId, 2293 ReplicationPeerConfig peerConfig) throws IOException { 2294 requirePermission(ctx, "updateReplicationPeerConfig", Action.ADMIN); 2295 } 2296 2297 @Override 2298 public void preTransitReplicationPeerSyncReplicationState( 2299 final ObserverContext<MasterCoprocessorEnvironment> ctx, String peerId, 2300 SyncReplicationState clusterState) throws IOException { 2301 requirePermission(ctx, "transitSyncReplicationPeerState", Action.ADMIN); 2302 } 2303 2304 @Override 2305 public void preListReplicationPeers(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2306 String regex) throws IOException { 2307 requirePermission(ctx, "listReplicationPeers", Action.ADMIN); 2308 } 2309 2310 @Override 2311 public void preRequestLock(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace, 2312 TableName tableName, RegionInfo[] regionInfos, String description) throws IOException { 2313 // There are operations in the CREATE and ADMIN domain which may require lock, READ 2314 // or WRITE. So for any lock request, we check for these two perms irrespective of lock type. 2315 String reason = String.format("Description=%s", description); 2316 checkLockPermissions(ctx, namespace, tableName, regionInfos, reason); 2317 } 2318 2319 @Override 2320 public void preLockHeartbeat(ObserverContext<MasterCoprocessorEnvironment> ctx, 2321 TableName tableName, String description) throws IOException { 2322 checkLockPermissions(ctx, null, tableName, null, description); 2323 } 2324 2325 @Override 2326 public void preExecuteProcedures(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2327 throws IOException { 2328 checkSystemOrSuperUser(getActiveUser(ctx)); 2329 } 2330 2331 @Override 2332 public void preSwitchRpcThrottle(ObserverContext<MasterCoprocessorEnvironment> ctx, 2333 boolean enable) throws IOException { 2334 requirePermission(ctx, "switchRpcThrottle", Action.ADMIN); 2335 } 2336 2337 @Override 2338 public void preIsRpcThrottleEnabled(ObserverContext<MasterCoprocessorEnvironment> ctx) 2339 throws IOException { 2340 requirePermission(ctx, "isRpcThrottleEnabled", Action.ADMIN); 2341 } 2342 2343 @Override 2344 public void preSwitchExceedThrottleQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, 2345 boolean enable) throws IOException { 2346 requirePermission(ctx, "switchExceedThrottleQuota", Action.ADMIN); 2347 } 2348 2349 /** 2350 * Returns the active user to which authorization checks should be applied. If we are in the 2351 * context of an RPC call, the remote user is used, otherwise the currently logged in user is 2352 * used. 2353 */ 2354 private User getActiveUser(ObserverContext<?> ctx) throws IOException { 2355 // for non-rpc handling, fallback to system user 2356 Optional<User> optionalUser = ctx.getRpcCallContext().map(ObserverRpcCallContext::getUser); 2357 if (optionalUser.isPresent()) { 2358 return optionalUser.get(); 2359 } 2360 return userProvider.getCurrent(); 2361 } 2362 2363 /** 2364 * @deprecated since 2.2.0 and will be removed in 4.0.0. Use 2365 * {@link Admin#hasUserPermissions(String, List)} instead. 2366 * @see Admin#hasUserPermissions(String, List) 2367 * @see <a href="https://issues.apache.org/jira/browse/HBASE-22117">HBASE-22117</a> 2368 */ 2369 @Deprecated 2370 @Override 2371 public void hasPermission(RpcController controller, HasPermissionRequest request, 2372 RpcCallback<HasPermissionResponse> done) { 2373 // Converts proto to a TablePermission object. 2374 TablePermission tPerm = AccessControlUtil.toTablePermission(request.getTablePermission()); 2375 // Check input user name 2376 if (!request.hasUserName()) { 2377 throw new IllegalStateException("Input username cannot be empty"); 2378 } 2379 final String inputUserName = request.getUserName().toStringUtf8(); 2380 AccessControlProtos.HasPermissionResponse response = null; 2381 try { 2382 User caller = RpcServer.getRequestUser().orElse(null); 2383 List<Permission> permissions = Lists.newArrayList(tPerm); 2384 preHasUserPermissions(caller, inputUserName, permissions); 2385 boolean hasPermission = 2386 regionEnv.getConnection().getAdmin().hasUserPermissions(inputUserName, permissions).get(0); 2387 response = ResponseConverter.buildHasPermissionResponse(hasPermission); 2388 } catch (IOException ioe) { 2389 ResponseConverter.setControllerException(controller, ioe); 2390 } 2391 done.run(response); 2392 } 2393 2394 @Override 2395 public void preGrant(ObserverContext<MasterCoprocessorEnvironment> ctx, 2396 UserPermission userPermission, boolean mergeExistingPermissions) throws IOException { 2397 preGrantOrRevoke(getActiveUser(ctx), "grant", userPermission); 2398 } 2399 2400 @Override 2401 public void preRevoke(ObserverContext<MasterCoprocessorEnvironment> ctx, 2402 UserPermission userPermission) throws IOException { 2403 preGrantOrRevoke(getActiveUser(ctx), "revoke", userPermission); 2404 } 2405 2406 private void preGrantOrRevoke(User caller, String request, UserPermission userPermission) 2407 throws IOException { 2408 switch (userPermission.getPermission().scope) { 2409 case GLOBAL: 2410 accessChecker.requireGlobalPermission(caller, request, Action.ADMIN, ""); 2411 break; 2412 case NAMESPACE: 2413 NamespacePermission namespacePerm = (NamespacePermission) userPermission.getPermission(); 2414 accessChecker.requireNamespacePermission(caller, request, namespacePerm.getNamespace(), 2415 null, Action.ADMIN); 2416 break; 2417 case TABLE: 2418 TablePermission tablePerm = (TablePermission) userPermission.getPermission(); 2419 accessChecker.requirePermission(caller, request, tablePerm.getTableName(), 2420 tablePerm.getFamily(), tablePerm.getQualifier(), null, Action.ADMIN); 2421 break; 2422 default: 2423 } 2424 if (!Superusers.isSuperUser(caller)) { 2425 accessChecker.performOnSuperuser(request, caller, userPermission.getUser()); 2426 } 2427 } 2428 2429 @Override 2430 public void preGetUserPermissions(ObserverContext<MasterCoprocessorEnvironment> ctx, 2431 String userName, String namespace, TableName tableName, byte[] family, byte[] qualifier, 2432 Permission.Scope permissionScope) throws IOException { 2433 preGetUserPermissions(getActiveUser(ctx), userName, namespace, tableName, family, qualifier, 2434 permissionScope); 2435 } 2436 2437 private void preGetUserPermissions(User caller, String userName, String namespace, 2438 TableName tableName, byte[] family, byte[] qualifier, Permission.Scope permissionScope) 2439 throws IOException { 2440 if (permissionScope == Permission.Scope.TABLE) { 2441 accessChecker.requirePermission(caller, "getUserPermissions", tableName, family, qualifier, 2442 userName, Action.ADMIN); 2443 } else if (permissionScope == Permission.Scope.NAMESPACE) { 2444 accessChecker.requireNamespacePermission(caller, "getUserPermissions", namespace, userName, 2445 Action.ADMIN); 2446 } else if (permissionScope == Permission.Scope.GLOBAL) { 2447 accessChecker.requirePermission(caller, "getUserPermissions", userName, Action.ADMIN); 2448 } else { 2449 preGetUserPermissions(caller, userName, namespace, tableName, family, qualifier); 2450 } 2451 } 2452 2453 private void preGetUserPermissions(User caller, String userName, String namespace, 2454 TableName tableName, byte[] family, byte[] qualifier) throws IOException { 2455 if (tableName != null) { 2456 accessChecker.requirePermission(caller, "getUserPermissions", tableName, family, qualifier, 2457 userName, Action.ADMIN); 2458 } else if (namespace != null) { 2459 accessChecker.requireNamespacePermission(caller, "getUserPermissions", namespace, userName, 2460 Action.ADMIN); 2461 } else { 2462 accessChecker.requirePermission(caller, "getUserPermissions", userName, Action.ADMIN); 2463 } 2464 } 2465 2466 @Override 2467 public void preHasUserPermissions(ObserverContext<MasterCoprocessorEnvironment> ctx, 2468 String userName, List<Permission> permissions) throws IOException { 2469 preHasUserPermissions(getActiveUser(ctx), userName, permissions); 2470 } 2471 2472 private void preHasUserPermissions(User caller, String userName, List<Permission> permissions) 2473 throws IOException { 2474 String request = "hasUserPermissions"; 2475 for (Permission permission : permissions) { 2476 if (!caller.getShortName().equals(userName)) { 2477 // User should have admin privilege if checking permission for other users 2478 if (permission instanceof TablePermission) { 2479 TablePermission tPerm = (TablePermission) permission; 2480 accessChecker.requirePermission(caller, request, tPerm.getTableName(), tPerm.getFamily(), 2481 tPerm.getQualifier(), userName, Action.ADMIN); 2482 } else if (permission instanceof NamespacePermission) { 2483 NamespacePermission nsPerm = (NamespacePermission) permission; 2484 accessChecker.requireNamespacePermission(caller, request, nsPerm.getNamespace(), userName, 2485 Action.ADMIN); 2486 } else { 2487 accessChecker.requirePermission(caller, request, userName, Action.ADMIN); 2488 } 2489 } else { 2490 // User don't need ADMIN privilege for self check. 2491 // Setting action as null in AuthResult to display empty action in audit log 2492 AuthResult result; 2493 if (permission instanceof TablePermission) { 2494 TablePermission tPerm = (TablePermission) permission; 2495 result = AuthResult.allow(request, "Self user validation allowed", caller, null, 2496 tPerm.getTableName(), tPerm.getFamily(), tPerm.getQualifier()); 2497 } else if (permission instanceof NamespacePermission) { 2498 NamespacePermission nsPerm = (NamespacePermission) permission; 2499 result = AuthResult.allow(request, "Self user validation allowed", caller, null, 2500 nsPerm.getNamespace()); 2501 } else { 2502 result = AuthResult.allow(request, "Self user validation allowed", caller, null, null, 2503 null, null); 2504 } 2505 AccessChecker.logResult(result); 2506 } 2507 } 2508 } 2509 2510 @Override 2511 public void preMoveServersAndTables(ObserverContext<MasterCoprocessorEnvironment> ctx, 2512 Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException { 2513 accessChecker.requirePermission(getActiveUser(ctx), "moveServersAndTables", null, 2514 Permission.Action.ADMIN); 2515 } 2516 2517 @Override 2518 public void preMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2519 Set<Address> servers, String targetGroup) throws IOException { 2520 accessChecker.requirePermission(getActiveUser(ctx), "moveServers", null, 2521 Permission.Action.ADMIN); 2522 } 2523 2524 @Override 2525 public void preMoveTables(ObserverContext<MasterCoprocessorEnvironment> ctx, 2526 Set<TableName> tables, String targetGroup) throws IOException { 2527 accessChecker.requirePermission(getActiveUser(ctx), "moveTables", null, 2528 Permission.Action.ADMIN); 2529 } 2530 2531 @Override 2532 public void preAddRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String name) 2533 throws IOException { 2534 accessChecker.requirePermission(getActiveUser(ctx), "addRSGroup", null, 2535 Permission.Action.ADMIN); 2536 } 2537 2538 @Override 2539 public void preRemoveRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String name) 2540 throws IOException { 2541 accessChecker.requirePermission(getActiveUser(ctx), "removeRSGroup", null, 2542 Permission.Action.ADMIN); 2543 } 2544 2545 @Override 2546 public void preBalanceRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName, 2547 BalanceRequest request) throws IOException { 2548 accessChecker.requirePermission(getActiveUser(ctx), "balanceRSGroup", null, 2549 Permission.Action.ADMIN); 2550 } 2551 2552 @Override 2553 public void preRemoveServers(ObserverContext<MasterCoprocessorEnvironment> ctx, 2554 Set<Address> servers) throws IOException { 2555 accessChecker.requirePermission(getActiveUser(ctx), "removeServers", null, 2556 Permission.Action.ADMIN); 2557 } 2558 2559 @Override 2560 public void preGetRSGroupInfo(ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) 2561 throws IOException { 2562 accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfo", null, 2563 Permission.Action.ADMIN); 2564 } 2565 2566 @Override 2567 public void preGetRSGroupInfoOfTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 2568 TableName tableName) throws IOException { 2569 accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfTable", null, 2570 Permission.Action.ADMIN); 2571 // todo: should add check for table existence 2572 } 2573 2574 @Override 2575 public void preListRSGroups(ObserverContext<MasterCoprocessorEnvironment> ctx) 2576 throws IOException { 2577 accessChecker.requirePermission(getActiveUser(ctx), "listRSGroups", null, 2578 Permission.Action.ADMIN); 2579 } 2580 2581 @Override 2582 public void preListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, 2583 String groupName) throws IOException { 2584 accessChecker.requirePermission(getActiveUser(ctx), "listTablesInRSGroup", null, 2585 Permission.Action.ADMIN); 2586 } 2587 2588 @Override 2589 public void preGetConfiguredNamespacesAndTablesInRSGroup( 2590 ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException { 2591 accessChecker.requirePermission(getActiveUser(ctx), "getConfiguredNamespacesAndTablesInRSGroup", 2592 null, Permission.Action.ADMIN); 2593 } 2594 2595 @Override 2596 public void preGetRSGroupInfoOfServer(ObserverContext<MasterCoprocessorEnvironment> ctx, 2597 Address server) throws IOException { 2598 accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfServer", null, 2599 Permission.Action.ADMIN); 2600 } 2601 2602 @Override 2603 public void preRenameRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String oldName, 2604 String newName) throws IOException { 2605 accessChecker.requirePermission(getActiveUser(ctx), "renameRSGroup", null, 2606 Permission.Action.ADMIN); 2607 } 2608 2609 @Override 2610 public void preUpdateRSGroupConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx, 2611 final String groupName, final Map<String, String> configuration) throws IOException { 2612 accessChecker.requirePermission(getActiveUser(ctx), "updateRSGroupConfig", null, 2613 Permission.Action.ADMIN); 2614 } 2615 2616 @Override 2617 public void preClearRegionBlockCache(ObserverContext<RegionServerCoprocessorEnvironment> ctx) 2618 throws IOException { 2619 accessChecker.requirePermission(getActiveUser(ctx), "clearRegionBlockCache", null, 2620 Permission.Action.ADMIN); 2621 } 2622 2623 @Override 2624 public void preUpdateRegionServerConfiguration( 2625 ObserverContext<RegionServerCoprocessorEnvironment> ctx, Configuration preReloadConf) 2626 throws IOException { 2627 accessChecker.requirePermission(getActiveUser(ctx), "updateConfiguration", null, 2628 Permission.Action.ADMIN); 2629 } 2630 2631 @Override 2632 public void preUpdateMasterConfiguration(ObserverContext<MasterCoprocessorEnvironment> ctx, 2633 Configuration preReloadConf) throws IOException { 2634 accessChecker.requirePermission(getActiveUser(ctx), "updateConfiguration", null, 2635 Permission.Action.ADMIN); 2636 } 2637 2638}