/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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 com.hazelcast.client.impl.protocol.task; import com.hazelcast.client.ClientTypes; import com.hazelcast.client.impl.ClientEndpointImpl; import com.hazelcast.client.impl.ReAuthenticationOperationSupplier; import com.hazelcast.client.impl.client.ClientPrincipal; import com.hazelcast.client.impl.protocol.AuthenticationStatus; import com.hazelcast.client.impl.protocol.ClientMessage; import com.hazelcast.config.GroupConfig; import com.hazelcast.core.Member; import com.hazelcast.core.MemberLeftException; import com.hazelcast.instance.Node; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Address; import com.hazelcast.nio.Connection; import com.hazelcast.nio.ConnectionType; import com.hazelcast.security.Credentials; import com.hazelcast.security.SecurityContext; import com.hazelcast.security.UsernamePasswordCredentials; import com.hazelcast.spi.Operation; import com.hazelcast.util.UuidUtil; import com.hazelcast.util.function.Supplier; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import static java.util.Collections.synchronizedList; /** * Base authentication task */ public abstract class AuthenticationBaseMessageTask<P> extends AbstractMultiTargetMessageTask<P> { protected transient ClientPrincipal principal; protected transient Credentials credentials; protected transient byte clientSerializationVersion; protected transient String clientVersion; private final List<Member> cleanedUpMembers = synchronizedList(new ArrayList<Member>()); public AuthenticationBaseMessageTask(ClientMessage clientMessage, Node node, Connection connection) { super(clientMessage, node, connection); } @Override protected Supplier<Operation> createOperationSupplier() { return new ReAuthenticationOperationSupplier(getUuid(), clientMessage.getCorrelationId()); } @Override protected Object reduce(Map<Member, Object> map) throws Throwable { for (Map.Entry<Member, Object> entry : map.entrySet()) { Member member = entry.getKey(); Object response = entry.getValue(); if (response instanceof Throwable) { if (response instanceof MemberLeftException) { cleanedUpMembers.add(member); continue; } throw (Throwable) response; } boolean isClientDisconnectOperationRun = (Boolean) response; if (isClientDisconnectOperationRun) { cleanedUpMembers.add(member); } } return prepareAuthenticatedClientMessage(); } @Override public Collection<Member> getTargets() { return clientEngine.getClusterService().getMembers(); } @Override protected ClientEndpointImpl getEndpoint() { return new ClientEndpointImpl(clientEngine, connection); } @Override protected boolean isAuthenticationMessage() { return true; } @Override public void processMessage() throws Throwable { byte serializationServiceVersion = serializationService.getVersion(); AuthenticationStatus authenticationStatus; if (clientSerializationVersion != serializationServiceVersion) { sendClientMessage(prepareSerializationVersionMismatchClientMessage()); return; } authenticationStatus = authenticate(); if (authenticationStatus == AuthenticationStatus.CREDENTIALS_FAILED) { sendClientMessage(prepareUnauthenticatedClientMessage()); return; } if (!isOwnerConnection()) { prepareAndSendResponse(authenticationStatus); return; } String uuid = getUuid(); String localMemberUUID = clientEngine.getThisUuid(); principal = new ClientPrincipal(uuid, localMemberUUID); super.processMessage(); } private void prepareAndSendResponse(AuthenticationStatus authenticationStatus) { boolean isNotMember = clientEngine.getClusterService().getMember(principal.getOwnerUuid()) == null; if (isNotMember) { logger.warning("Member having uuid " + principal.getOwnerUuid() + " is not part of the cluster. Client Authentication rejected."); authenticationStatus = AuthenticationStatus.CREDENTIALS_FAILED; } switch (authenticationStatus) { case AUTHENTICATED: sendClientMessage(prepareAuthenticatedClientMessage()); break; case CREDENTIALS_FAILED: sendClientMessage(prepareUnauthenticatedClientMessage()); break; case SERIALIZATION_VERSION_MISMATCH: sendClientMessage(prepareSerializationVersionMismatchClientMessage()); break; default: sendClientMessage(new IllegalStateException("Unsupported authentication status: " + authenticationStatus)); } } private AuthenticationStatus authenticate() { ILogger logger = clientEngine.getLogger(getClass()); AuthenticationStatus status; if (credentials == null) { status = AuthenticationStatus.CREDENTIALS_FAILED; logger.severe("Could not retrieve Credentials object!"); } else if (clientEngine.getSecurityContext() != null) { status = authenticate(clientEngine.getSecurityContext()); } else if (credentials instanceof UsernamePasswordCredentials) { UsernamePasswordCredentials usernamePasswordCredentials = (UsernamePasswordCredentials) credentials; status = authenticate(usernamePasswordCredentials); } else { status = AuthenticationStatus.CREDENTIALS_FAILED; logger.severe("Hazelcast security is disabled.\nUsernamePasswordCredentials or cluster " + "group-name and group-password should be used for authentication!\n" + "Current credentials type is: " + credentials.getClass().getName()); } return status; } private AuthenticationStatus authenticate(SecurityContext securityContext) { Connection connection = endpoint.getConnection(); credentials.setEndpoint(connection.getInetAddress().getHostAddress()); try { LoginContext lc = securityContext.createClientLoginContext(credentials); lc.login(); endpoint.setLoginContext(lc); return AuthenticationStatus.AUTHENTICATED; } catch (LoginException e) { logger.warning(e); return AuthenticationStatus.CREDENTIALS_FAILED; } } private AuthenticationStatus authenticate(UsernamePasswordCredentials credentials) { GroupConfig groupConfig = nodeEngine.getConfig().getGroupConfig(); String nodeGroupName = groupConfig.getName(); String nodeGroupPassword = groupConfig.getPassword(); boolean usernameMatch = nodeGroupName.equals(credentials.getUsername()); boolean passwordMatch = nodeGroupPassword.equals(credentials.getPassword()); return usernameMatch && passwordMatch ? AuthenticationStatus.AUTHENTICATED : AuthenticationStatus.CREDENTIALS_FAILED; } private ClientMessage prepareUnauthenticatedClientMessage() { Connection connection = endpoint.getConnection(); ILogger logger = clientEngine.getLogger(getClass()); logger.warning("Received auth from " + connection + " with principal " + principal + ", authentication failed"); byte status = AuthenticationStatus.CREDENTIALS_FAILED.getId(); return encodeAuth(status, null, null, null, serializationService.getVersion(), null); } private ClientMessage prepareSerializationVersionMismatchClientMessage() { return encodeAuth(AuthenticationStatus.SERIALIZATION_VERSION_MISMATCH.getId(), null, null, null, serializationService.getVersion(), null); } private ClientMessage prepareAuthenticatedClientMessage() { Connection connection = endpoint.getConnection(); ILogger logger = clientEngine.getLogger(getClass()); endpoint.authenticated(principal, credentials, isOwnerConnection(), clientVersion, clientMessage.getCorrelationId()); setConnectionType(); logger.info("Received auth from " + connection + ", successfully authenticated" + ", principal: " + principal + ", owner connection: " + isOwnerConnection() + ", client version: " + clientVersion); if (endpointManager.registerEndpoint(endpoint)) { clientEngine.bind(endpoint); } final Address thisAddress = clientEngine.getThisAddress(); byte status = AuthenticationStatus.AUTHENTICATED.getId(); return encodeAuth(status, thisAddress, principal.getUuid(), principal.getOwnerUuid(), serializationService.getVersion(), cleanedUpMembers); } private void setConnectionType() { String type = getClientType(); if (ClientTypes.JAVA.equals(type)) { connection.setType(ConnectionType.JAVA_CLIENT); } else if (ClientTypes.CSHARP.equals(type)) { connection.setType(ConnectionType.CSHARP_CLIENT); } else if (ClientTypes.CPP.equals(type)) { connection.setType(ConnectionType.CPP_CLIENT); } else if (ClientTypes.PYTHON.equals(type)) { connection.setType(ConnectionType.PYTHON_CLIENT); } else if (ClientTypes.RUBY.equals(type)) { connection.setType(ConnectionType.RUBY_CLIENT); } else if (ClientTypes.NODEJS.equals(type)) { connection.setType(ConnectionType.NODEJS_CLIENT); } else { clientEngine.getLogger(getClass()).info("Unknown client type: " + type); connection.setType(ConnectionType.BINARY_CLIENT); } } protected abstract ClientMessage encodeAuth(byte status, Address thisAddress, String uuid, String ownerUuid, byte serializationVersion, List<Member> cleanedUpMembers); protected abstract boolean isOwnerConnection(); protected abstract String getClientType(); private String getUuid() { if (principal != null) { return principal.getUuid(); } return UuidUtil.createClientUuid(endpoint.getConnection().getEndPoint()); } @Override public Permission getRequiredPermission() { return null; } }