blob: acb6940697a83b02693ed5e60f4353c432616a6d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.security.access;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.SnapshotDescription;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
import org.apache.hadoop.hbase.coprocessor.HasMasterServices;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.MasterObserver;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.UserProvider;
import org.apache.hadoop.hbase.security.access.SnapshotScannerHDFSAclHelper.PathHelper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
/**
* Set HDFS ACLs to hFiles to make HBase granted users have permission to scan snapshot
* <p>
* To use this feature, please mask sure HDFS config:
* <ul>
* <li>dfs.namenode.acls.enabled = true</li>
* <li>fs.permissions.umask-mode = 027 (or smaller umask than 027)</li>
* </ul>
* </p>
* <p>
* The implementation of this feature is as followings:
* <ul>
* <li>For common directories such as 'data' and 'archive', set other permission to '--x' to make
* everyone have the permission to access the directory.</li>
* <li>For namespace or table directories such as 'data/ns/table', 'archive/ns/table' and
* '.hbase-snapshot/snapshotName', set user 'r-x' access acl and 'r-x' default acl when following
* operations happen:
* <ul>
* <li>grant user with global, namespace or table permission;</li>
* <li>revoke user from global, namespace or table;</li>
* <li>snapshot table;</li>
* <li>truncate table;</li>
* </ul>
* </li>
* <li>Note: Because snapshots are at table level, so this feature just considers users with global,
* namespace or table permissions, ignores users with table CF or cell permissions.</li>
* </ul>
* </p>
*/
@CoreCoprocessor
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
public class SnapshotScannerHDFSAclController implements MasterCoprocessor, MasterObserver {
private static final Logger LOG = LoggerFactory.getLogger(SnapshotScannerHDFSAclController.class);
private SnapshotScannerHDFSAclHelper hdfsAclHelper = null;
private PathHelper pathHelper = null;
private MasterServices masterServices = null;
private volatile boolean initialized = false;
private volatile boolean aclTableInitialized = false;
/** Provider for mapping principal names to Users */
private UserProvider userProvider;
@Override
public Optional<MasterObserver> getMasterObserver() {
return Optional.of(this);
}
@Override
public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> c)
throws IOException {
if (c.getEnvironment().getConfiguration()
.getBoolean(SnapshotScannerHDFSAclHelper.ACL_SYNC_TO_HDFS_ENABLE, false)) {
MasterCoprocessorEnvironment mEnv = c.getEnvironment();
if (!(mEnv instanceof HasMasterServices)) {
throw new IOException("Does not implement HMasterServices");
}
masterServices = ((HasMasterServices) mEnv).getMasterServices();
hdfsAclHelper = new SnapshotScannerHDFSAclHelper(masterServices.getConfiguration(),
masterServices.getConnection());
pathHelper = hdfsAclHelper.getPathHelper();
hdfsAclHelper.setCommonDirectoryPermission();
initialized = true;
userProvider = UserProvider.instantiate(c.getEnvironment().getConfiguration());
} else {
LOG.warn("Try to initialize the coprocessor SnapshotScannerHDFSAclController but failure "
+ "because the config " + SnapshotScannerHDFSAclHelper.ACL_SYNC_TO_HDFS_ENABLE
+ " is false.");
}
}
@Override
public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException {
if (!initialized) {
return;
}
try (Admin admin = c.getEnvironment().getConnection().getAdmin()) {
if (admin.tableExists(PermissionStorage.ACL_TABLE_NAME)) {
// Check if acl table has 'm' CF, if not, add 'm' CF
TableDescriptor tableDescriptor = admin.getDescriptor(PermissionStorage.ACL_TABLE_NAME);
boolean containHdfsAclFamily = Arrays.stream(tableDescriptor.getColumnFamilies()).anyMatch(
family -> Bytes.equals(family.getName(), SnapshotScannerHDFSAclStorage.HDFS_ACL_FAMILY));
if (!containHdfsAclFamily) {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableDescriptor)
.setColumnFamily(ColumnFamilyDescriptorBuilder
.newBuilder(SnapshotScannerHDFSAclStorage.HDFS_ACL_FAMILY).build());
admin.modifyTable(builder.build());
}
aclTableInitialized = true;
} else {
throw new TableNotFoundException("Table " + PermissionStorage.ACL_TABLE_NAME
+ " is not created yet. Please check if " + getClass().getName()
+ " is configured after " + AccessController.class.getName());
}
}
}
@Override
public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c) {
if (initialized) {
hdfsAclHelper.close();
}
}
@Override
public void postCompletedCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> c,
TableDescriptor desc, RegionInfo[] regions) throws IOException {
if (needHandleTableHdfsAcl(desc, "createTable " + desc.getTableName())) {
TableName tableName = desc.getTableName();
// 1. Create table directories to make HDFS acls can be inherited
hdfsAclHelper.createTableDirectories(tableName);
// 2. Add table owner HDFS acls
String owner = getActiveUser(c).getShortName();
hdfsAclHelper.addTableAcl(tableName, Sets.newHashSet(owner), "create");
// 3. Record table owner permission is synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(c.getEnvironment().getConnection(), owner,
tableName);
}
}
@Override
public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> c,
NamespaceDescriptor ns) throws IOException {
if (checkInitialized("createNamespace " + ns.getName())) {
// Create namespace directories to make HDFS acls can be inherited
List<Path> paths = hdfsAclHelper.getNamespaceRootPaths(ns.getName());
for (Path path : paths) {
hdfsAclHelper.createDirIfNotExist(path);
}
}
}
@Override
public void postCompletedSnapshotAction(ObserverContext<MasterCoprocessorEnvironment> c,
SnapshotDescription snapshot, TableDescriptor tableDescriptor) throws IOException {
if (needHandleTableHdfsAcl(tableDescriptor, "snapshot " + snapshot.getName())) {
// Add HDFS acls of users with table read permission to snapshot files
hdfsAclHelper.snapshotAcl(snapshot);
}
}
@Override
public void postCompletedTruncateTableAction(ObserverContext<MasterCoprocessorEnvironment> c,
TableName tableName) throws IOException {
if (needHandleTableHdfsAcl(tableName, "truncateTable " + tableName)) {
// 1. create tmp table directories
hdfsAclHelper.createTableDirectories(tableName);
// 2. Since the table directories is recreated, so add HDFS acls again
Set<String> users = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false);
hdfsAclHelper.addTableAcl(tableName, users, "truncate");
}
}
@Override
public void postCompletedDeleteTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableName tableName) throws IOException {
if (!tableName.isSystemTable() && checkInitialized("deleteTable " + tableName)) {
/*
* Remove table user access HDFS acl from namespace directory if the user has no permissions
* of global, ns of the table or other tables of the ns, eg: Bob has 'ns1:t1' read permission,
* when delete 'ns1:t1', if Bob has global read permission, '@ns1' read permission or
* 'ns1:other_tables' read permission, then skip remove Bob access acl in ns1Dirs, otherwise,
* remove Bob access acl.
*/
try (Table aclTable =
ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
Set<String> users = SnapshotScannerHDFSAclStorage.getTableUsers(aclTable, tableName);
if (users.size() > 0) {
// 1. Remove table archive directory default ACLs
hdfsAclHelper.removeTableDefaultAcl(tableName, users);
// 2. Delete table owner permission is synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.deleteTableHdfsAcl(aclTable, tableName);
// 3. Remove namespace access acls
Set<String> removeUsers = filterUsersToRemoveNsAccessAcl(aclTable, tableName, users);
if (removeUsers.size() > 0) {
hdfsAclHelper.removeNamespaceAccessAcl(tableName, removeUsers, "delete");
}
}
}
}
}
@Override
public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableName tableName, TableDescriptor oldDescriptor, TableDescriptor currentDescriptor)
throws IOException {
try (Table aclTable =
ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
if (needHandleTableHdfsAcl(currentDescriptor, "modifyTable " + tableName)
&& !hdfsAclHelper.isAclSyncToHdfsEnabled(oldDescriptor)) {
// 1. Create table directories used for acl inherited
hdfsAclHelper.createTableDirectories(tableName);
// 2. Add table users HDFS acls
Set<String> tableUsers = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false);
Set<String> users =
hdfsAclHelper.getUsersWithNamespaceReadAction(tableName.getNamespaceAsString(), true);
users.addAll(tableUsers);
hdfsAclHelper.addTableAcl(tableName, users, "modify");
// 3. Record table user acls are synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(ctx.getEnvironment().getConnection(),
tableUsers, tableName);
} else if (needHandleTableHdfsAcl(oldDescriptor, "modifyTable " + tableName)
&& !hdfsAclHelper.isAclSyncToHdfsEnabled(currentDescriptor)) {
// 1. Remove empty table directories
List<Path> tableRootPaths = hdfsAclHelper.getTableRootPaths(tableName, false);
for (Path path : tableRootPaths) {
hdfsAclHelper.deleteEmptyDir(path);
}
// 2. Remove all table HDFS acls
Set<String> tableUsers = hdfsAclHelper.getUsersWithTableReadAction(tableName, false, false);
Set<String> users = hdfsAclHelper
.getUsersWithNamespaceReadAction(tableName.getNamespaceAsString(), true);
users.addAll(tableUsers);
hdfsAclHelper.removeTableAcl(tableName, users);
// 3. Remove namespace access HDFS acls for users who only own permission for this table
hdfsAclHelper.removeNamespaceAccessAcl(tableName,
filterUsersToRemoveNsAccessAcl(aclTable, tableName, tableUsers), "modify");
// 4. Record table user acl is not synced to HDFS
SnapshotScannerHDFSAclStorage.deleteUserTableHdfsAcl(ctx.getEnvironment().getConnection(),
tableUsers, tableName);
}
}
}
@Override
public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
String namespace) throws IOException {
if (checkInitialized("deleteNamespace " + namespace)) {
try (Table aclTable =
ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
// 1. Delete namespace archive dir default ACLs
Set<String> users = SnapshotScannerHDFSAclStorage.getEntryUsers(aclTable,
PermissionStorage.toNamespaceEntry(Bytes.toBytes(namespace)));
hdfsAclHelper.removeNamespaceDefaultAcl(namespace, users);
// 2. Record namespace user acl is not synced to HDFS
SnapshotScannerHDFSAclStorage.deleteNamespaceHdfsAcl(ctx.getEnvironment().getConnection(),
namespace);
// 3. Delete tmp namespace directory
/**
* Delete namespace tmp directory because it's created by this coprocessor when namespace is
* created to make namespace default acl can be inherited by tables. The namespace data
* directory is deleted by DeleteNamespaceProcedure, the namespace archive directory is
* deleted by HFileCleaner.
*/
hdfsAclHelper.deleteEmptyDir(pathHelper.getTmpNsDir(namespace));
}
}
}
@Override
public void postGrant(ObserverContext<MasterCoprocessorEnvironment> c,
UserPermission userPermission, boolean mergeExistingPermissions) throws IOException {
if (!checkInitialized(
"grant " + userPermission + ", merge existing permissions " + mergeExistingPermissions)) {
return;
}
try (Table aclTable =
c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
Configuration conf = c.getEnvironment().getConfiguration();
String userName = userPermission.getUser();
switch (userPermission.getAccessScope()) {
case GLOBAL:
UserPermission perm = getUserGlobalPermission(conf, userName);
if (perm != null && hdfsAclHelper.containReadAction(perm)) {
if (!isHdfsAclSet(aclTable, userName)) {
// 1. Get namespaces and tables which global user acls are already synced
Pair<Set<String>, Set<TableName>> skipNamespaceAndTables =
SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName);
Set<String> skipNamespaces = skipNamespaceAndTables.getFirst();
Set<TableName> skipTables = skipNamespaceAndTables.getSecond().stream()
.filter(t -> !skipNamespaces.contains(t.getNamespaceAsString()))
.collect(Collectors.toSet());
// 2. Add HDFS acl(skip namespaces and tables directories whose acl is set)
hdfsAclHelper.grantAcl(userPermission, skipNamespaces, skipTables);
// 3. Record global acl is sync to HDFS
SnapshotScannerHDFSAclStorage.addUserGlobalHdfsAcl(aclTable, userName);
}
} else {
// The merged user permission doesn't contain READ, so remove user global HDFS acls if
// it's set
removeUserGlobalHdfsAcl(aclTable, userName, userPermission);
}
break;
case NAMESPACE:
String namespace = ((NamespacePermission) userPermission.getPermission()).getNamespace();
UserPermission nsPerm = getUserNamespacePermission(conf, userName, namespace);
if (nsPerm != null && hdfsAclHelper.containReadAction(nsPerm)) {
if (!isHdfsAclSet(aclTable, userName, namespace)) {
// 1. Get tables which namespace user acls are already synced
Set<TableName> skipTables = SnapshotScannerHDFSAclStorage
.getUserNamespaceAndTable(aclTable, userName).getSecond();
// 2. Add HDFS acl(skip tables directories whose acl is set)
hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), skipTables);
}
// 3. Record namespace acl is synced to HDFS
SnapshotScannerHDFSAclStorage.addUserNamespaceHdfsAcl(aclTable, userName, namespace);
} else {
// The merged user permission doesn't contain READ, so remove user namespace HDFS acls
// if it's set
removeUserNamespaceHdfsAcl(aclTable, userName, namespace, userPermission);
}
break;
case TABLE:
TablePermission tablePerm = (TablePermission) userPermission.getPermission();
if (needHandleTableHdfsAcl(tablePerm)) {
TableName tableName = tablePerm.getTableName();
UserPermission tPerm = getUserTablePermission(conf, userName, tableName);
if (tPerm != null && hdfsAclHelper.containReadAction(tPerm)) {
if (!isHdfsAclSet(aclTable, userName, tableName)) {
// 1. create table dirs
hdfsAclHelper.createTableDirectories(tableName);
// 2. Add HDFS acl
hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), new HashSet<>(0));
}
// 2. Record table acl is synced to HDFS
SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, userName, tableName);
} else {
// The merged user permission doesn't contain READ, so remove user table HDFS acls if
// it's set
removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission);
}
}
break;
default:
throw new IllegalArgumentException(
"Illegal user permission scope " + userPermission.getAccessScope());
}
}
}
@Override
public void postRevoke(ObserverContext<MasterCoprocessorEnvironment> c,
UserPermission userPermission) throws IOException {
if (checkInitialized("revoke " + userPermission)) {
try (Table aclTable =
c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) {
String userName = userPermission.getUser();
Configuration conf = c.getEnvironment().getConfiguration();
switch (userPermission.getAccessScope()) {
case GLOBAL:
UserPermission userGlobalPerm = getUserGlobalPermission(conf, userName);
if (userGlobalPerm == null || !hdfsAclHelper.containReadAction(userGlobalPerm)) {
removeUserGlobalHdfsAcl(aclTable, userName, userPermission);
}
break;
case NAMESPACE:
NamespacePermission nsPerm = (NamespacePermission) userPermission.getPermission();
UserPermission userNsPerm =
getUserNamespacePermission(conf, userName, nsPerm.getNamespace());
if (userNsPerm == null || !hdfsAclHelper.containReadAction(userNsPerm)) {
removeUserNamespaceHdfsAcl(aclTable, userName, nsPerm.getNamespace(), userPermission);
}
break;
case TABLE:
TablePermission tPerm = (TablePermission) userPermission.getPermission();
if (needHandleTableHdfsAcl(tPerm)) {
TableName tableName = tPerm.getTableName();
UserPermission userTablePerm = getUserTablePermission(conf, userName, tableName);
if (userTablePerm == null || !hdfsAclHelper.containReadAction(userTablePerm)) {
removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission);
}
}
break;
default:
throw new IllegalArgumentException(
"Illegal user permission scope " + userPermission.getAccessScope());
}
}
}
}
private void removeUserGlobalHdfsAcl(Table aclTable, String userName,
UserPermission userPermission) throws IOException {
if (SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName)) {
// 1. Get namespaces and tables which global user acls are already synced
Pair<Set<String>, Set<TableName>> namespaceAndTable =
SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName);
Set<String> skipNamespaces = namespaceAndTable.getFirst();
Set<TableName> skipTables = namespaceAndTable.getSecond().stream()
.filter(t -> !skipNamespaces.contains(t.getNamespaceAsString()))
.collect(Collectors.toSet());
// 2. Remove user HDFS acls(skip namespaces and tables directories
// whose acl must be reversed)
hdfsAclHelper.revokeAcl(userPermission, skipNamespaces, skipTables);
// 3. Remove global user acl is synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.deleteUserGlobalHdfsAcl(aclTable, userName);
}
}
private void removeUserNamespaceHdfsAcl(Table aclTable, String userName, String namespace,
UserPermission userPermission) throws IOException {
if (SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, namespace)) {
if (!SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName)) {
// 1. Get tables whose namespace user acls are already synced
Set<TableName> skipTables =
SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName).getSecond();
// 2. Remove user HDFS acls(skip tables directories whose acl must be reversed)
hdfsAclHelper.revokeAcl(userPermission, new HashSet<>(), skipTables);
}
// 3. Remove namespace user acl is synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.deleteUserNamespaceHdfsAcl(aclTable, userName, namespace);
}
}
private void removeUserTableHdfsAcl(Table aclTable, String userName, TableName tableName,
UserPermission userPermission) throws IOException {
if (SnapshotScannerHDFSAclStorage.hasUserTableHdfsAcl(aclTable, userName, tableName)) {
if (!SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName)
&& !SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName,
tableName.getNamespaceAsString())) {
// 1. Remove table acls
hdfsAclHelper.revokeAcl(userPermission, new HashSet<>(0), new HashSet<>(0));
}
// 2. Remove table user acl is synced to HDFS in acl table
SnapshotScannerHDFSAclStorage.deleteUserTableHdfsAcl(aclTable, userName, tableName);
}
}
private UserPermission getUserGlobalPermission(Configuration conf, String userName)
throws IOException {
List<UserPermission> permissions = PermissionStorage.getUserPermissions(conf,
PermissionStorage.ACL_GLOBAL_NAME, null, null, userName, true);
return permissions.size() > 0 ? permissions.get(0) : null;
}
private UserPermission getUserNamespacePermission(Configuration conf, String userName,
String namespace) throws IOException {
List<UserPermission> permissions =
PermissionStorage.getUserNamespacePermissions(conf, namespace, userName, true);
return permissions.size() > 0 ? permissions.get(0) : null;
}
private UserPermission getUserTablePermission(Configuration conf, String userName,
TableName tableName) throws IOException {
List<UserPermission> permissions = PermissionStorage
.getUserTablePermissions(conf, tableName, null, null, userName, true).stream()
.filter(userPermission -> hdfsAclHelper
.isNotFamilyOrQualifierPermission((TablePermission) userPermission.getPermission()))
.collect(Collectors.toList());
return permissions.size() > 0 ? permissions.get(0) : null;
}
private boolean isHdfsAclSet(Table aclTable, String userName) throws IOException {
return isHdfsAclSet(aclTable, userName, null, null);
}
private boolean isHdfsAclSet(Table aclTable, String userName, String namespace)
throws IOException {
return isHdfsAclSet(aclTable, userName, namespace, null);
}
private boolean isHdfsAclSet(Table aclTable, String userName, TableName tableName)
throws IOException {
return isHdfsAclSet(aclTable, userName, null, tableName);
}
/**
* Check if user global/namespace/table HDFS acls is already set
*/
private boolean isHdfsAclSet(Table aclTable, String userName, String namespace,
TableName tableName) throws IOException {
boolean isSet = SnapshotScannerHDFSAclStorage.hasUserGlobalHdfsAcl(aclTable, userName);
if (namespace != null) {
isSet = isSet
|| SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName, namespace);
}
if (tableName != null) {
isSet = isSet
|| SnapshotScannerHDFSAclStorage.hasUserNamespaceHdfsAcl(aclTable, userName,
tableName.getNamespaceAsString())
|| SnapshotScannerHDFSAclStorage.hasUserTableHdfsAcl(aclTable, userName, tableName);
}
return isSet;
}
@InterfaceAudience.Private
boolean checkInitialized(String operation) {
if (initialized) {
if (aclTableInitialized) {
return true;
} else {
LOG.warn("Skip set HDFS acls because acl table is not initialized when " + operation);
}
}
return false;
}
private boolean needHandleTableHdfsAcl(TablePermission tablePermission) throws IOException {
return needHandleTableHdfsAcl(tablePermission.getTableName(), "")
&& hdfsAclHelper.isNotFamilyOrQualifierPermission(tablePermission);
}
private boolean needHandleTableHdfsAcl(TableName tableName, String operation) throws IOException {
return !tableName.isSystemTable() && checkInitialized(operation) && hdfsAclHelper
.isAclSyncToHdfsEnabled(masterServices.getTableDescriptors().get(tableName));
}
private boolean needHandleTableHdfsAcl(TableDescriptor tableDescriptor, String operation) {
TableName tableName = tableDescriptor.getTableName();
return !tableName.isSystemTable() && checkInitialized(operation)
&& hdfsAclHelper.isAclSyncToHdfsEnabled(tableDescriptor);
}
private User getActiveUser(ObserverContext<?> ctx) throws IOException {
// for non-rpc handling, fallback to system user
Optional<User> optionalUser = ctx.getCaller();
if (optionalUser.isPresent()) {
return optionalUser.get();
}
return userProvider.getCurrent();
}
/**
* Remove table user access HDFS acl from namespace directory if the user has no permissions of
* global, ns of the table or other tables of the ns, eg: Bob has 'ns1:t1' read permission, when
* delete 'ns1:t1', if Bob has global read permission, '@ns1' read permission or
* 'ns1:other_tables' read permission, then skip remove Bob access acl in ns1Dirs, otherwise,
* remove Bob access acl.
* @param aclTable acl table
* @param tableName the name of the table
* @param tablesUsers table users set
* @return users whose access acl will be removed from the namespace of the table
* @throws IOException if an error occurred
*/
private Set<String> filterUsersToRemoveNsAccessAcl(Table aclTable, TableName tableName,
Set<String> tablesUsers) throws IOException {
Set<String> removeUsers = new HashSet<>();
byte[] namespace = tableName.getNamespace();
for (String user : tablesUsers) {
List<byte[]> userEntries = SnapshotScannerHDFSAclStorage.getUserEntries(aclTable, user);
boolean remove = true;
for (byte[] entry : userEntries) {
if (PermissionStorage.isGlobalEntry(entry)
|| (PermissionStorage.isNamespaceEntry(entry)
&& Bytes.equals(PermissionStorage.fromNamespaceEntry(entry), namespace))
|| (!Bytes.equals(tableName.getName(), entry)
&& Bytes.equals(TableName.valueOf(entry).getNamespace(), namespace))) {
remove = false;
break;
}
}
if (remove) {
removeUsers.add(user);
}
}
return removeUsers;
}
static final class SnapshotScannerHDFSAclStorage {
/**
* Add a new CF in HBase acl table to record if the HBase read permission is synchronized to
* related hfiles. The record has two usages: 1. check if we need to remove HDFS acls for a
* grant without READ permission(eg: grant user table read permission and then grant user table
* write permission without merging the existing permissions, in this case, need to remove HDFS
* acls); 2. skip some HDFS acl sync because it may be already set(eg: grant user table read
* permission and then grant user ns read permission; grant user table read permission and then
* grant user table write permission with merging the existing permissions).
*/
static final byte[] HDFS_ACL_FAMILY = Bytes.toBytes("m");
// The value 'R' has no specific meaning, if cell value is not null, it means that the user HDFS
// acls is set to hfiles.
private static final byte[] HDFS_ACL_VALUE = Bytes.toBytes("R");
static void addUserGlobalHdfsAcl(Table aclTable, String user) throws IOException {
addUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME);
}
static void addUserNamespaceHdfsAcl(Table aclTable, String user, String namespace)
throws IOException {
addUserEntry(aclTable, user, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace)));
}
static void addUserTableHdfsAcl(Connection connection, Set<String> users, TableName tableName)
throws IOException {
try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
for (String user : users) {
addUserTableHdfsAcl(aclTable, user, tableName);
}
}
}
static void addUserTableHdfsAcl(Connection connection, String user, TableName tableName)
throws IOException {
try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
addUserTableHdfsAcl(aclTable, user, tableName);
}
}
static void addUserTableHdfsAcl(Table aclTable, String user, TableName tableName)
throws IOException {
addUserEntry(aclTable, user, tableName.getName());
}
private static void addUserEntry(Table t, String user, byte[] entry) throws IOException {
Put p = new Put(entry);
p.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(user), HDFS_ACL_VALUE);
t.put(p);
}
static void deleteUserGlobalHdfsAcl(Table aclTable, String user) throws IOException {
deleteUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME);
}
static void deleteUserNamespaceHdfsAcl(Table aclTable, String user, String namespace)
throws IOException {
deleteUserEntry(aclTable, user, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace)));
}
static void deleteUserTableHdfsAcl(Table aclTable, String user, TableName tableName)
throws IOException {
deleteUserEntry(aclTable, user, tableName.getName());
}
static void deleteUserTableHdfsAcl(Connection connection, Set<String> users,
TableName tableName) throws IOException {
try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
for (String user : users) {
deleteUserTableHdfsAcl(aclTable, user, tableName);
}
}
}
private static void deleteUserEntry(Table aclTable, String user, byte[] entry)
throws IOException {
Delete delete = new Delete(entry);
delete.addColumns(HDFS_ACL_FAMILY, Bytes.toBytes(user));
aclTable.delete(delete);
}
static void deleteNamespaceHdfsAcl(Connection connection, String namespace) throws IOException {
try (Table aclTable = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
deleteEntry(aclTable, Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace)));
}
}
static void deleteTableHdfsAcl(Table aclTable, TableName tableName) throws IOException {
deleteEntry(aclTable, tableName.getName());
}
private static void deleteEntry(Table aclTable, byte[] entry) throws IOException {
Delete delete = new Delete(entry);
delete.addFamily(HDFS_ACL_FAMILY);
aclTable.delete(delete);
}
static Set<String> getTableUsers(Table aclTable, TableName tableName) throws IOException {
return getEntryUsers(aclTable, tableName.getName());
}
private static Set<String> getEntryUsers(Table aclTable, byte[] entry) throws IOException {
Set<String> users = new HashSet<>();
Get get = new Get(entry);
get.addFamily(HDFS_ACL_FAMILY);
Result result = aclTable.get(get);
List<Cell> cells = result.listCells();
if (cells != null) {
for (Cell cell : cells) {
if (cell != null) {
users.add(Bytes.toString(CellUtil.cloneQualifier(cell)));
}
}
}
return users;
}
static Pair<Set<String>, Set<TableName>> getUserNamespaceAndTable(Table aclTable,
String userName) throws IOException {
Set<String> namespaces = new HashSet<>();
Set<TableName> tables = new HashSet<>();
List<byte[]> userEntries = getUserEntries(aclTable, userName);
for (byte[] entry : userEntries) {
if (PermissionStorage.isNamespaceEntry(entry)) {
namespaces.add(Bytes.toString(PermissionStorage.fromNamespaceEntry(entry)));
} else if (PermissionStorage.isTableEntry(entry)) {
tables.add(TableName.valueOf(entry));
}
}
return new Pair<>(namespaces, tables);
}
static List<byte[]> getUserEntries(Table aclTable, String userName) throws IOException {
Scan scan = new Scan();
scan.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(userName));
ResultScanner scanner = aclTable.getScanner(scan);
List<byte[]> entry = new ArrayList<>();
for (Result result : scanner) {
if (result != null && result.getRow() != null) {
entry.add(result.getRow());
}
}
return entry;
}
static boolean hasUserGlobalHdfsAcl(Table aclTable, String user) throws IOException {
return hasUserEntry(aclTable, user, PermissionStorage.ACL_GLOBAL_NAME);
}
static boolean hasUserNamespaceHdfsAcl(Table aclTable, String user, String namespace)
throws IOException {
return hasUserEntry(aclTable, user,
Bytes.toBytes(PermissionStorage.toNamespaceEntry(namespace)));
}
static boolean hasUserTableHdfsAcl(Table aclTable, String user, TableName tableName)
throws IOException {
return hasUserEntry(aclTable, user, tableName.getName());
}
private static boolean hasUserEntry(Table aclTable, String userName, byte[] entry)
throws IOException {
Get get = new Get(entry);
get.addColumn(HDFS_ACL_FAMILY, Bytes.toBytes(userName));
return aclTable.exists(get);
}
}
}