blob: d7328d386914fb634223a5f2bc287466e9baf1b2 [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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import org.apache.impala.catalog.CatalogException;
import org.apache.impala.catalog.CatalogServiceCatalog;
import org.apache.impala.catalog.Principal;
import org.apache.impala.catalog.PrincipalPrivilege;
import org.apache.impala.catalog.Role;
import org.apache.impala.common.Reference;
import org.apache.impala.thrift.TCatalogObject;
import org.apache.impala.thrift.TPrincipalType;
import org.apache.log4j.Logger;
import org.apache.sentry.api.service.thrift.TSentryGroup;
import org.apache.sentry.api.service.thrift.TSentryPrivilege;
import org.apache.sentry.api.service.thrift.TSentryRole;
import org.apache.impala.authorization.User;
import org.apache.impala.common.ImpalaException;
import org.apache.impala.common.ImpalaRuntimeException;
import org.apache.impala.common.InternalException;
import org.apache.impala.service.BackendConfig;
import org.apache.impala.thrift.TPrivilege;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.sentry.core.common.exception.SentryUserException;
import org.apache.sentry.service.common.SentryOwnerPrivilegeType;
import org.apache.sentry.service.common.ServiceConstants;
import org.apache.thrift.transport.TTransportException;
/**
* Thread safe class that acts as a link between the Sentry Service and the Catalog
* to ensure both places are updated consistently. More specifically, this class
* synchronizes updates to the Sentry Service and the Impala catalog to ensure
* they are applied atomically (in Impala's view) and only if reading/writing the
* policy via the Sentry Service succeeds. Note that there may be external updates
* to the Sentry Service that cannot be protected against.
* It also periodically refreshes the authorization policy metadata and updates the
* catalog with any changes. Because any catalog updates need to be synchronized with
* updates from GRANT/REVOKE statements, it makes sense for this class to
* synchronize all modifications.
*/
public class SentryProxy {
/**
* This class keeps track of catalog objects added and removed.
*/
@VisibleForTesting
protected static class AuthorizationDelta {
private final List<TCatalogObject> added_;
private final List<TCatalogObject> removed_;
public AuthorizationDelta() {
this(new ArrayList<>(), new ArrayList<>());
}
public AuthorizationDelta(List<TCatalogObject> added,
List<TCatalogObject> removed) {
added_ = added;
removed_ = removed;
}
public List<TCatalogObject> getAddedCatalogObjects() { return added_; }
public List<TCatalogObject> getRemovedCatalogObjects() { return removed_; }
}
private static final Logger LOG = Logger.getLogger(SentryProxy.class);
// Used to periodically poll the Sentry Service and updates the catalog with any
// changes.
private final ScheduledExecutorService policyReader_ =
Executors.newScheduledThreadPool(1);
// The Catalog the SentryPolicyUpdater is associated with.
private final CatalogServiceCatalog catalog_;
// The interface to access the Sentry Policy Service to read policy metadata.
private final SentryPolicyService sentryPolicyService_;
// This is the user that the Catalog Service is running as. For kerberized clusters,
// this is set to the Kerberos principal of Catalog. This user should always be a
// Sentry Service admin => have full rights to read/update the Sentry Service.
private final User processUser_;
// The value for the object ownership config.
private final String objectOwnershipConfigValue_;
public SentryProxy(SentryAuthorizationConfig authzConfig, CatalogServiceCatalog catalog)
throws ImpalaException {
Preconditions.checkNotNull(authzConfig);
SentryConfig sentryConfig = authzConfig.getSentryConfig();
Preconditions.checkNotNull(catalog);
Preconditions.checkNotNull(sentryConfig);
catalog_ = catalog;
String kerberosPrincipal = BackendConfig.INSTANCE.getBackendCfg().getPrincipal();
if (Strings.isNullOrEmpty(kerberosPrincipal)) {
processUser_ = new User(System.getProperty("user.name"));
} else {
processUser_ = new User(kerberosPrincipal);
}
sentryPolicyService_ = new SentryPolicyService(sentryConfig);
// For some tests, we create a config but may not have a config file.
if (sentryConfig.getConfigFile() != null && !sentryConfig.getConfigFile().isEmpty()) {
objectOwnershipConfigValue_ = sentryPolicyService_
.getConfigValue(ServiceConstants.ServerConfig
.SENTRY_DB_POLICY_STORE_OWNER_AS_PRIVILEGE);
} else {
objectOwnershipConfigValue_ = SentryOwnerPrivilegeType.NONE.toString();
}
// We configure the PolicyReader to swallow any exception because we do not want to
// kill the PolicyReader background thread on exception.
policyReader_.scheduleAtFixedRate(new PolicyReader(/*reset versions*/ false,
/*swallow exception*/ true, new AuthorizationDelta()), 0,
BackendConfig.INSTANCE.getSentryCatalogPollingFrequency(), TimeUnit.SECONDS);
}
/**
* Refreshes the authorization policy metadata by querying the Sentry Policy Service.
* There is currently no way to get a snapshot of the policy from the Sentry Service,
* so it is possible that Impala will end up in a state that is not consistent with a
* state the Sentry Service has ever been in. For example, consider the case where a
* refresh is running and all privileges for Principal A have been processed. Before
* moving to Principal B, the user revokes a privilege from Principal A and grants it to
* Principal B. Impala will temporarily (until the next refresh) think the privilege is
* granted to Principal A AND to Principal B.
* TODO: Think more about consistency as well as how to recover from errors that leave
* the policy in a potentially inconsistent state (an RPC fails part-way through a
* refresh). We should also consider applying this entire update to the catalog
* atomically.
*/
private class PolicyReader implements Runnable {
private final boolean resetVersions_;
// A flag to indicate whether or not to swallow any exception thrown.
private final boolean swallowException_;
// Keep track of catalog objects added/removed.
private final AuthorizationDelta authzDelta_;
public PolicyReader(boolean resetVersions, boolean swallowException,
AuthorizationDelta authzDelta) {
resetVersions_ = resetVersions;
swallowException_ = swallowException;
authzDelta_ = authzDelta;
}
public void run() {
synchronized (SentryProxy.this) {
refreshSentryAuthorization(catalog_, sentryPolicyService_, processUser_,
resetVersions_, swallowException_, authzDelta_);
}
}
}
/**
* Refreshes Sentry authorization and updates the catalog.
*/
@VisibleForTesting
static void refreshSentryAuthorization(CatalogServiceCatalog catalog,
SentryPolicyService sentryPolicyService, User processUser, boolean resetVersions,
boolean swallowException, AuthorizationDelta authzDelta) {
long startTime = System.currentTimeMillis();
try {
refreshRolePrivileges(catalog, sentryPolicyService, processUser, resetVersions,
authzDelta);
refreshUserPrivileges(catalog, sentryPolicyService, processUser, resetVersions,
authzDelta);
} catch (Exception e) {
LOG.error("Error refreshing Sentry policy: ", e);
if (swallowException) return;
// We need to differentiate between Sentry not available exception and
// any other exceptions.
if (e.getCause() != null && e.getCause() instanceof SentryUserException) {
Throwable sentryException = e.getCause();
if (sentryException.getCause() != null &&
sentryException.getCause() instanceof TTransportException) {
throw new SentryUnavailableException(e);
}
}
throw new SentryPolicyReaderException(e);
} finally {
LOG.debug("Refreshing Sentry policy took " +
(System.currentTimeMillis() - startTime) + "ms");
}
}
/**
* Updates all roles and their associated privileges in the catalog by adding,
* removing, and replacing the catalog objects to match those in Sentry since
* the last sentry sync update.
*/
private static void refreshRolePrivileges(CatalogServiceCatalog catalog,
SentryPolicyService sentryPolicyService, User processUser, boolean resetVersions,
AuthorizationDelta authzDelta) throws ImpalaException {
// Assume all roles should be removed. Then query the Policy Service and remove
// roles from this set that actually exist.
Set<String> rolesToRemove = catalog.getAuthPolicy().getAllRoleNames();
// The keys (role names) in listAllRolesPrivileges here are always in lower case.
Map<String, Set<TSentryPrivilege>> allRolesPrivileges =
sentryPolicyService.listAllRolesPrivileges(processUser);
// Read the full policy, adding new/modified roles to "updatedRoles".
for (TSentryRole sentryRole:
sentryPolicyService.listAllRoles(processUser)) {
// This role exists and should not be removed, delete it from the
// rolesToRemove set.
rolesToRemove.remove(sentryRole.getRoleName().toLowerCase());
Set<String> grantGroups = Sets.newHashSet();
for (TSentryGroup group: sentryRole.getGroups()) {
grantGroups.add(group.getGroupName());
}
Role existingRole =
catalog.getAuthPolicy().getRole(sentryRole.getRoleName());
Role role;
// These roles are the same, use the current role.
if (existingRole != null &&
existingRole.getGrantGroups().equals(grantGroups)) {
role = existingRole;
if (resetVersions) {
role.setCatalogVersion(catalog.incrementAndGetCatalogVersion());
}
} else {
LOG.debug("Adding role: " + sentryRole.getRoleName());
role = catalog.addRole(sentryRole.getRoleName(), grantGroups);
authzDelta.getAddedCatalogObjects().add(role.toTCatalogObject());
}
// allRolesPrivileges keys and sentryRole.getName() are used here since they both
// come from Sentry so they agree in case.
refreshPrivilegesInCatalog(catalog, resetVersions, sentryRole.getRoleName(), role,
allRolesPrivileges, authzDelta);
}
// Remove all the roles, incrementing the catalog version to indicate
// a change.
for (String roleName: rolesToRemove) {
LOG.debug("Removing role: " + roleName);
authzDelta.getRemovedCatalogObjects().add(
catalog.removeRole(roleName).toTCatalogObject());
}
}
/**
* Updates all users and their associated privileges in the catalog by adding,
* removing, and replacing the catalog objects to match those in Sentry since the
* last Sentry sync update. Take note that we only store the users with privileges
* stored in Sentry and not all available users in the system. User privileges do not
* support grant groups.
*/
private static void refreshUserPrivileges(CatalogServiceCatalog catalog,
SentryPolicyService sentryPolicyService, User processUser, boolean resetVersions,
AuthorizationDelta authzDelta) throws ImpalaException {
// Assume all users should be removed. Then query the Policy Service and remove
// users from this set that actually exist.
Set<String> usersToRemove = catalog.getAuthPolicy().getAllUserNames();
// The keys (user names) in listAllUsersPrivileges here are always in lower case.
Map<String, Set<TSentryPrivilege>> allUsersPrivileges =
sentryPolicyService.listAllUsersPrivileges(processUser);
for (Map.Entry<String, Set<TSentryPrivilege>> userPrivilegesEntry:
allUsersPrivileges.entrySet()) {
String userName = userPrivilegesEntry.getKey();
// This user exists and should not be removed so remove it from the
// usersToRemove set.
usersToRemove.remove(userName);
Reference<Boolean> existingUser = new Reference<>();
org.apache.impala.catalog.User user = catalog.addUserIfNotExists(userName,
existingUser);
if (existingUser.getRef() && resetVersions) {
user.setCatalogVersion(catalog.incrementAndGetCatalogVersion());
} else if (!existingUser.getRef()) {
LOG.debug("Adding user: " + user.getName());
authzDelta.getAddedCatalogObjects().add(user.toTCatalogObject());
}
// allUsersPrivileges keys and userPrivilegesEntry.getKey() are used here since
// they both come from Sentry so they agree in case.
refreshPrivilegesInCatalog(catalog, resetVersions, userPrivilegesEntry.getKey(),
user, allUsersPrivileges, authzDelta);
}
// Remove all the users, incrementing the catalog version to indicate
// a change.
for (String userName: usersToRemove) {
LOG.debug("Removing user: " + userName);
authzDelta.getRemovedCatalogObjects().add(
catalog.removeUser(userName).toTCatalogObject());
}
}
/**
* Updates the privileges for a given principal in the catalog since the last Sentry
* sync update. The sentryPrincipalName is used to match against the key in
* allPrincipalPrivileges, which both come from Sentry, so they should have the
* same case sensitivity.
*/
private static void refreshPrivilegesInCatalog(CatalogServiceCatalog catalog,
boolean resetVersions, String sentryPrincipalName, Principal principal,
Map<String, Set<TSentryPrivilege>> allPrincipalPrivileges,
AuthorizationDelta authzDelta) throws CatalogException {
// Assume all privileges should be removed. Privileges that still exist are
// deleted from this set and we are left with the set of privileges that need
// to be removed.
Set<String> privilegesToRemove = principal.getPrivilegeNames();
// It is important to get a set of privileges using sentryPrincipalName
// and not principal.getName() because principal.getName() may return a
// principal name with a different case than the principal names stored
// in allPrincipalPrivileges. See IMPALA-7729 for more information.
Set<TSentryPrivilege> sentryPrivileges = allPrincipalPrivileges.get(
sentryPrincipalName);
if (sentryPrivileges == null) {
sentryPrivileges = Sets.newHashSet();
}
// Check all the privileges that are part of this principal.
for (TSentryPrivilege sentryPriv: sentryPrivileges) {
TPrivilege thriftPriv =
SentryPolicyService.sentryPrivilegeToTPrivilege(sentryPriv, principal);
String privilegeName = PrincipalPrivilege.buildPrivilegeName(thriftPriv);
privilegesToRemove.remove(privilegeName);
PrincipalPrivilege existingPrincipalPriv = principal.getPrivilege(privilegeName);
// We already know about this privilege (privileges cannot be modified).
if (existingPrincipalPriv != null &&
existingPrincipalPriv.getCreateTimeMs() == sentryPriv.getCreateTime()) {
if (resetVersions) {
existingPrincipalPriv.setCatalogVersion(
catalog.incrementAndGetCatalogVersion());
}
continue;
}
if (principal.getPrincipalType() == TPrincipalType.ROLE) {
LOG.debug("Adding role privilege: " + privilegeName);
authzDelta.getAddedCatalogObjects().add(
catalog.addRolePrivilege(principal.getName(), thriftPriv).toTCatalogObject());
} else {
LOG.debug("Adding user privilege: " + privilegeName);
authzDelta.getAddedCatalogObjects().add(
catalog.addUserPrivilege(principal.getName(), thriftPriv).toTCatalogObject());
}
}
// Remove the privileges that no longer exist.
for (String privilegeName: privilegesToRemove) {
if (principal.getPrincipalType() == TPrincipalType.ROLE) {
LOG.debug("Removing role privilege: " + privilegeName);
authzDelta.getRemovedCatalogObjects().add(
catalog.removeRolePrivilege(principal.getName(), privilegeName)
.toTCatalogObject());
} else {
LOG.debug("Removing user privilege: " + privilegeName);
authzDelta.getRemovedCatalogObjects().add(
catalog.removeUserPrivilege(principal.getName(), privilegeName)
.toTCatalogObject());
}
}
}
/**
* Checks whether the given user is an admin on the Sentry Service.
*/
public boolean isSentryAdmin(User user)
throws InternalException {
try {
return sentryPolicyService_.isSentryAdmin(user);
} catch (SentryUserException e) {
// When a user is not defined in Sentry, isAdmin() will throw
// SentryUserException, we will consider this requesting user
// as a non-Sentry administrator.
return false;
}
}
/**
* Creates a new role using the Sentry Service and updates the Impala catalog.
* If the RPC to the Sentry Service fails the Impala catalog will not
* be modified. Returns the new Role.
* Throws exception if there was any error updating the Sentry Service or
* if a role with the same name already exists in the catalog. This includes
* the case where a role was added externally (eg. via Hive). If the role was added
* externally, Impala will load it during the next refresh of the policy.
* TODO: Consider adding the role to the policy if we find it was created
* externally.
*/
public synchronized Role createRole(User user, String roleName)
throws ImpalaException {
Role role = null;
if (catalog_.getAuthPolicy().getRole(roleName) != null) {
throw new CatalogException("Role already exists: " + roleName);
}
sentryPolicyService_.createRole(user, roleName, false);
// Initially the role has no grant groups (empty set).
role = catalog_.addRole(roleName, Sets.<String>newHashSet());
return role;
}
/**
* Drops the given role using the Sentry Service and updates the Impala catalog.
* If the RPC to the Sentry Service fails the Impala catalog will not
* be modified. Returns the removed Role or null if the role did not exist in the
* Catalog.
* Throws exception if there was any error updating the Sentry Service.
*/
public synchronized Role dropRole(User user, String roleName) throws ImpalaException {
sentryPolicyService_.dropRole(user, roleName, false);
return catalog_.removeRole(roleName);
}
/**
* Removes the role grant group using the Sentry Service and updates the Impala
* catalog. If the RPC to the Sentry Service fails the Impala catalog will not
* be modified. Returns the updated Role.
* Throws exception if there was any error updating the Sentry Service or if the Impala
* catalog does not contain the given role name.
*/
public synchronized Role grantRoleGroup(User user, String roleName, String groupName)
throws ImpalaException {
sentryPolicyService_.grantRoleToGroup(user, roleName, groupName);
return catalog_.addRoleGrantGroup(roleName, groupName);
}
/**
* Removes the role grant group using the Sentry Service and updates the Impala
* catalog. If the RPC to the Sentry Service fails the Impala catalog will not
* be modified. Returns the updated Role.
* Throws exception if there was any error updating the Sentry Service or if the Impala
* catalog does not contain the given role name.
*/
public synchronized Role revokeRoleGroup(User user, String roleName, String groupName)
throws ImpalaException {
sentryPolicyService_.revokeRoleFromGroup(user, roleName, groupName);
return catalog_.removeRoleGrantGroup(roleName, groupName);
}
/**
* Grants privileges to a role in the Sentry Service and updates the Impala
* catalog. If the RPC to the Sentry Service fails, the Impala catalog will not
* be modified. Returns the granted privileges.
* Throws exception if there was any error updating the Sentry Service or if the Impala
* catalog does not contain the given role name.
* This code is odd because we need to avoid duplicate privileges in Sentry because
* the same privilege with and without grant option are two different privileges.
* https://issues.apache.org/jira/browse/SENTRY-2408
* If the current privilege and the requested privilege have the same grant option
* state, then just execute the grant. This is necessary so that if the user does not
* have the ability to grant, Sentry will throw an exception. We don't want to
* expose data by skipping the grant if it already exists.
* For the case when the existing privilege does not have the grant option, but the
* request does, we need to first add the new privilege, then revoke the old one.
* If this is done in the wrong order, and an exception is thrown, the user will get
* a "REVOKE_PRIVILEGE" error on a grant.
* For the case when the existing privilege does have the grant option, but the
* request does not, we add the grant option to the request because the privilege
* should not be "downgraded", we don't want to have duplicates, and we still need
* Sentry to perform the "has grant ability" check. Downgraded indicates changing a
* privilege from one that has a grant option to one that does not.
*/
public synchronized List<PrincipalPrivilege> grantRolePrivileges(User user,
String roleName, List<TPrivilege> privileges, boolean hasGrantOption,
List<PrincipalPrivilege> removedPrivileges) throws ImpalaException {
// First find out what's in the catalog. All privileges will have the same grant
// option set. The only case there will be more than one privilege is in the case
// of multiple column privileges.
Preconditions.checkArgument(!privileges.isEmpty());
TPrivilege tWithGrant = PrincipalPrivilege.copyPrivilegeWithGrant(privileges.get(0),
true);
PrincipalPrivilege catWithGrant = catalog_.getPrincipalPrivilege(roleName,
tWithGrant);
TPrivilege tNoGrant = PrincipalPrivilege.copyPrivilegeWithGrant(privileges.get(0),
false);
PrincipalPrivilege catNoGrant = catalog_.getPrincipalPrivilege(roleName, tNoGrant);
// List of privileges that should be removed. If removed, they will be added to
// the removedPrivileges list.
List<TPrivilege> toRemove = null;
if (catNoGrant != null && hasGrantOption) {
toRemove = privileges.stream().map(p ->
PrincipalPrivilege.copyPrivilegeWithGrant(p, false))
.collect(Collectors.toList());
} else if (catWithGrant != null && !hasGrantOption) {
// Elevate the requested privileges.
privileges = privileges.stream().map(p -> p.setHas_grant_opt(true))
.collect(Collectors.toList());
}
// This is a list of privileges that were added, to be returned.
List<PrincipalPrivilege> rolePrivileges = Lists.newArrayList();
// Do the grants first
sentryPolicyService_.grantRolePrivileges(user, roleName, privileges);
// Update the catalog
for (TPrivilege privilege: privileges) {
rolePrivileges.add(catalog_.addRolePrivilege(roleName, privilege));
}
// Then the revokes
if (toRemove != null && !toRemove.isEmpty()) {
sentryPolicyService_.revokeRolePrivileges(user, roleName, toRemove);
for (TPrivilege privilege : toRemove) {
PrincipalPrivilege rolePriv = catalog_.removeRolePrivilege(roleName,
PrincipalPrivilege.buildPrivilegeName(privilege));
if (rolePriv == null) continue;
removedPrivileges.add(rolePriv);
}
// If we removed anything, it might have removed the grants, so redo the grants.
// TODO: https://issues.apache.org/jira/browse/SENTRY-2408
// When Sentry adds API to modify privileges, this code can be refactored.
sentryPolicyService_.grantRolePrivileges(user, roleName, privileges);
}
return rolePrivileges;
}
/**
* Revokes privileges from a role in the Sentry Service and updates the Impala
* catalog. If the RPC to the Sentry Service fails the Impala catalog will not be
* modified. Returns the removed privileges. Throws an exception if there was any error
* updating the Sentry Service or if the Impala catalog does not contain the given role
* name. The addedPrivileges parameter will be populated with any new privileges that
* are granted, in the case where the revoke involves a grant option. In this case
* privileges that contain the grant option are removed and the new privileges without
* the grant option are added.
*
* The revoke code is confusing because of the various usages of "grant option". The
* parameter "hasGrantOption" indicates that the revoke should just remove the grant
* option from an existing privilege. The grant option on the privilege indicates
* whether the existing privilege has the grant option set which for the case of
* revokes, there's currently no SQL statement that will result in the grant option
* being set on the request.
*/
public synchronized List<PrincipalPrivilege> revokeRolePrivileges(User user,
String roleName, List<TPrivilege> privileges, boolean hasGrantOption,
List<PrincipalPrivilege> addedPrivileges) throws ImpalaException {
List<PrincipalPrivilege> rolePrivileges = Lists.newArrayList();
if (!hasGrantOption) {
sentryPolicyService_.revokeRolePrivileges(user, roleName, privileges);
// Update the catalog. The catalog should only have one privilege whether it has
// the grant option or not. We need to remove it which ever one is set. Since the
// catalog object name for privileges contains the grantoption value, we need to
// check both.
for (TPrivilege privilege: privileges) {
TPrivilege privNotGrant = privilege.deepCopy();
privNotGrant.setHas_grant_opt(!privilege.has_grant_opt);
PrincipalPrivilege rolePrivNotGrant = catalog_.removeRolePrivilege(roleName,
PrincipalPrivilege.buildPrivilegeName(privNotGrant));
PrincipalPrivilege rolePriv = catalog_.removeRolePrivilege(roleName,
PrincipalPrivilege.buildPrivilegeName(privilege));
if (rolePrivNotGrant != null) {
rolePrivileges.add(rolePrivNotGrant);
}
if (rolePriv != null) {
rolePrivileges.add(rolePriv);
}
}
} else {
// If the REVOKE GRANT OPTION has been specified, the privileges with grant must be
// revoked and these same privileges are added back to the catalog with the grant
// option set to false. They can not simply be updated because "grantoption" is part
// of the name of the catalog object. Sentry does not yet provide an
// "alter privilege" API so we need to revoke the privileges and re-grant them.
sentryPolicyService_.revokeRolePrivileges(user, roleName, privileges);
List<TPrivilege> newPrivs = Lists.newArrayList();
for (TPrivilege privilege: privileges) {
PrincipalPrivilege existingPriv = catalog_.getPrincipalPrivilege(roleName,
privilege);
if (existingPriv == null) continue;
rolePrivileges.add(catalog_.removeRolePrivilege(roleName,
PrincipalPrivilege.buildPrivilegeName(privilege)));
TPrivilege addedPriv = new TPrivilege(existingPriv.toThrift());
addedPriv.setHas_grant_opt(false);
newPrivs.add(addedPriv);
}
// Re-grant the updated privileges.
if (!newPrivs.isEmpty()) {
sentryPolicyService_.grantRolePrivileges(user, roleName, newPrivs);
}
// Update the catalog
for (TPrivilege newPriv: newPrivs) {
addedPrivileges.add(catalog_.addRolePrivilege(roleName, newPriv));
}
}
return rolePrivileges;
}
/**
* Performs a synchronous refresh of all authorization policy metadata and updates
* the Catalog with any changes. Throws an ImpalaRuntimeException if there are any
* errors executing the refresh job.
*/
public void refresh(boolean resetVersions, List<TCatalogObject> added,
List<TCatalogObject> removed) throws ImpalaRuntimeException {
try {
// Since this is a synchronous refresh, any exception thrown while running the
// refresh should be thrown here instead of silently swallowing it.
policyReader_.submit(new PolicyReader(resetVersions, /*swallow exception*/ false,
new AuthorizationDelta(added, removed))).get();
} catch (Exception e) {
if (e.getCause() != null && e.getCause() instanceof SentryUnavailableException) {
throw new ImpalaRuntimeException("Error refreshing authorization policy. " +
"Sentry is unavailable. Ensure Sentry is up: ", e);
}
throw new ImpalaRuntimeException("Error refreshing authorization policy, " +
"current policy state may be inconsistent. Running 'invalidate metadata' " +
"may resolve this problem: ", e);
}
}
/**
* Checks if object ownership is enabled in Sentry.
*/
public boolean isObjectOwnershipEnabled() {
if (objectOwnershipConfigValue_.length() == 0 ||
objectOwnershipConfigValue_.equalsIgnoreCase(
SentryOwnerPrivilegeType.NONE.toString())) {
return false;
}
return true;
}
/**
* Checks if 'with grant' is enabled for object ownership in Sentry.
*/
public boolean isObjectOwnershipGrantEnabled() {
if (objectOwnershipConfigValue_.equalsIgnoreCase(
SentryOwnerPrivilegeType.ALL_WITH_GRANT.toString())) {
return true;
}
return false;
}
}