org.apache.sentry.sqoop.binding.SqoopAuthBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sentry.sqoop.binding.SqoopAuthBinding.java

Source

/*
 * 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.api.generic.thrift.SentryGenericServiceClient;
import org.apache.sentry.api.generic.thrift.SentryGenericServiceClientFactory;
import org.apache.sentry.api.generic.thrift.TAuthorizable;
import org.apache.sentry.api.generic.thrift.TSentryGrantOption;
import org.apache.sentry.api.generic.thrift.TSentryPrivilege;
import org.apache.sentry.api.generic.thrift.TSentryRole;
import org.apache.sentry.api.common.ApiConstants;
import org.apache.sentry.api.tools.GenericPrivilegeConverter;
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();
        }

        // for convenience, set the PrivilegeConverter.
        if (authConf.get(ApiConstants.ClientConfig.PRIVILEGE_CONVERTER) == null) {
            authConf.set(ApiConstants.ClientConfig.PRIVILEGE_CONVERTER, GenericPrivilegeConverter.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) throws SentryUserException {
        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.listAllPrivilegesByRoleName(subject.getName(), role, COMPONENT_TYPE,
                            sqoopServer.getName());
                } else if (resource.getType().equalsIgnoreCase(MResource.TYPE.SERVER.name())) {
                    return client.listAllPrivilegesByRoleName(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.grantRoleToGroups(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.revokeRoleFromGroups(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);
        }
    }
}