| /* |
| * 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.sentry.sqoop.binding; |
| |
| import java.lang.reflect.Constructor; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.sentry.core.common.exception.SentryUserException; |
| import org.apache.sentry.core.common.ActiveRoleSet; |
| import org.apache.sentry.core.common.Authorizable; |
| import org.apache.sentry.core.common.Model; |
| import org.apache.sentry.core.common.Subject; |
| import org.apache.sentry.core.model.sqoop.Server; |
| import org.apache.sentry.core.model.sqoop.SqoopActionConstant; |
| import org.apache.sentry.core.model.sqoop.SqoopActionFactory; |
| import org.apache.sentry.core.model.sqoop.SqoopPrivilegeModel; |
| import org.apache.sentry.policy.common.PolicyEngine; |
| import org.apache.sentry.provider.common.AuthorizationComponent; |
| import org.apache.sentry.provider.common.AuthorizationProvider; |
| import org.apache.sentry.provider.common.ProviderBackend; |
| import org.apache.sentry.provider.common.ProviderBackendContext; |
| import org.apache.sentry.provider.db.generic.SentryGenericProviderBackend; |
| import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient; |
| import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClientFactory; |
| import org.apache.sentry.provider.db.generic.service.thrift.TAuthorizable; |
| import org.apache.sentry.provider.db.generic.service.thrift.TSentryGrantOption; |
| import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege; |
| import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole; |
| import org.apache.sentry.sqoop.conf.SqoopAuthConf.AuthzConfVars; |
| import org.apache.sqoop.common.SqoopException; |
| import org.apache.sqoop.model.MPrivilege; |
| import org.apache.sqoop.model.MResource; |
| import org.apache.sqoop.model.MRole; |
| import org.apache.sqoop.security.SecurityError; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| public class SqoopAuthBinding { |
| private static final Logger LOG = LoggerFactory.getLogger(SqoopAuthBinding.class); |
| private static final String COMPONENT_TYPE = AuthorizationComponent.SQOOP; |
| |
| private final Configuration authConf; |
| private final AuthorizationProvider authProvider; |
| private final Server sqoopServer; |
| private final Subject bindingSubject; |
| private ProviderBackend providerBackend; |
| |
| private final SqoopActionFactory actionFactory = new SqoopActionFactory(); |
| private final String SQOOP_POLICY_ENGINE_OLD = "org.apache.sentry.policy.sqoop.SimpleSqoopPolicyEngine"; |
| |
| public SqoopAuthBinding(Configuration authConf, String serverName) throws Exception { |
| this.authConf = authConf; |
| this.authConf.set(AuthzConfVars.AUTHZ_SERVER_NAME.getVar(), serverName); |
| this.sqoopServer = new Server(serverName); |
| this.authProvider = createAuthProvider(); |
| /** The Sqoop server principal will use the binding */ |
| this.bindingSubject = new Subject(UserGroupInformation.getCurrentUser() |
| .getShortUserName()); |
| } |
| |
| /** |
| * Instantiate the configured authz provider |
| * @return {@link AuthorizationProvider} |
| */ |
| private AuthorizationProvider createAuthProvider() throws Exception { |
| /** |
| * get the authProvider class, policyEngine class, providerBackend class and resources from the sqoopAuthConf config |
| */ |
| String authProviderName = authConf.get(AuthzConfVars.AUTHZ_PROVIDER.getVar(),AuthzConfVars.AUTHZ_PROVIDER.getDefault()); |
| String resourceName = authConf.get(AuthzConfVars.AUTHZ_PROVIDER_RESOURCE.getVar(), AuthzConfVars.AUTHZ_PROVIDER_RESOURCE.getDefault()); |
| String providerBackendName = authConf.get(AuthzConfVars.AUTHZ_PROVIDER_BACKEND.getVar(), AuthzConfVars.AUTHZ_PROVIDER_BACKEND.getDefault()); |
| String policyEngineName = authConf.get(AuthzConfVars.AUTHZ_POLICY_ENGINE.getVar(), AuthzConfVars.AUTHZ_POLICY_ENGINE.getDefault()); |
| String serviceName = authConf.get(AuthzConfVars.AUTHZ_SERVER_NAME.getVar()); |
| |
| // for the backward compatibility |
| if (SQOOP_POLICY_ENGINE_OLD.equals(policyEngineName)) { |
| policyEngineName = AuthzConfVars.AUTHZ_POLICY_ENGINE.getDefault(); |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Using authorization provider " + authProviderName + |
| " with resource " + resourceName + ", policy engine " |
| + policyEngineName + ", provider backend " + providerBackendName); |
| } |
| |
| // the SqoopProviderBackend is deleted in SENTRY-828, this is for the compatible with the |
| // previous Sentry. |
| if ("org.apache.sentry.sqoop.binding.SqoopProviderBackend".equals(providerBackendName)) { |
| providerBackendName = SentryGenericProviderBackend.class.getName(); |
| } |
| |
| //Instantiate the configured providerBackend |
| Constructor<?> providerBackendConstructor = Class.forName(providerBackendName) |
| .getDeclaredConstructor(Configuration.class, String.class); |
| providerBackendConstructor.setAccessible(true); |
| providerBackend = (ProviderBackend) providerBackendConstructor.newInstance(new Object[] { |
| authConf, resourceName }); |
| if (providerBackend instanceof SentryGenericProviderBackend) { |
| ((SentryGenericProviderBackend) providerBackend).setComponentType(COMPONENT_TYPE); |
| ((SentryGenericProviderBackend) providerBackend).setServiceName(serviceName); |
| } |
| |
| // Create backend context |
| ProviderBackendContext context = new ProviderBackendContext(); |
| context.setAllowPerDatabase(false); |
| context.setValidators(SqoopPrivilegeModel.getInstance().getPrivilegeValidators(serviceName)); |
| providerBackend.initialize(context); |
| |
| //Instantiate the configured policyEngine |
| Constructor<?> policyConstructor = |
| Class.forName(policyEngineName).getDeclaredConstructor(ProviderBackend.class); |
| policyConstructor.setAccessible(true); |
| PolicyEngine policyEngine = |
| (PolicyEngine) policyConstructor.newInstance(new Object[] {providerBackend}); |
| |
| //Instantiate the configured authProvider |
| Constructor<?> constrctor = |
| Class.forName(authProviderName).getDeclaredConstructor(Configuration.class, String.class, |
| PolicyEngine.class, Model.class); |
| constrctor.setAccessible(true); |
| return (AuthorizationProvider) constrctor.newInstance(new Object[] {authConf, resourceName, |
| policyEngine, SqoopPrivilegeModel.getInstance()}); |
| } |
| |
| /** |
| * Authorize access to a Sqoop privilege |
| * @param subject |
| * @param authorizable |
| * @param action |
| * @return true or false |
| */ |
| public boolean authorize(Subject subject, MPrivilege privilege) { |
| List<Authorizable> authorizables = toAuthorizable(privilege.getResource()); |
| if (!hasServerInclude(authorizables)) { |
| authorizables.add(0, sqoopServer); |
| } |
| return authProvider.hasAccess(subject, |
| authorizables, |
| Sets.newHashSet(actionFactory.getActionByName(privilege.getAction())), ActiveRoleSet.ALL); |
| } |
| |
| public boolean hasServerInclude(List<Authorizable> authorizables) { |
| for (Authorizable authorizable : authorizables) { |
| if (authorizable.getTypeName().equalsIgnoreCase(sqoopServer.getTypeName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * The Sentry-296(generate client for connection pooling) has already finished development and reviewed by now. When it |
| * was committed to master, the getClient method was needed to refactor using the connection pool |
| */ |
| private SentryGenericServiceClient getClient() throws Exception { |
| return SentryGenericServiceClientFactory.create(authConf); |
| } |
| |
| public void createRole(final Subject subject, final String role) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.createRole(subject.getName(), role, COMPONENT_TYPE); |
| return null; |
| } |
| }); |
| } |
| |
| public void dropRole(final Subject subject, final String role) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.dropRole(subject.getName(), role, COMPONENT_TYPE); |
| return null; |
| } |
| }); |
| } |
| |
| public List<MRole> listAllRoles(final Subject subject) throws SqoopException { |
| Set<TSentryRole> tSentryRoles = execute(new Command<Set<TSentryRole>>() { |
| @Override |
| public Set<TSentryRole> run(SentryGenericServiceClient client) |
| throws Exception { |
| return client.listAllRoles(subject.getName(), COMPONENT_TYPE); |
| } |
| }); |
| |
| List<MRole> roles = Lists.newArrayList(); |
| for (TSentryRole tRole : tSentryRoles) { |
| roles.add(new MRole(tRole.getRoleName())); |
| } |
| return roles; |
| } |
| |
| public List<MRole> listRolesByGroup(final Subject subject, final String groupName) throws SqoopException { |
| Set<TSentryRole> tSentryRoles = execute(new Command<Set<TSentryRole>>() { |
| @Override |
| public Set<TSentryRole> run(SentryGenericServiceClient client) |
| throws Exception { |
| return client.listRolesByGroupName(subject.getName(), groupName, COMPONENT_TYPE); |
| } |
| }); |
| |
| List<MRole> roles = Lists.newArrayList(); |
| for (TSentryRole tSentryRole : tSentryRoles) { |
| roles.add(new MRole(tSentryRole.getRoleName())); |
| } |
| return roles; |
| } |
| |
| public List<MPrivilege> listPrivilegeByRole(final Subject subject, final String role, final MResource resource) throws SqoopException { |
| Set<TSentryPrivilege> tSentryPrivileges = execute(new Command<Set<TSentryPrivilege>>() { |
| @Override |
| public Set<TSentryPrivilege> run(SentryGenericServiceClient client) |
| throws Exception { |
| if (resource == null) { |
| return client.listPrivilegesByRoleName(subject.getName(), role, COMPONENT_TYPE, sqoopServer.getName()); |
| } else if (resource.getType().equalsIgnoreCase(MResource.TYPE.SERVER.name())) { |
| return client.listPrivilegesByRoleName(subject.getName(), role, COMPONENT_TYPE, resource.getName()); |
| } else { |
| return client.listPrivilegesByRoleName(subject.getName(), role, COMPONENT_TYPE, sqoopServer.getName(), toAuthorizable(resource)); |
| } |
| } |
| }); |
| |
| List<MPrivilege> privileges = Lists.newArrayList(); |
| for (TSentryPrivilege tSentryPrivilege : tSentryPrivileges) { |
| privileges.add(toSqoopPrivilege(tSentryPrivilege)); |
| } |
| return privileges; |
| } |
| |
| public void grantPrivilege(final Subject subject, final String role, final MPrivilege privilege) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.grantPrivilege(subject.getName(), role, COMPONENT_TYPE, toTSentryPrivilege(privilege)); |
| return null; |
| } |
| }); |
| } |
| |
| public void revokePrivilege(final Subject subject, final String role, final MPrivilege privilege) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.revokePrivilege(subject.getName(), role, COMPONENT_TYPE, toTSentryPrivilege(privilege)); |
| return null; |
| } |
| }); |
| } |
| |
| public void grantGroupToRole(final Subject subject, final String group, final MRole role) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.addRoleToGroups(subject.getName(), role.getName(), COMPONENT_TYPE, Sets.newHashSet(group)); |
| return null; |
| } |
| }); |
| } |
| |
| public void revokeGroupfromRole(final Subject subject, final String group, final MRole role) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.deleteRoleToGroups(subject.getName(), role.getName(), COMPONENT_TYPE, Sets.newHashSet(group)); |
| return null; |
| } |
| }); |
| } |
| |
| public void renamePrivilege(final Subject subject, final MResource srcResource, final MResource dstResource) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| client.renamePrivilege(subject.getName(), COMPONENT_TYPE, sqoopServer.getName(), |
| toAuthorizable(srcResource), toAuthorizable(dstResource)); |
| return null; |
| } |
| }); |
| } |
| |
| public void dropPrivilege(final MResource resource) throws SqoopException { |
| execute(new Command<Void>() { |
| @Override |
| public Void run(SentryGenericServiceClient client) throws Exception { |
| TSentryPrivilege privilege = new TSentryPrivilege(); |
| privilege.setComponent(COMPONENT_TYPE); |
| privilege.setServiceName(sqoopServer.getName()); |
| privilege.setAuthorizables(toTSentryAuthorizable(resource)); |
| privilege.setAction(SqoopActionConstant.ALL); |
| client.dropPrivilege(bindingSubject.getName(), COMPONENT_TYPE, privilege); |
| return null; |
| } |
| }); |
| } |
| |
| private MPrivilege toSqoopPrivilege(TSentryPrivilege tPrivilege) { |
| //construct a sqoop resource |
| boolean grantOption = false; |
| if (tPrivilege.getGrantOption() == TSentryGrantOption.TRUE) { |
| grantOption = true; |
| } |
| //construct a sqoop privilege |
| return new MPrivilege( |
| toSqoopResource(tPrivilege.getAuthorizables()), |
| tPrivilege.getAction().equalsIgnoreCase(SqoopActionConstant.ALL) ? SqoopActionConstant.ALL_NAME |
| : tPrivilege.getAction(), grantOption); |
| } |
| |
| private MResource toSqoopResource(List<TAuthorizable> authorizables) { |
| if (authorizables == null || authorizables.isEmpty()) { |
| //server resource |
| return new MResource(sqoopServer.getName(), MResource.TYPE.SERVER); |
| } else { |
| //currently Sqoop only has one-level hierarchy authorizable resource |
| return new MResource(authorizables.get(0).getName(), authorizables.get(0).getType()); |
| } |
| } |
| |
| /** |
| * construct a Sentry privilege to call by the thrift API |
| * @param privilege |
| * @return {@link TSentryPrivilege} |
| */ |
| private TSentryPrivilege toTSentryPrivilege(MPrivilege privilege) { |
| TSentryPrivilege tSentryPrivilege = new TSentryPrivilege(); |
| tSentryPrivilege.setComponent(COMPONENT_TYPE); |
| tSentryPrivilege.setServiceName(sqoopServer.getName()); |
| tSentryPrivilege.setAction(privilege.getAction().equalsIgnoreCase( |
| SqoopActionConstant.ALL_NAME) ? SqoopActionConstant.ALL : privilege |
| .getAction()); |
| if (privilege.isWith_grant_option()) { |
| tSentryPrivilege.setGrantOption(TSentryGrantOption.TRUE); |
| } else { |
| tSentryPrivilege.setGrantOption(TSentryGrantOption.FALSE); |
| } |
| tSentryPrivilege.setAuthorizables(toTSentryAuthorizable(privilege.getResource())); |
| return tSentryPrivilege; |
| } |
| |
| |
| private List<TAuthorizable> toTSentryAuthorizable(MResource resource) { |
| List<TAuthorizable> tAuthorizables = Lists.newArrayList(); |
| /** |
| * Currently Sqoop supports grant privileges on server object, but the server name must be equaled the configuration |
| * of org.apache.sqoop.security.authorization.server_name in the Sqoop.properties. |
| */ |
| if (resource.getType().equalsIgnoreCase(MResource.TYPE.SERVER.name())) { |
| if (!resource.getName().equalsIgnoreCase(sqoopServer.getName())) { |
| throw new IllegalArgumentException( resource.getName() + " must be equal to " + sqoopServer.getName() + "\n" + |
| " Currently Sqoop supports grant/revoke privileges on server object, but the server name must be equal to the configuration " + |
| "of org.apache.sqoop.security.authorization.server_name in the Sqoop.properties"); |
| } |
| } else { |
| tAuthorizables.add(new TAuthorizable(resource.getType(), resource.getName())); |
| } |
| return tAuthorizables; |
| } |
| |
| private List<Authorizable> toAuthorizable(final MResource resource) { |
| List<Authorizable> authorizables = Lists.newArrayList(); |
| if (resource == null) { |
| return authorizables; |
| } |
| authorizables.add(new Authorizable() { |
| @Override |
| public String getTypeName() { |
| return resource.getType(); |
| } |
| |
| @Override |
| public String getName() { |
| return resource.getName(); |
| } |
| }); |
| return authorizables; |
| } |
| |
| /** |
| * A Command is a closure used to pass a block of code from individual |
| * functions to execute, which centralizes connection error |
| * handling. Command is parameterized on the return type of the function. |
| */ |
| private interface Command<T> { |
| T run(SentryGenericServiceClient client) throws Exception; |
| } |
| |
| private <T> T execute(Command<T> cmd) throws SqoopException { |
| try (SentryGenericServiceClient client = getClient()){ |
| return cmd.run(client); |
| } catch (SentryUserException ex) { |
| String msg = "Unable to excute command on sentry server: " + ex.getMessage(); |
| LOG.error(msg, ex); |
| throw new SqoopException(SecurityError.AUTH_0014, msg, ex); |
| } catch (Exception ex) { |
| String msg = "Unable to obtain client:" + ex.getMessage(); |
| LOG.error(msg, ex); |
| throw new SqoopException(SecurityError.AUTH_0014, msg, ex); |
| } |
| } |
| } |