org.apache.hive.service.auth.HiveAuthFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.service.auth.HiveAuthFactory.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.hive.service.auth;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import javax.net.ssl.SSLServerSocket;
import javax.security.auth.login.LoginException;
import javax.security.sasl.Sasl;

import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.metastore.HiveMetaStore;
import org.apache.hadoop.hive.metastore.HiveMetaStore.HMSHandler;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.shims.HadoopShims.KerberosNameShim;
import org.apache.hadoop.hive.shims.ShimLoader;
import org.apache.hadoop.hive.thrift.DBTokenStore;
import org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge;
import org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge.Server.ServerMode;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.hive.service.cli.HiveSQLException;
import org.apache.hive.service.cli.thrift.ThriftCLIService;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class helps in some aspects of authentication. It creates the proper Thrift classes for the
 * given configuration as well as helps with authenticating requests.
 */
public class HiveAuthFactory {
    private static final Logger LOG = LoggerFactory.getLogger(HiveAuthFactory.class);

    public enum AuthTypes {
        NOSASL("NOSASL"), NONE("NONE"), LDAP("LDAP"), KERBEROS("KERBEROS"), CUSTOM("CUSTOM"), PAM("PAM");

        private final String authType;

        AuthTypes(String authType) {
            this.authType = authType;
        }

        public String getAuthName() {
            return authType;
        }

    }

    private HadoopThriftAuthBridge.Server saslServer;
    private String authTypeStr;
    private final String transportMode;
    private final HiveConf conf;

    public static final String HS2_PROXY_USER = "hive.server2.proxy.user";
    public static final String HS2_CLIENT_TOKEN = "hiveserver2ClientToken";

    private static Field keytabFile = null;
    private static Method getKeytab = null;
    static {
        Class<?> clz = UserGroupInformation.class;
        try {
            keytabFile = clz.getDeclaredField("keytabFile");
            keytabFile.setAccessible(true);
        } catch (NoSuchFieldException nfe) {
            LOG.debug("Cannot find private field \"keytabFile\" in class: "
                    + UserGroupInformation.class.getCanonicalName(), nfe);
            keytabFile = null;
        }

        try {
            getKeytab = clz.getDeclaredMethod("getKeytab");
            getKeytab.setAccessible(true);
        } catch (NoSuchMethodException nme) {
            LOG.debug("Cannot find private method \"getKeytab\" in class:"
                    + UserGroupInformation.class.getCanonicalName(), nme);
            getKeytab = null;
        }
    }

    public HiveAuthFactory(HiveConf conf) throws TTransportException, IOException {
        this.conf = conf;
        transportMode = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_TRANSPORT_MODE);
        authTypeStr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_AUTHENTICATION);

        // In http mode we use NOSASL as the default auth type
        if ("http".equalsIgnoreCase(transportMode)) {
            if (authTypeStr == null) {
                authTypeStr = AuthTypes.NOSASL.getAuthName();
            }
        } else {
            if (authTypeStr == null) {
                authTypeStr = AuthTypes.NONE.getAuthName();
            }
            if (authTypeStr.equalsIgnoreCase(AuthTypes.KERBEROS.getAuthName())) {
                String principal = conf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_PRINCIPAL);
                String keytab = conf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_KEYTAB);
                if (needUgiLogin(UserGroupInformation.getCurrentUser(),
                        SecurityUtil.getServerPrincipal(principal, "0.0.0.0"), keytab)) {
                    saslServer = ShimLoader.getHadoopThriftAuthBridge().createServer(principal, keytab);
                } else {
                    // Using the default constructor to avoid unnecessary UGI login.
                    saslServer = new HadoopThriftAuthBridge.Server();
                }

                // start delegation token manager
                try {
                    // rawStore is only necessary for DBTokenStore
                    Object rawStore = null;
                    String tokenStoreClass = conf
                            .getVar(HiveConf.ConfVars.METASTORE_CLUSTER_DELEGATION_TOKEN_STORE_CLS);

                    if (tokenStoreClass.equals(DBTokenStore.class.getName())) {
                        HMSHandler baseHandler = new HiveMetaStore.HMSHandler("new db based metaserver", conf,
                                true);
                        rawStore = baseHandler.getMS();
                    }

                    saslServer.startDelegationTokenSecretManager(conf, rawStore, ServerMode.HIVESERVER2);
                } catch (MetaException | IOException e) {
                    throw new TTransportException("Failed to start token manager", e);
                }
            }
        }
    }

    public Map<String, String> getSaslProperties() {
        Map<String, String> saslProps = new HashMap<String, String>();
        SaslQOP saslQOP = SaslQOP.fromString(conf.getVar(ConfVars.HIVE_SERVER2_THRIFT_SASL_QOP));
        saslProps.put(Sasl.QOP, saslQOP.toString());
        saslProps.put(Sasl.SERVER_AUTH, "true");
        return saslProps;
    }

    public TTransportFactory getAuthTransFactory() throws LoginException {
        TTransportFactory transportFactory;
        if (authTypeStr.equalsIgnoreCase(AuthTypes.KERBEROS.getAuthName())) {
            try {
                transportFactory = saslServer.createTransportFactory(getSaslProperties());
            } catch (TTransportException e) {
                throw new LoginException(e.getMessage());
            }
        } else if (authTypeStr.equalsIgnoreCase(AuthTypes.NONE.getAuthName())) {
            transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
        } else if (authTypeStr.equalsIgnoreCase(AuthTypes.LDAP.getAuthName())) {
            transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
        } else if (authTypeStr.equalsIgnoreCase(AuthTypes.PAM.getAuthName())) {
            transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
        } else if (authTypeStr.equalsIgnoreCase(AuthTypes.NOSASL.getAuthName())) {
            transportFactory = new TTransportFactory();
        } else if (authTypeStr.equalsIgnoreCase(AuthTypes.CUSTOM.getAuthName())) {
            transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
        } else {
            throw new LoginException("Unsupported authentication type " + authTypeStr);
        }
        return transportFactory;
    }

    /**
     * Returns the thrift processor factory for HiveServer2 running in binary mode
     * @param service
     * @return
     * @throws LoginException
     */
    public TProcessorFactory getAuthProcFactory(ThriftCLIService service) throws LoginException {
        if (authTypeStr.equalsIgnoreCase(AuthTypes.KERBEROS.getAuthName())) {
            return KerberosSaslHelper.getKerberosProcessorFactory(saslServer, service);
        } else {
            return PlainSaslHelper.getPlainProcessorFactory(service);
        }
    }

    public String getRemoteUser() {
        return saslServer == null ? null : saslServer.getRemoteUser();
    }

    public String getIpAddress() {
        if (saslServer == null || saslServer.getRemoteAddress() == null) {
            return null;
        } else {
            return saslServer.getRemoteAddress().getHostAddress();
        }
    }

    // Perform kerberos login using the hadoop shim API if the configuration is available
    public static void loginFromKeytab(HiveConf hiveConf) throws IOException {
        String principal = hiveConf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_PRINCIPAL);
        String keyTabFile = hiveConf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_KEYTAB);
        if (principal.isEmpty() || keyTabFile.isEmpty()) {
            throw new IOException("HiveServer2 Kerberos principal or keytab is not correctly configured");
        } else {
            UserGroupInformation.loginUserFromKeytab(SecurityUtil.getServerPrincipal(principal, "0.0.0.0"),
                    keyTabFile);
        }
    }

    // Perform SPNEGO login using the hadoop shim API if the configuration is available
    public static UserGroupInformation loginFromSpnegoKeytabAndReturnUGI(HiveConf hiveConf) throws IOException {
        String principal = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_PRINCIPAL);
        String keyTabFile = hiveConf.getVar(ConfVars.HIVE_SERVER2_SPNEGO_KEYTAB);
        if (principal.isEmpty() || keyTabFile.isEmpty()) {
            throw new IOException("HiveServer2 SPNEGO principal or keytab is not correctly configured");
        } else {
            return UserGroupInformation.loginUserFromKeytabAndReturnUGI(
                    SecurityUtil.getServerPrincipal(principal, "0.0.0.0"), keyTabFile);
        }
    }

    public static TTransport getSocketTransport(String host, int port, int loginTimeout) {
        return new TSocket(host, port, loginTimeout);
    }

    public static TTransport getSSLSocket(String host, int port, int loginTimeout) throws TTransportException {
        return TSSLTransportFactory.getClientSocket(host, port, loginTimeout);
    }

    public static TTransport getSSLSocket(String host, int port, int loginTimeout, String trustStorePath,
            String trustStorePassWord) throws TTransportException {
        TSSLTransportFactory.TSSLTransportParameters params = new TSSLTransportFactory.TSSLTransportParameters();
        params.setTrustStore(trustStorePath, trustStorePassWord);
        params.requireClientAuth(true);
        return TSSLTransportFactory.getClientSocket(host, port, loginTimeout, params);
    }

    public static TServerSocket getServerSocket(String hiveHost, int portNum) throws TTransportException {
        InetSocketAddress serverAddress;
        if (hiveHost == null || hiveHost.isEmpty()) {
            // Wildcard bind
            serverAddress = new InetSocketAddress(portNum);
        } else {
            serverAddress = new InetSocketAddress(hiveHost, portNum);
        }
        return new TServerSocket(serverAddress);
    }

    public static TServerSocket getServerSSLSocket(String hiveHost, int portNum, String keyStorePath,
            String keyStorePassWord, List<String> sslVersionBlacklist)
            throws TTransportException, UnknownHostException {
        TSSLTransportFactory.TSSLTransportParameters params = new TSSLTransportFactory.TSSLTransportParameters();
        params.setKeyStore(keyStorePath, keyStorePassWord);
        InetSocketAddress serverAddress;
        if (hiveHost == null || hiveHost.isEmpty()) {
            // Wildcard bind
            serverAddress = new InetSocketAddress(portNum);
        } else {
            serverAddress = new InetSocketAddress(hiveHost, portNum);
        }
        TServerSocket thriftServerSocket = TSSLTransportFactory.getServerSocket(portNum, 0,
                serverAddress.getAddress(), params);
        if (thriftServerSocket.getServerSocket() instanceof SSLServerSocket) {
            List<String> sslVersionBlacklistLocal = new ArrayList<String>();
            for (String sslVersion : sslVersionBlacklist) {
                sslVersionBlacklistLocal.add(sslVersion.trim().toLowerCase(Locale.ROOT));
            }
            SSLServerSocket sslServerSocket = (SSLServerSocket) thriftServerSocket.getServerSocket();
            List<String> enabledProtocols = new ArrayList<String>();
            for (String protocol : sslServerSocket.getEnabledProtocols()) {
                if (sslVersionBlacklistLocal.contains(protocol.toLowerCase(Locale.ROOT))) {
                    LOG.debug("Disabling SSL Protocol: " + protocol);
                } else {
                    enabledProtocols.add(protocol);
                }
            }
            sslServerSocket.setEnabledProtocols(enabledProtocols.toArray(new String[0]));
            LOG.info("SSL Server Socket Enabled Protocols: "
                    + Arrays.toString(sslServerSocket.getEnabledProtocols()));
        }
        return thriftServerSocket;
    }

    // retrieve delegation token for the given user
    public String getDelegationToken(String owner, String renewer) throws HiveSQLException {
        if (saslServer == null) {
            throw new HiveSQLException("Delegation token only supported over kerberos authentication", "08S01");
        }

        try {
            String tokenStr = saslServer.getDelegationTokenWithService(owner, renewer, HS2_CLIENT_TOKEN);
            if (tokenStr == null || tokenStr.isEmpty()) {
                throw new HiveSQLException("Received empty retrieving delegation token for user " + owner, "08S01");
            }
            return tokenStr;
        } catch (IOException e) {
            throw new HiveSQLException("Error retrieving delegation token for user " + owner, "08S01", e);
        } catch (InterruptedException e) {
            throw new HiveSQLException("delegation token retrieval interrupted", "08S01", e);
        }
    }

    // cancel given delegation token
    public void cancelDelegationToken(String delegationToken) throws HiveSQLException {
        if (saslServer == null) {
            throw new HiveSQLException("Delegation token only supported over kerberos authentication", "08S01");
        }
        try {
            saslServer.cancelDelegationToken(delegationToken);
        } catch (IOException e) {
            throw new HiveSQLException("Error canceling delegation token " + delegationToken, "08S01", e);
        }
    }

    public void renewDelegationToken(String delegationToken) throws HiveSQLException {
        if (saslServer == null) {
            throw new HiveSQLException("Delegation token only supported over kerberos authentication", "08S01");
        }
        try {
            saslServer.renewDelegationToken(delegationToken);
        } catch (IOException e) {
            throw new HiveSQLException("Error renewing delegation token " + delegationToken, "08S01", e);
        }
    }

    public String getUserFromToken(String delegationToken) throws HiveSQLException {
        if (saslServer == null) {
            throw new HiveSQLException("Delegation token only supported over kerberos authentication", "08S01");
        }
        try {
            return saslServer.getUserFromToken(delegationToken);
        } catch (IOException e) {
            throw new HiveSQLException("Error extracting user from delegation token " + delegationToken, "08S01",
                    e);
        }
    }

    public static void verifyProxyAccess(String realUser, String proxyUser, String ipAddress, HiveConf hiveConf)
            throws HiveSQLException {
        try {
            UserGroupInformation sessionUgi;
            if (UserGroupInformation.isSecurityEnabled()) {
                KerberosNameShim kerbName = ShimLoader.getHadoopShims().getKerberosNameShim(realUser);
                sessionUgi = UserGroupInformation.createProxyUser(kerbName.getServiceName(),
                        UserGroupInformation.getLoginUser());
            } else {
                sessionUgi = UserGroupInformation.createRemoteUser(realUser);
            }
            if (!proxyUser.equalsIgnoreCase(realUser)) {
                ProxyUsers.refreshSuperUserGroupsConfiguration(hiveConf);
                ProxyUsers.authorize(UserGroupInformation.createProxyUser(proxyUser, sessionUgi), ipAddress,
                        hiveConf);
            }
        } catch (IOException e) {
            throw new HiveSQLException("Failed to validate proxy privilege of " + realUser + " for " + proxyUser,
                    "08S01", e);
        }
    }

    public static boolean needUgiLogin(UserGroupInformation ugi, String principal, String keytab) {
        return null == ugi || !ugi.hasKerberosCredentials() || !ugi.getUserName().equals(principal)
                || !Objects.equals(keytab, getKeytabFromUgi());
    }

    private static String getKeytabFromUgi() {
        synchronized (UserGroupInformation.class) {
            try {
                if (keytabFile != null) {
                    return (String) keytabFile.get(null);
                } else if (getKeytab != null) {
                    return (String) getKeytab.invoke(UserGroupInformation.getCurrentUser());
                } else {
                    return null;
                }
            } catch (Exception e) {
                LOG.debug("Fail to get keytabFile path via reflection", e);
                return null;
            }
        }
    }
}