Package org.voltdb

Source Code of org.voltdb.AuthSystem$KerberosAuthenticationRequest

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb;

import static org.voltdb.common.Constants.AUTH_HANDSHAKE;
import static org.voltdb.common.Constants.AUTH_HANDSHAKE_VERSION;
import static org.voltdb.common.Constants.AUTH_SERVICE_NAME;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.mindrot.BCrypt;
import org.voltcore.logging.Level;
import org.voltcore.logging.VoltLogger;
import org.voltdb.catalog.Connector;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Procedure;
import org.voltdb.security.AuthenticationRequest;
import org.voltdb.utils.Encoder;
import org.voltdb.utils.LogKeys;

import com.google_voltpatches.common.base.Charsets;
import com.google_voltpatches.common.base.Throwables;
import com.google_voltpatches.common.collect.ImmutableList;
import com.google_voltpatches.common.collect.ImmutableMap;
import com.google_voltpatches.common.collect.ImmutableSet;
import java.util.EnumSet;
import org.voltdb.common.Permission;


/**
* The AuthSystem parses authentication and permission information from the catalog and uses it to generate a representation
* of the permissions assigned to users and groups.
*
*/
public class AuthSystem {

    private static final VoltLogger authLogger = new VoltLogger("AUTH");

    /**
     * JASS Login configuration entry designator
     */
    public static final String VOLTDB_SERVICE_LOGIN_MODULE =
            System.getProperty("VOLTDB_SERVICE_LOGIN_MODULE", "VoltDBService");

    /**
     * Authentication provider enumeration. It serves also as mapping mechanism
     * for providers, which are configured in the deployment file, and the login
     * packet service field.
     */
    public enum AuthProvider {
        HASH("hash","database"),
        KERBEROS("kerberos","kerberos");

        private final static Map<String,AuthProvider> providerMap;
        private final static Map<String,AuthProvider> serviceMap;

        static {
            ImmutableMap.Builder<String, AuthProvider> pbldr = ImmutableMap.builder();
            ImmutableMap.Builder<String, AuthProvider> sbldr = ImmutableMap.builder();
            for (AuthProvider ap: values()) {
                pbldr.put(ap.provider,ap);
                sbldr.put(ap.service,ap);
            }
            providerMap = pbldr.build();
            serviceMap = sbldr.build();
        }

        final String provider;
        final String service;

        AuthProvider(String provider, String service) {
            this.provider = provider;
            this.service = service;
        }

        /**
         * @return its security provider equivalent
         */
        public String provider() {
            return provider;
        }

        /**
         * @return its login packet service equivalent
         */
        public String service() {
            return service;
        }

        public static AuthProvider fromProvider(String provider) {
            AuthProvider ap = providerMap.get(provider);
            if (ap == null) {
                throw new IllegalArgumentException("No provider mapping for " + provider);
            }
            return ap;
        }

        public static AuthProvider fromService(String service) {
            AuthProvider ap = serviceMap.get(service);
            if (ap == null) {
                throw new IllegalArgumentException("No service mapping for " + service);
            }
            return ap;
        }
    }

    /**
     * Representation of a permission group.
     *
     */
    class AuthGroup {
        /**
         * Name of the group
         */
        private final String m_name;

        /**
         * Set of users that are a member of this group
         */
        private Set<AuthUser> m_users = new HashSet<AuthUser>();

        private final EnumSet<Permission> m_permissions = EnumSet.noneOf(Permission.class);

        /**
         *
         * @param name Name of the group
         * @param sysproc Whether membership in this group grants permission to invoke system procedures
         * @param defaultproc Whether membership in this group grants permission to invoke default procedures
         * @param defaultprocread Whether membership in this group grants permission to invoke only read default procedures
         * @param adhoc Whether membership in this group grants permission to invoke adhoc queries
         */
        private AuthGroup(String name, EnumSet<Permission> permissions) {
            m_name = name.intern();
            m_permissions.addAll(permissions);
        }

        private void finish() {
            m_users = ImmutableSet.copyOf(m_users);
        }
    }

    /**
     * Representation of the permissions associated with a specific user along with a SHA-1 double hashed copy of the users
     * clear text password.
     *
     */
    public static class AuthUser {
        /**
         * SHA-1 double hashed copy of the users clear text password
         */
        private final byte[] m_sha1ShadowPassword;

        /**
         * SHA-1 hashed and then bcrypted copy of the users clear text password
         */
        private final String m_bcryptShadowPassword;

        /**
         * Name of the user
         */
        public final String m_name;

        /**
         * Fast iterable list of groups this user is a member of.
         */
        private List<AuthGroup> m_groups = new ArrayList<AuthGroup>();

        private EnumSet<Permission> m_permissions = EnumSet.noneOf(Permission.class);

        /**
         * Fast membership check set of stored procedures this user has permission to invoke.
         * This is generated when the catalog is parsed and it includes procedures the user has permission
         * to invoke by virtue of group membership. The catalog entry for the stored procedure is used here.
         */
        private Set<Procedure> m_authorizedProcedures = new HashSet<Procedure>();

        /**
         * Set of export connectors this user is authorized to access.
         */
        private Set<Connector> m_authorizedConnectors = new HashSet<Connector>();

        /**
         * The constructor accepts the password as either sha1 or bcrypt. In practice
         * there will be only one passed in depending on the format of the password in the catalog.
         * The other will be null and that is used to determine how to hash the supplied password
         * for auth
         * @param shadowPassword SHA-1 double hashed copy of the users clear text password
         * @param name Name of the user
         */
        private AuthUser(byte[] sha1ShadowPassword, String bcryptShadowPassword, String name) {
            m_sha1ShadowPassword = sha1ShadowPassword;
            m_bcryptShadowPassword = bcryptShadowPassword;
            if (name != null) {
                m_name = name.intern();
            } else {
                m_name = null;
            }
        }

        /**
         * Check if a user has permission to invoke the specified stored procedure
         * Handle both user-written procedures and default auto-generated ones.
         * @param proc Catalog entry for the stored procedure to check
         * @return true if the user has permission and false otherwise
         */
        public boolean hasUserDefinedProcedurePermission(Procedure proc) {
            if (proc == null) {
                return false;
            }
            return hasPermission(Permission.ALLPROC) || m_authorizedProcedures.contains(proc);
        }

        /**
         * Check if a user has any one of given permission.
         * @return true if the user has permission and false otherwise
         */
        public boolean hasPermission(Permission... perms) {
            for (int i = 0; i < perms.length;i++) {
                if (m_permissions.contains(perms[i])) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Get group names.
         * @return group name array
         */
        public final String[] getGroupNames() {
            String[] groupNames = new String[m_groups.size()];
            for (int i = 0; i < m_groups.size(); ++i) {
                groupNames[i] = m_groups.get(i).m_name;
            }
            return groupNames;
        }

        public boolean authorizeConnector(String connectorClass) {
            if (connectorClass == null) {
                return false;
            }
            for (Connector c : m_authorizedConnectors) {
                if (c.getLoaderclass().equals(connectorClass)) {
                    return true;
                }
            }
            return false;
        }

        private void finish() {
            m_groups = ImmutableList.copyOf(m_groups);
            m_authorizedProcedures = ImmutableSet.copyOf(m_authorizedProcedures);
            m_authorizedConnectors = ImmutableSet.copyOf(m_authorizedConnectors);
        }
    }

    /**
     * Storage for user permissions keyed on the username
     */
    private Map<String, AuthUser> m_users = new HashMap<String, AuthUser>();

    /**
     * Storage for group permissions keyed on group name.
     */
    private Map<String, AuthGroup> m_groups = new HashMap<String, AuthGroup>();

    /**
     * Indicates whether security is enabled. If security is disabled all authentications will succede and all returned
     * AuthUsers will allow everything.
     */
    private final boolean m_enabled;

    /**
     * The configured authentication provider
     */
    private final AuthProvider m_authProvider;

    /**
     * VoltDB Kerberos service login context
     */
    private final LoginContext m_loginCtx;

    /**
     * VoltDB service principal name
     */
    private final byte [] m_principalName;

    private final GSSManager m_gssManager;

    AuthSystem(final Database db, boolean enabled) {
        AuthProvider ap = null;
        LoginContext loginContext = null;
        GSSManager gssManager = null;
        byte [] principal = null;

        m_enabled = enabled;
        if (!m_enabled) {
            m_authProvider = ap;
            m_loginCtx = loginContext;
            m_principalName = principal;
            m_gssManager = null;
            return;
        }
        m_authProvider = AuthProvider.fromProvider(db.getSecurityprovider());
        if (m_authProvider == AuthProvider.KERBEROS) {
            try {
                loginContext = new LoginContext(VOLTDB_SERVICE_LOGIN_MODULE);
            } catch (LoginException|SecurityException ex) {
                VoltDB.crashGlobalVoltDB(
                        "Cannot initialize JAAS LoginContext", true, ex);
            }
            try {
                loginContext.login();
                principal = loginContext
                        .getSubject()
                        .getPrincipals()
                        .iterator().next()
                        .getName()
                        .getBytes(Charsets.UTF_8)
                        ;
                gssManager = GSSManager.getInstance();
            } catch (AccountExpiredException ex) {
                VoltDB.crashGlobalVoltDB(
                        "VoltDB assigned service principal has expired", true, ex);
            } catch(CredentialExpiredException ex) {
                VoltDB.crashGlobalVoltDB(
                        "VoltDB assigned service principal credentials have expired", true, ex);
            } catch(FailedLoginException ex) {
                VoltDB.crashGlobalVoltDB(
                        "VoltDB failed to authenticate against kerberos", true, ex);
            }
            catch (LoginException ex) {
                VoltDB.crashGlobalVoltDB(
                        "VoltDB service principal failed to login", true, ex);
            }
            catch (Exception ex) {
                VoltDB.crashGlobalVoltDB(
                        "Unexpected exception occured during service authentication", true, ex);
            }
        }
        m_loginCtx = loginContext;
        m_principalName = principal;
        m_gssManager = gssManager;
        /*
         * First associate all users with groups and vice versa
         */
        for (org.voltdb.catalog.User catalogUser : db.getUsers()) {
            String shadowPassword = catalogUser.getShadowpassword();
            byte sha1ShadowPassword[] = null;
            if (shadowPassword.length() == 40) {
                /*
                 * This is an old catalog with a SHA-1 password
                 * Need to hex decode it
                 */
                sha1ShadowPassword = Encoder.hexDecode(shadowPassword);
            } else if (shadowPassword.length() != 60) {
                /*
                 * If not 40 should be 60 since it is bcrypt
                 */
                VoltDB.crashGlobalVoltDB(
                        "Found a shadowPassword in the catalog that was in an unrecogized format", true, null);
            }

            final AuthUser user = new AuthUser( sha1ShadowPassword, shadowPassword, catalogUser.getTypeName());
            m_users.put(user.m_name, user);
            for (org.voltdb.catalog.GroupRef catalogGroupRef : catalogUser.getGroups()) {
                final org.voltdb.catalog.Group  catalogGroup = catalogGroupRef.getGroup();
                AuthGroup group = null;
                if (!m_groups.containsKey(catalogGroup.getTypeName())) {
                    group = new AuthGroup(catalogGroup.getTypeName(), Permission.getPermissionSetForGroup(catalogGroup));
                    m_groups.put(group.m_name, group);
                } else {
                    group = m_groups.get(catalogGroup.getTypeName());
                }

                user.m_permissions.addAll(group.m_permissions);

                group.m_users.add(user);
                user.m_groups.add(group);
            }
        }

        for (org.voltdb.catalog.Group catalogGroup : db.getGroups()) {
            AuthGroup group = null;
            if (!m_groups.containsKey(catalogGroup.getTypeName())) {
                group = new AuthGroup(catalogGroup.getTypeName(), Permission.getPermissionSetForGroup(catalogGroup));
                m_groups.put(group.m_name, group);
                //A group not associated with any users? Weird stuff.
            } else {
                group = m_groups.get(catalogGroup.getTypeName());
            }
        }

        /*
         * Then iterate through each procedure and and add it
         * to the set of procedures for each specified user and for the members of
         * each specified group.
         */
        for (org.voltdb.catalog.Procedure catalogProcedure : db.getProcedures()) {
            for (org.voltdb.catalog.UserRef catalogUserRef : catalogProcedure.getAuthusers()) {
                final org.voltdb.catalog.User catalogUser = catalogUserRef.getUser();
                final AuthUser user = m_users.get(catalogUser.getTypeName());
                if (user == null) {
                    //Error case. Procedure has a user listed as authorized but no such user exists
                } else {
                    user.m_authorizedProcedures.add(catalogProcedure);
                }
            }

            for (org.voltdb.catalog.GroupRef catalogGroupRef : catalogProcedure.getAuthgroups()) {
                final org.voltdb.catalog.Group catalogGroup = catalogGroupRef.getGroup();
                final AuthGroup group = m_groups.get(catalogGroup.getTypeName());
                if (group == null) {
                    //Error case. Procedure has a group listed as authorized but no such user exists
                } else {
                    for (AuthUser user : group.m_users) {
                        user.m_authorizedProcedures.add(catalogProcedure);
                    }
                }
            }
        }

        m_users = ImmutableMap.copyOf(m_users);
        m_groups = ImmutableMap.copyOf(m_groups);
        for (AuthUser user : m_users.values()) {
            user.finish();
        }
        for (AuthGroup group : m_groups.values()) {
            group.finish();
        }
    }

    /**
     * Check the username and password against the catalog. Return the appropriate permission
     * set for that user if the information is correct and return null otherwise. If security is disabled
     * an AuthUser object that grants all permissions is returned.
     * @param username Name of the user to authenticate
     * @param password SHA-1 single hashed version of the users clear text password
     * @return The permission set for the user if authentication succeeds or null if authentication fails.
     */
    boolean authenticate(String username, byte[] password) {
        if (!m_enabled) {
            return true;
        }
        final AuthUser user = m_users.get(username);
        if (user == null) {
            authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_NoSuchUser.name(), new String[] {username}, null);
            return false;
        }

        boolean matched = true;
        if (user.m_sha1ShadowPassword != null) {
            MessageDigest md = null;
            try {
                md = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException e) {
                VoltDB.crashLocalVoltDB(e.getMessage(), true, e);
            }
            byte passwordHash[] = md.digest(password);

            /*
             * A n00bs attempt at constant time comparison
             */
            for (int ii = 0; ii < passwordHash.length; ii++) {
                if (passwordHash[ii] != user.m_sha1ShadowPassword[ii]){
                    matched = false;
                }
            }
        } else {
            matched = BCrypt.checkpw(Encoder.hexEncode(password), user.m_bcryptShadowPassword);
        }

        if (matched) {
            authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_AuthenticatedUser.name(), new Object[] {username}, null);
            return true;
        } else {
            authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_AuthFailedPasswordMistmatch.name(), new String[] {username}, null);
            return false;
        }
    }

    public static class AuthDisabledUser extends AuthUser {
        public AuthDisabledUser() {
            super(null, null, null);
        }

        @Override
        public boolean hasUserDefinedProcedurePermission(Procedure proc) {
            return true;
        }

        @Override
        public boolean hasPermission(Permission... p) {
            return true;
        }

        @Override
        public boolean authorizeConnector(String connectorName) {
            return true;
        }
    }

    private final AuthUser m_authDisabledUser = new AuthDisabledUser();

    AuthUser getUser(String name) {
        if (!m_enabled) {
            return m_authDisabledUser;
        }
        return m_users.get(name);
    }

    public String[] getGroupNamesForUser(String userName) {
        if (userName == null) {
            return new String[] {};
        }
        AuthUser user = getUser(userName);
        if (user == null) {
            return new String[] {};
        }
        return user.getGroupNames();
    }

    public class HashAuthenticationRequest extends AuthenticationRequest {

        private final String m_user;
        private final byte [] m_password;

        public HashAuthenticationRequest(final String user, final byte [] hash) {
            m_user = user;
            m_password = hash;
        }

        @Override
        protected boolean authenticateImpl() throws Exception {
            if (!m_enabled) {
                m_authenticatedUser = m_user;
                return true;
            }
            else if (m_authProvider != AuthProvider.HASH) {
                return false;
            }
            final AuthUser user = m_users.get(m_user);
            if (user == null) {
                authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_NoSuchUser.name(), new String[] {m_user}, null);
                return false;
            }

            boolean matched = true;
            if (user.m_sha1ShadowPassword != null) {
                MessageDigest md = null;
                try {
                    md = MessageDigest.getInstance("SHA-1");
                } catch (NoSuchAlgorithmException e) {
                    VoltDB.crashLocalVoltDB(e.getMessage(), true, e);
                }
                byte passwordHash[] = md.digest(m_password);

                /*
                 * A n00bs attempt at constant time comparison
                 */
                for (int ii = 0; ii < passwordHash.length; ii++) {
                    if (passwordHash[ii] != user.m_sha1ShadowPassword[ii]){
                        matched = false;
                    }
                }
            } else {
                matched = BCrypt.checkpw(Encoder.hexEncode(m_password), user.m_bcryptShadowPassword);
            }

            if (matched) {
                authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_AuthenticatedUser.name(), new Object[] {m_user}, null);
                m_authenticatedUser = m_user;
                return true;
            } else {
                authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_AuthFailedPasswordMistmatch.name(), new String[] {m_user}, null);
                return false;
            }
        }
    }

    public class KerberosAuthenticationRequest extends AuthenticationRequest {
        private SocketChannel m_socket;
        public KerberosAuthenticationRequest(final SocketChannel socket) {
            m_socket = socket;
        }
        @Override
        protected boolean authenticateImpl() throws Exception {
            if (!m_enabled) {
                m_authenticatedUser = "_^_pinco_pallo_^_";
                return true;
            }
            else if (m_authProvider != AuthProvider.KERBEROS) {
                return false;
            }
            int msgSize =
                      4 // message size header
                    + 1 // protocol version
                    + 1 // result code
                    + 4 // service name length
                    + m_principalName.length;

            final ByteBuffer bb = ByteBuffer.allocate(4096);

            /*
             * write the service principal response. This gives the connecting client
             * the service principal name form which it constructs the GSS context
             * used in the client/service authentication handshake
             */
            bb.putInt(msgSize-4).put(AUTH_HANDSHAKE_VERSION).put(AUTH_SERVICE_NAME);
            bb.putInt(m_principalName.length);
            bb.put(m_principalName);
            bb.flip();

            while (bb.hasRemaining()) {
                m_socket.write(bb);
            }

            String authenticatedUser = Subject.doAs(m_loginCtx.getSubject(), new PrivilegedAction<String>() {
                /**
                 * Establish an authenticated GSS security context
                 * For further information on GSS please refer to
                 * <a href="http://en.wikipedia.org/wiki/Generic_Security_Services_Application_Program_Interface">this</a>
                 * article on Generic Security Services Application Program Interface
                 */
                @Override
                public String run() {
                    GSSContext context = null;
                    try {
                        // derive the credentials from the authenticated service subject
                        context = m_gssManager.createContext((GSSCredential)null);
                        byte [] token;

                        while (!context.isEstablished()) {
                            // read in the next packet size
                            bb.clear().limit(4);
                            while (bb.hasRemaining()) {
                                if (m_socket.read(bb) == -1) throw new EOFException();
                            }
                            bb.flip();

                            int msgSize = bb.getInt();
                            if (msgSize > bb.capacity()) {
                                authLogger.warn("Authentication packet exceeded alloted size");
                                return null;
                            }
                            // read the initiator (client) context token
                            bb.clear().limit(msgSize);
                            while (bb.hasRemaining()) {
                                if (m_socket.read(bb) == -1) throw new EOFException();
                            }
                            bb.flip();

                            byte version = bb.get();
                            if (version != AUTH_HANDSHAKE_VERSION) {
                                authLogger.warn("Encountered unexpected authentication protocol version " + version);
                                return null;
                            }

                            byte tag = bb.get();
                            if (tag != AUTH_HANDSHAKE) {
                                authLogger.warn("Encountered unexpected authentication protocol tag " + tag);
                                return null;
                            }

                            // process the initiator (client) context token. If it returns a non empty token
                            // transmit it to the initiator
                            token = context.acceptSecContext(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining());
                            if (token != null) {
                                msgSize = 4 + 1 + 1 + token.length;
                                bb.clear().limit(msgSize);
                                bb.putInt(msgSize-4).put(AUTH_HANDSHAKE_VERSION).put(AUTH_HANDSHAKE);
                                bb.put(token);
                                bb.flip();

                                while (bb.hasRemaining()) {
                                    m_socket.write(bb);
                                }
                            }
                        }
                        // at this juncture we an established security context between
                        // the client and this service
                        String authenticateUserName = context.getSrcName().toString();

                        // check if both ends are authenticated
                        if (!context.getMutualAuthState()) {
                            return null;
                        }
                        context.dispose();
                        context = null;

                        return authenticateUserName;

                    } catch (IOException|GSSException ex) {
                        Throwables.propagate(ex);
                    } finally {
                        if (context != null) try { context.dispose(); } catch (Exception ignoreIt) {}
                    }
                    return null;
                }
            });

            if (authenticatedUser != null) {
                final AuthUser user = m_users.get(authenticatedUser);
                if (user == null) {
                    authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_NoSuchUser.name(), new String[] {authenticatedUser}, null);
                    return false;
                }
                m_authenticatedUser = authenticatedUser;
                authLogger.l7dlog(Level.INFO, LogKeys.auth_AuthSystem_AuthenticatedUser.name(), new Object[] {authenticatedUser}, null);
            }

            return true;
        }
    }
}
TOP

Related Classes of org.voltdb.AuthSystem$KerberosAuthenticationRequest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.