blob: 11e2aa415ae7ca3e87537c253edab9246fec4d30 [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.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);
}
}
}