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 */ 018 019package org.apache.hadoop.hbase.security.access; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Optional; 027import java.util.Set; 028import java.util.stream.Collectors; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.Path; 031import org.apache.hadoop.hbase.Cell; 032import org.apache.hadoop.hbase.CellUtil; 033import org.apache.hadoop.hbase.HBaseInterfaceAudience; 034import org.apache.hadoop.hbase.NamespaceDescriptor; 035import org.apache.hadoop.hbase.TableName; 036import org.apache.hadoop.hbase.TableNotFoundException; 037import org.apache.hadoop.hbase.client.Admin; 038import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 039import org.apache.hadoop.hbase.client.Connection; 040import org.apache.hadoop.hbase.client.Delete; 041import org.apache.hadoop.hbase.client.Get; 042import org.apache.hadoop.hbase.client.Put; 043import org.apache.hadoop.hbase.client.RegionInfo; 044import org.apache.hadoop.hbase.client.Result; 045import org.apache.hadoop.hbase.client.ResultScanner; 046import org.apache.hadoop.hbase.client.Scan; 047import org.apache.hadoop.hbase.client.SnapshotDescription; 048import org.apache.hadoop.hbase.client.Table; 049import org.apache.hadoop.hbase.client.TableDescriptor; 050import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 051import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor; 052import org.apache.hadoop.hbase.coprocessor.HasMasterServices; 053import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 054import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 055import org.apache.hadoop.hbase.coprocessor.MasterObserver; 056import org.apache.hadoop.hbase.coprocessor.ObserverContext; 057import org.apache.hadoop.hbase.master.MasterServices; 058import org.apache.hadoop.hbase.security.User; 059import org.apache.hadoop.hbase.security.UserProvider; 060import org.apache.hadoop.hbase.security.access.SnapshotScannerHDFSAclHelper.PathHelper; 061import org.apache.hadoop.hbase.util.Bytes; 062import org.apache.hadoop.hbase.util.Pair; 063import org.apache.yetus.audience.InterfaceAudience; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067import org.apache.hbase.thirdparty.com.google.common.collect.Sets; 068 069/** 070 * Set HDFS ACLs to hFiles to make HBase granted users have permission to scan snapshot 071 * <p> 072 * To use this feature, please mask sure HDFS config: 073 * <ul> 074 * <li>dfs.namenode.acls.enabled = true</li> 075 * <li>fs.permissions.umask-mode = 027 (or smaller umask than 027)</li> 076 * </ul> 077 * </p> 078 * <p> 079 * The implementation of this feature is as followings: 080 * <ul> 081 * <li>For common directories such as 'data' and 'archive', set other permission to '--x' to make 082 * everyone have the permission to access the directory.</li> 083 * <li>For namespace or table directories such as 'data/ns/table', 'archive/ns/table' and 084 * '.hbase-snapshot/snapshotName', set user 'r-x' access acl and 'r-x' default acl when following 085 * operations happen: 086 * <ul> 087 * <li>grant user with global, namespace or table permission;</li> 088 * <li>revoke user from global, namespace or table;</li> 089 * <li>snapshot table;</li> 090 * <li>truncate table;</li> 091 * </ul> 092 * </li> 093 * <li>Note: Because snapshots are at table level, so this feature just considers users with global, 094 * namespace or table permissions, ignores users with table CF or cell permissions.</li> 095 * </ul> 096 * </p> 097 */ 098@CoreCoprocessor 099@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 100public class SnapshotScannerHDFSAclController implements MasterCoprocessor, MasterObserver { 101 private static final Logger LOG = LoggerFactory.getLogger(SnapshotScannerHDFSAclController.class); 102 103 private SnapshotScannerHDFSAclHelper hdfsAclHelper = null; 104 private PathHelper pathHelper = null; 105 private MasterServices masterServices = null; 106 private volatile boolean initialized = false; 107 private volatile boolean aclTableInitialized = false; 108 /** Provider for mapping principal names to Users */ 109 private UserProvider userProvider; 110 111 @Override 112 public Optional<MasterObserver> getMasterObserver() { 113 return Optional.of(this); 114 } 115 116 @Override 117 public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> c) 118 throws IOException { 119 if (c.getEnvironment().getConfiguration() 120 .getBoolean(SnapshotScannerHDFSAclHelper.ACL_SYNC_TO_HDFS_ENABLE, false)) { 121 MasterCoprocessorEnvironment mEnv = c.getEnvironment(); 122 if (!(mEnv instanceof HasMasterServices)) { 123 throw new IOException("Does not implement HMasterServices"); 124 } 125 masterServices = ((HasMasterServices) mEnv).getMasterServices(); 126 hdfsAclHelper = new SnapshotScannerHDFSAclHelper(masterServices.getConfiguration(), 127 masterServices.getConnection()); 128 pathHelper = hdfsAclHelper.getPathHelper(); 129 hdfsAclHelper.setCommonDirectoryPermission(); 130 initialized = true; 131 userProvider = UserProvider.instantiate(c.getEnvironment().getConfiguration()); 132 } else { 133 LOG.warn("Try to initialize the coprocessor SnapshotScannerHDFSAclController but failure " 134 + "because the config " + SnapshotScannerHDFSAclHelper.ACL_SYNC_TO_HDFS_ENABLE 135 + " is false."); 136 } 137 } 138 139 @Override 140 public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException { 141 if (!initialized) { 142 return; 143 } 144 try (Admin admin = c.getEnvironment().getConnection().getAdmin()) { 145 if (admin.tableExists(PermissionStorage.ACL_TABLE_NAME)) { 146 // Check if acl table has 'm' CF, if not, add 'm' CF 147 TableDescriptor tableDescriptor = admin.getDescriptor(PermissionStorage.ACL_TABLE_NAME); 148 boolean containHdfsAclFamily = Arrays.stream(tableDescriptor.getColumnFamilies()).anyMatch( 149 family -> Bytes.equals(family.getName(), SnapshotScannerHDFSAclStorage.HDFS_ACL_FAMILY)); 150 if (!containHdfsAclFamily) { 151 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableDescriptor) 152 .setColumnFamily(ColumnFamilyDescriptorBuilder 153 .newBuilder(SnapshotScannerHDFSAclStorage.HDFS_ACL_FAMILY).build()); 154 admin.modifyTable(builder.build()); 155 } 156 aclTableInitialized = true; 157 } else { 158 throw new TableNotFoundException("Table " + PermissionStorage.ACL_TABLE_NAME 159 + " is not created yet. Please check if " + getClass().getName() 160 + " is configured after " + AccessController.class.getName()); 161 } 162 } 163 } 164 165 @Override 166 public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c) { 167 if (initialized) { 168 hdfsAclHelper.close(); 169 } 170 } 171 172 @Override 173 public void postCompletedCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> c, 174 TableDescriptor desc, RegionInfo[] regions) throws IOException { 175 if (needHandleTableHdfsAcl(desc, "createTable " + desc.getTableName())) { 176 TableName tableName = desc.getTableName(); 177 // 1. Create table directories to make HDFS acls can be inherited 178 hdfsAclHelper.createTableDirectories(tableName); 179 // 2. Add table owner HDFS acls 180 String owner = 181 desc.getOwnerString() == null ? getActiveUser(c).getShortName() : desc.getOwnerString(); 182 hdfsAclHelper.addTableAcl(tableName, Sets.newHashSet(owner), "create"); 183 // 3. Record table owner permission is synced to HDFS in acl table 184 SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(c.getEnvironment().getConnection(), owner, 185 tableName); 186 } 187 } 188 189 @Override 190 public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> c, 191 NamespaceDescriptor ns) throws IOException { 192 if (checkInitialized("createNamespace " + ns.getName())) { 193 // Create namespace directories to make HDFS acls can be inherited 194 List<Path> paths = hdfsAclHelper.getNamespaceRootPaths(ns.getName()); 195 for (Path path : paths) { 196 hdfsAclHelper.createDirIfNotExist(path); 197 } 198 } 199 } 200 201 @Override 202 public void postCompletedSnapshotAction(ObserverContext<MasterCoprocessorEnvironment> c, 203 SnapshotDescription snapshot, TableDescriptor tableDescriptor) throws IOException { 204 if (needHandleTableHdfsAcl(tableDescriptor, "snapshot " + snapshot.getName())) { 205 // Add HDFS acls of users with table read permission to snapshot files 206 hdfsAclHelper.snapshotAcl(snapshot); 207 } 208 } 209 210 @Override 211 public void postCompletedTruncateTableAction(ObserverContext<MasterCoprocessorEnvironment> c, 212 TableName tableName) throws IOException { 213 if (needHandleTableHdfsAcl(tableName, "truncateTable " + tableName)) { 214 // 1. create tmp table directories 215 hdfsAclHelper.createTableDirectories(tableName); 216 // 2. Since the table directories is recreated, so add HDFS acls again 217 Set<String> users = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false); 218 hdfsAclHelper.addTableAcl(tableName, users, "truncate"); 219 } 220 } 221 222 @Override 223 public void postCompletedDeleteTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx, 224 TableName tableName) throws IOException { 225 if (!tableName.isSystemTable() && checkInitialized("deleteTable " + tableName)) { 226 /* 227 * Remove table user access HDFS acl from namespace directory if the user has no permissions 228 * of global, ns of the table or other tables of the ns, eg: Bob has 'ns1:t1' read permission, 229 * when delete 'ns1:t1', if Bob has global read permission, '@ns1' read permission or 230 * 'ns1:other_tables' read permission, then skip remove Bob access acl in ns1Dirs, otherwise, 231 * remove Bob access acl. 232 */ 233 try (Table aclTable = 234 ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 235 Set<String> users = SnapshotScannerHDFSAclStorage.getTableUsers(aclTable, tableName); 236 if (users.size() > 0) { 237 // 1. Remove table archive directory default ACLs 238 hdfsAclHelper.removeTableDefaultAcl(tableName, users); 239 // 2. Delete table owner permission is synced to HDFS in acl table 240 SnapshotScannerHDFSAclStorage.deleteTableHdfsAcl(aclTable, tableName); 241 // 3. Remove namespace access acls 242 Set<String> removeUsers = filterUsersToRemoveNsAccessAcl(aclTable, tableName, users); 243 if (removeUsers.size() > 0) { 244 hdfsAclHelper.removeNamespaceAccessAcl(tableName, removeUsers, "delete"); 245 } 246 } 247 } 248 } 249 } 250 251 @Override 252 public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 253 TableName tableName, TableDescriptor oldDescriptor, TableDescriptor currentDescriptor) 254 throws IOException { 255 try (Table aclTable = 256 ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 257 if (needHandleTableHdfsAcl(currentDescriptor, "modifyTable " + tableName) 258 && !hdfsAclHelper.isAclSyncToHdfsEnabled(oldDescriptor)) { 259 // 1. Create table directories used for acl inherited 260 hdfsAclHelper.createTableDirectories(tableName); 261 // 2. Add table users HDFS acls 262 Set<String> tableUsers = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false); 263 Set<String> users = 264 hdfsAclHelper.getUsersWithNamespaceReadAction(tableName.getNamespaceAsString(), true); 265 users.addAll(tableUsers); 266 hdfsAclHelper.addTableAcl(tableName, users, "modify"); 267 // 3. Record table user acls are synced to HDFS in acl table 268 SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(ctx.getEnvironment().getConnection(), 269 tableUsers, tableName); 270 } else if (needHandleTableHdfsAcl(oldDescriptor, "modifyTable " + tableName) 271 && !hdfsAclHelper.isAclSyncToHdfsEnabled(currentDescriptor)) { 272 // 1. Remove empty table directories 273 List<Path> tableRootPaths = hdfsAclHelper.getTableRootPaths(tableName, false); 274 for (Path path : tableRootPaths) { 275 hdfsAclHelper.deleteEmptyDir(path); 276 } 277 // 2. Remove all table HDFS acls 278 Set<String> tableUsers = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false); 279 Set<String> users = hdfsAclHelper 280 .getUsersWithNamespaceReadAction(tableName.getNamespaceAsString(), true); 281 users.addAll(tableUsers); 282 hdfsAclHelper.removeTableAcl(tableName, users); 283 // 3. Remove namespace access HDFS acls for users who only own permission for this table 284 hdfsAclHelper.removeNamespaceAccessAcl(tableName, 285 filterUsersToRemoveNsAccessAcl(aclTable, tableName, tableUsers), "modify"); 286 // 4. Record table user acl is not synced to HDFS 287 SnapshotScannerHDFSAclStorage.deleteUserTableHdfsAcl(ctx.getEnvironment().getConnection(), 288 tableUsers, tableName); 289 } 290 } 291 } 292 293 @Override 294 public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, 295 String namespace) throws IOException { 296 if (checkInitialized("deleteNamespace " + namespace)) { 297 try (Table aclTable = 298 ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 299 // 1. Delete namespace archive dir default ACLs 300 Set<String> users = SnapshotScannerHDFSAclStorage.getEntryUsers(aclTable, 301 PermissionStorage.toNamespaceEntry(Bytes.toBytes(namespace))); 302 hdfsAclHelper.removeNamespaceDefaultAcl(namespace, users); 303 // 2. Record namespace user acl is not synced to HDFS 304 SnapshotScannerHDFSAclStorage.deleteNamespaceHdfsAcl(ctx.getEnvironment().getConnection(), 305 namespace); 306 // 3. Delete tmp namespace directory 307 /** 308 * Delete namespace tmp directory because it's created by this coprocessor when namespace is 309 * created to make namespace default acl can be inherited by tables. The namespace data 310 * directory is deleted by DeleteNamespaceProcedure, the namespace archive directory is 311 * deleted by HFileCleaner. 312 */ 313 hdfsAclHelper.deleteEmptyDir(pathHelper.getTmpNsDir(namespace)); 314 } 315 } 316 } 317 318 @Override 319 public void postGrant(ObserverContext<MasterCoprocessorEnvironment> c, 320 UserPermission userPermission, boolean mergeExistingPermissions) throws IOException { 321 if (!checkInitialized( 322 "grant " + userPermission + ", merge existing permissions " + mergeExistingPermissions)) { 323 return; 324 } 325 try (Table aclTable = 326 c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 327 Configuration conf = c.getEnvironment().getConfiguration(); 328 String userName = userPermission.getUser(); 329 switch (userPermission.getAccessScope()) { 330 case GLOBAL: 331 UserPermission perm = getUserGlobalPermission(conf, userName); 332 if (perm != null && hdfsAclHelper.containReadAction(perm)) { 333 if (!isHdfsAclSet(aclTable, userName)) { 334 // 1. Get namespaces and tables which global user acls are already synced 335 Pair<Set<String>, Set<TableName>> skipNamespaceAndTables = 336 SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName); 337 Set<String> skipNamespaces = skipNamespaceAndTables.getFirst(); 338 Set<TableName> skipTables = skipNamespaceAndTables.getSecond().stream() 339 .filter(t -> !skipNamespaces.contains(t.getNamespaceAsString())) 340 .collect(Collectors.toSet()); 341 // 2. Add HDFS acl(skip namespaces and tables directories whose acl is set) 342 hdfsAclHelper.grantAcl(userPermission, skipNamespaces, skipTables); 343 // 3. Record global acl is sync to HDFS 344 SnapshotScannerHDFSAclStorage.addUserGlobalHdfsAcl(aclTable, userName); 345 } 346 } else { 347 // The merged user permission doesn't contain READ, so remove user global HDFS acls if 348 // it's set 349 removeUserGlobalHdfsAcl(aclTable, userName, userPermission); 350 } 351 break; 352 case NAMESPACE: 353 String namespace = ((NamespacePermission) userPermission.getPermission()).getNamespace(); 354 UserPermission nsPerm = getUserNamespacePermission(conf, userName, namespace); 355 if (nsPerm != null && hdfsAclHelper.containReadAction(nsPerm)) { 356 if (!isHdfsAclSet(aclTable, userName, namespace)) { 357 // 1. Get tables which namespace user acls are already synced 358 Set<TableName> skipTables = SnapshotScannerHDFSAclStorage 359 .getUserNamespaceAndTable(aclTable, userName).getSecond(); 360 // 2. Add HDFS acl(skip tables directories whose acl is set) 361 hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), skipTables); 362 } 363 // 3. Record namespace acl is synced to HDFS 364 SnapshotScannerHDFSAclStorage.addUserNamespaceHdfsAcl(aclTable, userName, namespace); 365 } else { 366 // The merged user permission doesn't contain READ, so remove user namespace HDFS acls 367 // if it's set 368 removeUserNamespaceHdfsAcl(aclTable, userName, namespace, userPermission); 369 } 370 break; 371 case TABLE: 372 TablePermission tablePerm = (TablePermission) userPermission.getPermission(); 373 if (needHandleTableHdfsAcl(tablePerm)) { 374 TableName tableName = tablePerm.getTableName(); 375 UserPermission tPerm = getUserTablePermission(conf, userName, tableName); 376 if (tPerm != null && hdfsAclHelper.containReadAction(tPerm)) { 377 if (!isHdfsAclSet(aclTable, userName, tableName)) { 378 // 1. create table dirs 379 hdfsAclHelper.createTableDirectories(tableName); 380 // 2. Add HDFS acl 381 hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), new HashSet<>(0)); 382 } 383 // 2. Record table acl is synced to HDFS 384 SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, userName, tableName); 385 } else { 386 // The merged user permission doesn't contain READ, so remove user table HDFS acls if 387 // it's set 388 removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission); 389 } 390 } 391 break; 392 default: 393 throw new IllegalArgumentException( 394 "Illegal user permission scope " + userPermission.getAccessScope()); 395 } 396 } 397 } 398 399 @Override 400 public void postRevoke(ObserverContext<MasterCoprocessorEnvironment> c, 401 UserPermission userPermission) throws IOException { 402 if (checkInitialized("revoke " + userPermission)) { 403 try (Table aclTable = 404 c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { 405 String userName = userPermission.getUser(); 406 Configuration conf = c.getEnvironment().getConfiguration(); 407 switch (userPermission.getAccessScope()) { 408 case GLOBAL: 409 UserPermission userGlobalPerm = getUserGlobalPermission(conf, userName); 410 if (userGlobalPerm == null || !hdfsAclHelper.containReadAction(userGlobalPerm)) { 411 removeUserGlobalHdfsAcl(aclTable, userName, userPermission); 412 } 413 break; 414 case NAMESPACE: 415 NamespacePermission nsPerm = (NamespacePermission) userPermission.getPermission(); 416 UserPermission userNsPerm = 417 getUserNamespacePermission(conf, userName, nsPerm.getNamespace()); 418 if (userNsPerm == null || !hdfsAclHelper.containReadAction(userNsPerm)) { 419 removeUserNamespaceHdfsAcl(aclTable, userName, nsPerm.getNamespace(), userPermission); 420 } 421 break; 422 case TABLE: 423 TablePermission tPerm = (TablePermission) userPermission.getPermission(); 424 if (needHandleTableHdfsAcl(tPerm)) { 425 TableName tableName = tPerm.getTableName(); 426 UserPermission userTablePerm = getUserTablePermission(conf, userName, tableName); 427 if (userTablePerm == null || !hdfsAclHelper.containReadAction(userTablePerm)) { 428 removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission); 429 } 430 } 431 break; 432 default: 433 throw new IllegalArgumentException( 434 "Illegal user permission scope " + userPermission.getAccessScope()); 435 } 436 } 437 } 438 } 439 440 private void removeUserGlobalHdfsAcl(Table aclTable, String userName, 441 UserPermission userPermission) throws IOException { 442 if (SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName)) { 443 // 1. Get namespaces and tables which global user acls are already synced 444 Pair<Set<String>, Set<TableName>> namespaceAndTable = 445 SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName); 446 Set<String> skipNamespaces = namespaceAndTable.getFirst(); 447 Set<TableName> skipTables = namespaceAndTable.getSecond().stream() 448 .filter(t -> !skipNamespaces.contains(t.getNamespaceAsString())) 449 .collect(Collectors.toSet()); 450 // 2. Remove user HDFS acls(skip namespaces and tables directories 451 // whose acl must be reversed) 452 hdfsAclHelper.revokeAcl(userPermission, skipNamespaces, skipTables); 453 // 3. Remove global user acl is synced to HDFS in acl table 454 SnapshotScannerHDFSAclStorage.deleteUserGlobalHdfsAcl(aclTable, userName); 455 } 456 } 457 458 private void removeUserNamespaceHdfsAcl(Table aclTable, String userName, String namespace, 459 UserPermission userPermission) throws IOException { 460 if (SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, namespace)) { 461 if (!SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName)) { 462 // 1. Get tables whose namespace user acls are already synced 463 Set<TableName> skipTables = 464 SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName).getSecond(); 465 // 2. Remove user HDFS acls(skip tables directories whose acl must be reversed) 466 hdfsAclHelper.revokeAcl(userPermission, new HashSet<>(), skipTables); 467 } 468 // 3. Remove namespace user acl is synced to HDFS in acl table 469 SnapshotScannerHDFSAclStorage.deleteUserNamespaceHdfsAcl(aclTable, userName, namespace); 470 } 471 } 472 473 private void removeUserTableHdfsAcl(Table aclTable, String userName, TableName tableName, 474 UserPermission userPermission) throws IOException { 475 if (SnapshotScannerHDFSAclStorage.hasUserTableHdfsAcl(aclTable, userName, tableName)) { 476 if (!SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName) 477 && !SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, 478 tableName.getNamespaceAsString())) { 479 // 1. Remove table acls 480 hdfsAclHelper.revokeAcl(userPermission, new HashSet<>(0), new HashSet<>(0)); 481 } 482 // 2. Remove table user acl is synced to HDFS in acl table 483 SnapshotScannerHDFSAclStorage.deleteUserTableHdfsAcl(aclTable, userName, tableName); 484 } 485 } 486 487 private UserPermission getUserGlobalPermission(Configuration conf, String userName) 488 throws IOException { 489 List<UserPermission> permissions = PermissionStorage.getUserPermissions(conf, 490 PermissionStorage.ACL_GLOBAL_NAME, null, null, userName, true); 491 return permissions.size() > 0 ? permissions.get(0) : null; 492 } 493 494 private UserPermission getUserNamespacePermission(Configuration conf, String userName, 495 String namespace) throws IOException { 496 List<UserPermission> permissions = 497 PermissionStorage.getUserNamespacePermissions(conf, namespace, userName, true); 498 return permissions.size() > 0 ? permissions.get(0) : null; 499 } 500 501 private UserPermission getUserTablePermission(Configuration conf, String userName, 502 TableName tableName) throws IOException { 503 List<UserPermission> permissions = PermissionStorage 504 .getUserTablePermissions(conf, tableName, null, null, userName, true).stream() 505 .filter(userPermission -> hdfsAclHelper 506 .isNotFamilyOrQualifierPermission((TablePermission) userPermission.getPermission())) 507 .collect(Collectors.toList()); 508 return permissions.size() > 0 ? permissions.get(0) : null; 509 } 510 511 private boolean isHdfsAclSet(Table aclTable, String userName) throws IOException { 512 return isHdfsAclSet(aclTable, userName, null, null); 513 } 514 515 private boolean isHdfsAclSet(Table aclTable, String userName, String namespace) 516 throws IOException { 517 return isHdfsAclSet(aclTable, userName, namespace, null); 518 } 519 520 private boolean isHdfsAclSet(Table aclTable, String userName, TableName tableName) 521 throws IOException { 522 return isHdfsAclSet(aclTable, userName, null, tableName); 523 } 524 525 /** 526 * Check if user global/namespace/table HDFS acls is already set 527 */ 528 private boolean isHdfsAclSet(Table aclTable, String userName, String namespace, 529 TableName tableName) throws IOException { 530 boolean isSet = SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName); 531 if (namespace != null) { 532 isSet = isSet 533 || SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, namespace); 534 } 535 if (tableName != null) { 536 isSet = isSet 537 || SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, 538 tableName.getNamespaceAsString()) 539 || SnapshotScannerHDFSAclStorage.hasUserTableHdfsAcl(aclTable, userName, tableName); 540 } 541 return isSet; 542 } 543 544 @InterfaceAudience.Private 545 boolean checkInitialized(String operation) { 546 if (initialized) { 547 if (aclTableInitialized) { 548 return true; 549 } else { 550 LOG.warn("Skip set HDFS acls because acl table is not initialized when " + operation); 551 } 552 } 553 return false; 554 } 555 556 private boolean needHandleTableHdfsAcl(TablePermission tablePermission) throws IOException { 557 return needHandleTableHdfsAcl(tablePermission.getTableName(), "") 558 && hdfsAclHelper.isNotFamilyOrQualifierPermission(tablePermission); 559 } 560 561 private boolean needHandleTableHdfsAcl(TableName tableName, String operation) throws IOException { 562 return !tableName.isSystemTable() && checkInitialized(operation) && hdfsAclHelper 563 .isAclSyncToHdfsEnabled(masterServices.getTableDescriptors().get(tableName)); 564 } 565 566 private boolean needHandleTableHdfsAcl(TableDescriptor tableDescriptor, String operation) { 567 TableName tableName = tableDescriptor.getTableName(); 568 return !tableName.isSystemTable() && checkInitialized(operation) 569 && hdfsAclHelper.isAclSyncToHdfsEnabled(tableDescriptor); 570 } 571 572 private User getActiveUser(ObserverContext<?> ctx) throws IOException { 573 // for non-rpc handling, fallback to system user 574 Optional<User> optionalUser = ctx.getCaller(); 575 if (optionalUser.isPresent()) { 576 return optionalUser.get(); 577 } 578 return userProvider.getCurrent(); 579 } 580 581 /** 582 * Remove table user access HDFS acl from namespace directory if the user has no permissions of 583 * global, ns of the table or other tables of the ns, eg: Bob has 'ns1:t1' read permission, when 584 * delete 'ns1:t1', if Bob has global read permission, '@ns1' read permission or 585 * 'ns1:other_tables' read permission, then skip remove Bob access acl in ns1Dirs, otherwise, 586 * remove Bob access acl. 587 * @param aclTable acl table 588 * @param tableName the name of the table 589 * @param tablesUsers table users set 590 * @return users whose access acl will be removed from the namespace of the table 591 * @throws IOException if an error occurred 592 */ 593 private Set<String> filterUsersToRemoveNsAccessAcl(Table aclTable, TableName tableName, 594 Set<String> tablesUsers) throws IOException { 595 Set<String> removeUsers = new HashSet<>(); 596 byte[] namespace = tableName.getNamespace(); 597 for (String user : tablesUsers) { 598 List<byte[]> userEntries = SnapshotScannerHDFSAclStorage.getUserEntries(aclTable, user); 599 boolean remove = true; 600 for (byte[] entry : userEntries) { 601 if (PermissionStorage.isGlobalEntry(entry) 602 || (PermissionStorage.isNamespaceEntry(entry) 603 && Bytes.equals(PermissionStorage.fromNamespaceEntry(entry), namespace)) 604 || (!Bytes.equals(tableName.getName(), entry) 605 && Bytes.equals(TableName.valueOf(entry).getNamespace(), namespace))) { 606 remove = false; 607 break; 608 } 609 } 610 if (remove) { 611 removeUsers.add(user); 612 } 613 } 614 return removeUsers; 615 } 616 617 static final class SnapshotScannerHDFSAclStorage { 618 /** 619 * Add a new CF in HBase acl table to record if the HBase read permission is synchronized to 620 * related hfiles. The record has two usages: 1. check if we need to remove HDFS acls for a 621 * grant without READ permission(eg: grant user table read permission and then grant user table 622 * write permission without merging the existing permissions, in this case, need to remove HDFS 623 * acls); 2. skip some HDFS acl sync because it may be already set(eg: grant user table read 624 * permission and then grant user ns read permission; grant user table read permission and then 625 * grant user table write permission with merging the existing permissions). 626 */ 627 static final byte[] HDFS_ACL_FAMILY = Bytes.toBytes("m"); 628 // The value 'R' has no specific meaning, if cell value is not null, it means that the user HDFS 629 // acls is set to hfiles. 630 private static final byte[] HDFS_ACL_VALUE = Bytes.toBytes("R"); 631 632 static void addUserGlobalHdfsAcl(Table aclTable, String user) throws IOException { 633 addUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME); 634 } 635 636 static void addUserNamespaceHdfsAcl(Table aclTable, String user, String namespace) 637 throws IOException { 638 addUserEntry(aclTable, user, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace))); 639 } 640 641 static void addUserTableHdfsAcl(Connection connection, Set<String> users, TableName tableName) 642 throws IOException { 643 try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) { 644 for (String user : users) { 645 addUserTableHdfsAcl(aclTable, user, tableName); 646 } 647 } 648 } 649 650 static void addUserTableHdfsAcl(Connection connection, String user, TableName tableName) 651 throws IOException { 652 try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) { 653 addUserTableHdfsAcl(aclTable, user, tableName); 654 } 655 } 656 657 static void addUserTableHdfsAcl(Table aclTable, String user, TableName tableName) 658 throws IOException { 659 addUserEntry(aclTable, user, tableName.getName()); 660 } 661 662 private static void addUserEntry(Table t, String user, byte[] entry) throws IOException { 663 Put p = new Put(entry); 664 p.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(user), HDFS_ACL_VALUE); 665 t.put(p); 666 } 667 668 static void deleteUserGlobalHdfsAcl(Table aclTable, String user) throws IOException { 669 deleteUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME); 670 } 671 672 static void deleteUserNamespaceHdfsAcl(Table aclTable, String user, String namespace) 673 throws IOException { 674 deleteUserEntry(aclTable, user, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace))); 675 } 676 677 static void deleteUserTableHdfsAcl(Table aclTable, String user, TableName tableName) 678 throws IOException { 679 deleteUserEntry(aclTable, user, tableName.getName()); 680 } 681 682 static void deleteUserTableHdfsAcl(Connection connection, Set<String> users, 683 TableName tableName) throws IOException { 684 try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) { 685 for (String user : users) { 686 deleteUserTableHdfsAcl(aclTable, user, tableName); 687 } 688 } 689 } 690 691 private static void deleteUserEntry(Table aclTable, String user, byte[] entry) 692 throws IOException { 693 Delete delete = new Delete(entry); 694 delete.addColumns(HDFS_ACL_FAMILY, Bytes.toBytes(user)); 695 aclTable.delete(delete); 696 } 697 698 static void deleteNamespaceHdfsAcl(Connection connection, String namespace) throws IOException { 699 try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) { 700 deleteEntry(aclTable, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace))); 701 } 702 } 703 704 static void deleteTableHdfsAcl(Table aclTable, TableName tableName) throws IOException { 705 deleteEntry(aclTable, tableName.getName()); 706 } 707 708 private static void deleteEntry(Table aclTable, byte[] entry) throws IOException { 709 Delete delete = new Delete(entry); 710 delete.addFamily(HDFS_ACL_FAMILY); 711 aclTable.delete(delete); 712 } 713 714 static Set<String> getTableUsers(Table aclTable, TableName tableName) throws IOException { 715 return getEntryUsers(aclTable, tableName.getName()); 716 } 717 718 private static Set<String> getEntryUsers(Table aclTable, byte[] entry) throws IOException { 719 Set<String> users = new HashSet<>(); 720 Get get = new Get(entry); 721 get.addFamily(HDFS_ACL_FAMILY); 722 Result result = aclTable.get(get); 723 List<Cell> cells = result.listCells(); 724 if (cells != null) { 725 for (Cell cell : cells) { 726 if (cell != null) { 727 users.add(Bytes.toString(CellUtil.cloneQualifier(cell))); 728 } 729 } 730 } 731 return users; 732 } 733 734 static Pair<Set<String>, Set<TableName>> getUserNamespaceAndTable(Table aclTable, 735 String userName) throws IOException { 736 Set<String> namespaces = new HashSet<>(); 737 Set<TableName> tables = new HashSet<>(); 738 List<byte[]> userEntries = getUserEntries(aclTable, userName); 739 for (byte[] entry : userEntries) { 740 if (PermissionStorage.isNamespaceEntry(entry)) { 741 namespaces.add(Bytes.toString(PermissionStorage.fromNamespaceEntry(entry))); 742 } else if (PermissionStorage.isTableEntry(entry)) { 743 tables.add(TableName.valueOf(entry)); 744 } 745 } 746 return new Pair<>(namespaces, tables); 747 } 748 749 static List<byte[]> getUserEntries(Table aclTable, String userName) throws IOException { 750 Scan scan = new Scan(); 751 scan.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(userName)); 752 ResultScanner scanner = aclTable.getScanner(scan); 753 List<byte[]> entry = new ArrayList<>(); 754 for (Result result : scanner) { 755 if (result != null && result.getRow() != null) { 756 entry.add(result.getRow()); 757 } 758 } 759 return entry; 760 } 761 762 static boolean hasUserGlobalHdfsAcl(Table aclTable, String user) throws IOException { 763 return hasUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME); 764 } 765 766 static boolean hasUserNamespaceHdfsAcl(Table aclTable, String user, String namespace) 767 throws IOException { 768 return hasUserEntry(aclTable, user, 769 Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace))); 770 } 771 772 static boolean hasUserTableHdfsAcl(Table aclTable, String user, TableName tableName) 773 throws IOException { 774 return hasUserEntry(aclTable, user, tableName.getName()); 775 } 776 777 private static boolean hasUserEntry(Table aclTable, String userName, byte[] entry) 778 throws IOException { 779 Get get = new Get(entry); 780 get.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(userName)); 781 return aclTable.exists(get); 782 } 783 } 784}