blob: 4e255d330961d308b38cdeed2cb9fefe043b08ec [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.impala.authorization.sentry;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.hadoop.hive.metastore.api.PrincipalType;
import org.apache.impala.authorization.AuthorizationChecker;
import org.apache.impala.authorization.AuthorizationDelta;
import org.apache.impala.authorization.AuthorizationException;
import org.apache.impala.authorization.AuthorizationManager;
import org.apache.impala.authorization.User;
import org.apache.impala.catalog.Principal;
import org.apache.impala.catalog.Role;
import org.apache.impala.common.ImpalaException;
import org.apache.impala.common.InternalException;
import org.apache.impala.common.JniUtil;
import org.apache.impala.common.UnsupportedFeatureException;
import org.apache.impala.service.FeCatalogManager;
import org.apache.impala.service.FeSupport;
import org.apache.impala.thrift.TCatalogServiceRequestHeader;
import org.apache.impala.thrift.TCreateDropRoleParams;
import org.apache.impala.thrift.TDdlExecResponse;
import org.apache.impala.thrift.TErrorCode;
import org.apache.impala.thrift.TGrantRevokePrivParams;
import org.apache.impala.thrift.TGrantRevokeRoleParams;
import org.apache.impala.thrift.TResultSet;
import org.apache.impala.thrift.TSentryAdminCheckRequest;
import org.apache.impala.thrift.TSentryAdminCheckResponse;
import org.apache.impala.thrift.TShowGrantPrincipalParams;
import org.apache.impala.thrift.TShowRolesParams;
import org.apache.impala.thrift.TShowRolesResult;
import org.apache.impala.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import static org.apache.impala.thrift.TPrincipalType.ROLE;
/**
* An implementation of {@link AuthorizationManager} for Impalad that uses Sentry.
*
* The methods here use the authorization data stored in Impalad catalog via
* {@link org.apache.impala.authorization.AuthorizationPolicy}.
*
* Operations not supported by Sentry will throw an {@link UnsupportedFeatureException}.
*/
public class SentryImpaladAuthorizationManager implements AuthorizationManager {
private static final Logger LOG =
LoggerFactory.getLogger(SentryImpaladAuthorizationManager.class);
private final FeCatalogManager catalog_;
private final Supplier<? extends SentryAuthorizationChecker> authzChecker_;
public SentryImpaladAuthorizationManager(FeCatalogManager catalog,
Supplier<? extends AuthorizationChecker> authzChecker) {
Preconditions.checkNotNull(catalog);
Preconditions.checkNotNull(authzChecker);
Preconditions.checkArgument(authzChecker.get() instanceof SentryAuthorizationChecker);
catalog_ = catalog;
authzChecker_ = (Supplier<SentryAuthorizationChecker>) authzChecker;
}
@Override
public void createRole(User requestingUser, TCreateDropRoleParams params,
TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void dropRole(User requestingUser, TCreateDropRoleParams params,
TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public TShowRolesResult getRoles(TShowRolesParams params) throws ImpalaException {
Set<String> groups = authzChecker_.get()
.getUserGroups(new User(params.requesting_user));
// Check if the user is part of the group (case-sensitive) this SHOW ROLES
// statement is targeting. If they are already a member of the group,
// the admin requirement can be removed.
// If the the statement is SHOW CURRENT ROLES, the admin requirement can also be
// removed.
boolean adminOp =
!(groups.contains(params.getGrant_group()) || params.is_show_current_roles);
if (adminOp) {
validateSentryAdmin(params.getRequesting_user());
}
TShowRolesResult result = new TShowRolesResult();
List<Role> roles = Lists.newArrayList();
if (params.isIs_show_current_roles() || params.isSetGrant_group()) {
User user = new User(params.getRequesting_user());
Set<String> groupNames;
if (params.isIs_show_current_roles()) {
groupNames = authzChecker_.get().getUserGroups(user);
} else {
Preconditions.checkState(params.isSetGrant_group());
groupNames = Sets.newHashSet(params.getGrant_group());
}
for (String groupName: groupNames) {
roles.addAll(catalog_.getOrCreateCatalog().getAuthPolicy()
.getGrantedRoles(groupName));
}
} else {
Preconditions.checkState(!params.isIs_show_current_roles());
roles = catalog_.getOrCreateCatalog().getAuthPolicy().getAllRoles();
}
result.setRole_names(Lists.newArrayListWithExpectedSize(roles.size()));
for (Role role: roles) {
result.getRole_names().add(role.getName());
}
Collections.sort(result.getRole_names());
return result;
}
@Override
public void grantRoleToGroup(User requestingUser, TGrantRevokeRoleParams params,
TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void revokeRoleFromGroup(User requestingUser, TGrantRevokeRoleParams params,
TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void grantPrivilegeToRole(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void revokePrivilegeFromRole(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void grantPrivilegeToUser(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void revokePrivilegeFromUser(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void grantPrivilegeToGroup(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void revokePrivilegeFromGroup(TCatalogServiceRequestHeader header,
TGrantRevokePrivParams params, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public TResultSet getPrivileges(TShowGrantPrincipalParams params)
throws ImpalaException {
Principal principal = (params.principal_type == ROLE) ?
catalog_.getOrCreateCatalog().getAuthPolicy().getPrincipal(params.getName(),
params.getPrincipal_type()) :
Principal.newInstance(params.name, params.principal_type, new HashSet<>());
if (principal == null) {
throw new AuthorizationException(String.format("%s '%s' does not exist.",
Principal.toString(params.principal_type), params.name));
}
if (isAdminOp(params, principal, authzChecker_.get())) {
validateSentryAdmin(params.getRequesting_user());
}
switch (params.getPrincipal_type()) {
case USER:
Set<String> groupNames = authzChecker_.get().getUserGroups(
new User(params.getName()));
return catalog_.getOrCreateCatalog().getAuthPolicy().getUserPrivileges(
params.getName(), groupNames, params.getPrivilege());
case ROLE:
return catalog_.getOrCreateCatalog().getAuthPolicy().getRolePrivileges(
params.getName(), params.getPrivilege());
case GROUP:
throw new UnsupportedFeatureException(
"SHOW GRANT GROUP is not supported by Sentry.");
default:
throw new InternalException(String.format("Unexpected TPrincipalType: %s",
params.getPrincipal_type()));
}
}
private static boolean isAdminOp(TShowGrantPrincipalParams params, Principal principal,
SentryAuthorizationChecker authzChecker) throws ImpalaException {
Set<String> groupNames = authzChecker.getUserGroups(new User(params.requesting_user));
switch (params.principal_type) {
case USER:
return !principal.getName().equals(params.requesting_user);
case GROUP:
case ROLE:
return Sets.intersection(groupNames, principal.getGrantGroups()).isEmpty();
default:
return false;
}
}
@Override
public void updateDatabaseOwnerPrivilege(String serverName, String databaseName,
String oldOwner, PrincipalType oldOwnerType, String newOwner,
PrincipalType newOwnerType, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public void updateTableOwnerPrivilege(String serverName, String databaseName,
String tableName, String oldOwner, PrincipalType oldOwnerType, String newOwner,
PrincipalType newOwnerType, TDdlExecResponse response) throws ImpalaException {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
@Override
public AuthorizationDelta refreshAuthorization(boolean resetVersions) {
throw new UnsupportedOperationException(String.format(
"%s is not supported in Impalad", ClassUtil.getMethodName()));
}
/**
* Validates if the given user is a Sentry admin. The Sentry admin check will make an
* RPC call to the Catalog server. This check is necessary because some operations
* in this class does not need to make a call to Sentry, e.g. "show roles" and
* "show grant" because the authorization data can be retrieved directly from the
* Impalad catalog without going to Sentry. In order to ensure those operations can
* only be executed by a Sentry admin, a separate call to the Catalog server is needed
* to check if the given user is a Sentry admin.
*
* @throws AuthorizationException thrown when a given user is not a Sentry admin.
*/
private static void validateSentryAdmin(String user) throws ImpalaException {
TSentryAdminCheckRequest request = new TSentryAdminCheckRequest();
TCatalogServiceRequestHeader header = new TCatalogServiceRequestHeader();
header.setRequesting_user(user);
request.setHeader(header);
byte[] thriftReq = JniUtil.serializeToThrift(request);
byte[] thriftRes = FeSupport.CheckSentryAdmin(thriftReq);
TSentryAdminCheckResponse response = new TSentryAdminCheckResponse();
JniUtil.deserializeThrift(response, thriftRes);
if (response.getStatus().getStatus_code() != TErrorCode.OK) {
throw new InternalException(String.format("Error requesting SentryAdminCheck: %s",
Joiner.on("\n").join(response.getStatus().getError_msgs())));
}
if (!response.isIs_admin()) {
throw new AuthorizationException(String.format("User '%s' does not have " +
"privileges to access the requested policy metadata.", user));
}
}
}