| // 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.Preconditions; |
| import com.google.common.collect.Lists; |
| import org.apache.impala.authorization.Authorizable.Type; |
| import org.apache.impala.authorization.AuthorizationConfig; |
| import org.apache.impala.authorization.AuthorizationContext; |
| import org.apache.impala.authorization.AuthorizationException; |
| import org.apache.impala.authorization.BaseAuthorizationChecker; |
| import org.apache.impala.authorization.Privilege; |
| import org.apache.impala.authorization.PrivilegeRequest; |
| import org.apache.impala.authorization.User; |
| import org.apache.impala.authorization.AuthorizationPolicy; |
| import org.apache.impala.common.InternalException; |
| import org.apache.impala.thrift.TSessionState; |
| import org.apache.impala.util.EventSequence; |
| import org.apache.sentry.core.common.ActiveRoleSet; |
| import org.apache.sentry.core.common.Subject; |
| import org.apache.sentry.core.model.db.DBModelAuthorizable; |
| import org.apache.sentry.provider.common.ResourceAuthorizationProvider; |
| |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| /** |
| * An implementation of AuthorizationChecker that uses Sentry. |
| */ |
| public class SentryAuthorizationChecker extends BaseAuthorizationChecker { |
| private final ResourceAuthorizationProvider provider_; |
| private final SentryAuthorizableServer server_; |
| |
| public SentryAuthorizationChecker(AuthorizationConfig config, |
| AuthorizationPolicy policy) { |
| super(config); |
| if (config.isEnabled()) { |
| server_ = new SentryAuthorizableServer(config.getServerName()); |
| provider_ = createProvider(config, new SentryAuthorizationPolicy(policy)); |
| Preconditions.checkNotNull(provider_); |
| } else { |
| provider_ = null; |
| server_ = null; |
| } |
| } |
| |
| /** |
| * Returns the set of groups this user belongs to. |
| */ |
| public Set<String> getUserGroups(User user) throws InternalException { |
| try { |
| return provider_.getGroupMapping().getGroups(user.getShortName()); |
| } catch (Exception e) { |
| if (SentryUtil.isSentryGroupNotFound(e)) { |
| // Sentry 2.1+ throws exceptions when user does not exist; swallow the |
| // exception and just return an empty set for this case. |
| return Collections.emptySet(); |
| } |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| protected void authorizeRowFilterAndColumnMask(User user, |
| List<PrivilegeRequest> privilegeRequests) |
| throws AuthorizationException, InternalException { |
| } |
| |
| @Override |
| public void invalidateAuthorizationCache() { |
| // Authorization refresh in Sentry is done by updating {@link AuthorizationPolicy}. |
| } |
| |
| @Override |
| public boolean needsMaskingOrFiltering(User user, String dbName, String tableName, |
| List<String> requiredColumns) { |
| return false; |
| } |
| |
| @Override |
| public String createColumnMask(User user, String dbName, String tableName, |
| String columnName) { |
| return columnName; |
| } |
| |
| @Override |
| public AuthorizationContext createAuthorizationContext(boolean doAudits, |
| String sqlStmt, TSessionState sessionState, Optional<EventSequence> timeline) { |
| return new AuthorizationContext(timeline); |
| } |
| |
| /* |
| * Creates a new ResourceAuthorizationProvider based on the given configuration. |
| */ |
| private static ResourceAuthorizationProvider createProvider(AuthorizationConfig config, |
| SentryAuthorizationPolicy policy) { |
| return SentryAuthProvider.createProvider(config, policy); |
| } |
| |
| @Override |
| public boolean authorizeResource(AuthorizationContext authzCtx, User user, |
| PrivilegeRequest request) throws InternalException { |
| EnumSet<ImpalaAction> actions = ImpalaAction.from(request.getPrivilege()); |
| |
| List<DBModelAuthorizable> authorizables = Lists.newArrayList( |
| server_.getDBModelAuthorizableHierarchy()); |
| // If request.getAuthorizable() is null, the request is for server-level permission. |
| if (request.getAuthorizable() != null) { |
| Preconditions.checkState(request.getAuthorizable() instanceof SentryAuthorizable); |
| authorizables.addAll(((SentryAuthorizable) request.getAuthorizable()) |
| .getDBModelAuthorizableHierarchy()); |
| } |
| |
| // The Hive Access API does not currently provide a way to check if the user |
| // has any privileges on a given resource. |
| if (request.getPrivilege().hasAnyOf()) { |
| for (ImpalaAction action: actions) { |
| if (provider_.hasAccess(new Subject(user.getShortName()), authorizables, |
| EnumSet.of(action), request.hasGrantOption(), ActiveRoleSet.ALL)) { |
| return true; |
| } |
| } |
| return false; |
| // AuthorizableFn is special due to Sentry not having the concept of a function in |
| // DBModelAuthorizable.AuthorizableType. As a result, the list of authorizables for |
| // an AuthorizableFn only contains the server and database, but not the function |
| // itself. So there is no need to remove the last authorizable here. |
| } else if (request.getPrivilege() == Privilege.CREATE && authorizables.size() > 1 && |
| !(request.getAuthorizable().getType() == Type.FUNCTION)) { |
| // CREATE on an object requires CREATE on the parent, |
| // so don't check access on the object we're creating. |
| authorizables.remove(authorizables.size() - 1); |
| } |
| return provider_.hasAccess(new Subject(user.getShortName()), authorizables, actions, |
| request.hasGrantOption(), ActiveRoleSet.ALL); |
| } |
| } |