blob: a18ac07a290eadaf8e2804a7c63ef78c949a43fd [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.geode.internal.protocol.protobuf.v1;
import static org.apache.geode.internal.protocol.protobuf.statistics.ProtobufClientStatistics.PROTOBUF_CLIENT_STATISTICS;
import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Properties;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.experimental.categories.Category;
import org.apache.geode.Statistics;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.distributed.ConfigurationProperties;
import org.apache.geode.internal.AvailablePortHelper;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.protocol.protobuf.v1.serializer.ProtobufProtocolSerializer;
import org.apache.geode.management.internal.security.ResourceConstants;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.ResourcePermission;
import org.apache.geode.security.SecurityManager;
import org.apache.geode.test.junit.categories.ClientServerTest;
/**
* Security seems to have a few possible setups: * Manual SecurityManager set: integrated security *
* Security enabled without SecurityManager set: integrated security * Legacy security: - with peer
* or client auth enabled: we call this incompatible with our auth. - with neither enabled: this is
* what we call "security off". Don't auth at all.
*/
@Category({ClientServerTest.class})
public class AuthenticationIntegrationTest {
private static final String TEST_USERNAME = "bob";
private static final String TEST_PASSWORD = "bobspassword";
private Cache cache;
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
private OutputStream outputStream;
private InputStream inputStream;
private ProtobufProtocolSerializer protobufProtocolSerializer;
private Properties expectedAuthProperties;
private Socket socket;
@Before
public void setUp() {
System.setProperty("geode.feature-protobuf-protocol", "true");
}
public void setupCacheServerAndSocket() throws Exception {
CacheServer cacheServer = cache.addCacheServer();
int cacheServerPort = AvailablePortHelper.getRandomAvailableTCPPort();
cacheServer.setPort(cacheServerPort);
cacheServer.start();
socket = new Socket("localhost", cacheServerPort);
await().until(socket::isConnected);
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
protobufProtocolSerializer = new ProtobufProtocolSerializer();
MessageUtil.performAndVerifyHandshake(socket);
}
private static class SimpleSecurityManager implements SecurityManager {
private final Object authorizedPrincipal;
private final Properties authorizedCredentials;
SimpleSecurityManager(Object validPrincipal, Properties validCredentials) {
this.authorizedPrincipal = validPrincipal;
authorizedCredentials = validCredentials;
}
@Override
public Object authenticate(Properties credentials) throws AuthenticationFailedException {
if (authorizedCredentials.equals(credentials)) {
return authorizedPrincipal;
} else {
throw new AuthenticationFailedException(
"Test properties: " + credentials + " don't match authorized " + authorizedCredentials);
}
}
@Override
public boolean authorize(Object principal, ResourcePermission permission) {
return principal.equals(authorizedPrincipal);
}
}
private Cache createCacheWithSecurityManagerTakingExpectedCreds() {
expectedAuthProperties = new Properties();
expectedAuthProperties.setProperty(ResourceConstants.USER_NAME, TEST_USERNAME);
expectedAuthProperties.setProperty(ResourceConstants.PASSWORD, TEST_PASSWORD);
SimpleSecurityManager securityManager =
new SimpleSecurityManager("this is a secret string or something.", expectedAuthProperties);
Properties properties = new Properties();
CacheFactory cacheFactory = new CacheFactory(properties);
cacheFactory.set(ConfigurationProperties.MCAST_PORT, "0"); // sometimes it isn't due to other
// tests.
cacheFactory.set(ConfigurationProperties.USE_CLUSTER_CONFIGURATION, "false");
cacheFactory.set(ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION, "false");
cacheFactory.setSecurityManager(securityManager);
return cacheFactory.create();
}
private Cache createNoSecurityCache() {
Properties properties = new Properties();
CacheFactory cacheFactory = new CacheFactory(properties);
cacheFactory.set(ConfigurationProperties.MCAST_PORT, "0"); // sometimes it isn't due to other
// tests.
cacheFactory.set(ConfigurationProperties.USE_CLUSTER_CONFIGURATION, "false");
cacheFactory.set(ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION, "false");
cacheFactory.setSecurityManager(null);
return cacheFactory.create();
}
@After
public void tearDown() throws IOException {
if (cache != null) {
cache.close();
cache = null;
}
if (socket != null) {
socket.close();
}
socket = null;
inputStream = null;
outputStream = null;
}
@Test
public void noopAuthenticationSucceeds() throws Exception {
cache = createNoSecurityCache();
setupCacheServerAndSocket();
ClientProtocol.Message getRegionsMessage = ClientProtocol.Message.newBuilder()
.setGetRegionNamesRequest(RegionAPI.GetRegionNamesRequest.newBuilder()).build();
protobufProtocolSerializer.serialize(getRegionsMessage, outputStream);
ClientProtocol.Message regionsResponse = protobufProtocolSerializer.deserialize(inputStream);
assertEquals(ClientProtocol.Message.MessageTypeCase.GETREGIONNAMESRESPONSE,
regionsResponse.getMessageTypeCase());
}
@Test
public void skippingAuthenticationFails() throws Exception {
cache = createCacheWithSecurityManagerTakingExpectedCreds();
setupCacheServerAndSocket();
ClientProtocol.Message getRegionsMessage = ClientProtocol.Message.newBuilder()
.setGetRegionNamesRequest(RegionAPI.GetRegionNamesRequest.newBuilder()).build();
protobufProtocolSerializer.serialize(getRegionsMessage, outputStream);
ClientProtocol.Message errorResponse = protobufProtocolSerializer.deserialize(inputStream);
assertEquals(ClientProtocol.Message.MessageTypeCase.ERRORRESPONSE,
errorResponse.getMessageTypeCase());
assertEquals(BasicTypes.ErrorCode.AUTHENTICATION_FAILED,
errorResponse.getErrorResponse().getError().getErrorCode());
verifyConnectionClosed();
}
@Test
public void simpleAuthenticationSucceeds() throws Exception {
cache = createCacheWithSecurityManagerTakingExpectedCreds();
setupCacheServerAndSocket();
ClientProtocol.Message authenticationRequest = ClientProtocol.Message.newBuilder()
.setHandshakeRequest(ConnectionAPI.HandshakeRequest.newBuilder()
.putCredentials(ResourceConstants.USER_NAME, TEST_USERNAME)
.putCredentials(ResourceConstants.PASSWORD, TEST_PASSWORD))
.build();
authenticationRequest.writeDelimitedTo(outputStream);
ConnectionAPI.HandshakeResponse authenticationResponse =
parseSimpleHandshakeResponseFromInput();
assertTrue(authenticationResponse.getAuthenticated());
ClientProtocol.Message getRegionsMessage = ClientProtocol.Message.newBuilder()
.setGetRegionNamesRequest(RegionAPI.GetRegionNamesRequest.newBuilder()).build();
protobufProtocolSerializer.serialize(getRegionsMessage, outputStream);
ClientProtocol.Message regionsResponse = protobufProtocolSerializer.deserialize(inputStream);
assertEquals(ClientProtocol.Message.MessageTypeCase.GETREGIONNAMESRESPONSE,
regionsResponse.getMessageTypeCase());
}
@Test
public void simpleAuthenticationWithEmptyCreds() throws Exception {
cache = createCacheWithSecurityManagerTakingExpectedCreds();
setupCacheServerAndSocket();
ClientProtocol.Message authenticationRequest = ClientProtocol.Message.newBuilder()
.setHandshakeRequest(ConnectionAPI.HandshakeRequest.newBuilder()).build();
authenticationRequest.writeDelimitedTo(outputStream);
ConnectionAPI.HandshakeResponse authenticationResponse =
parseSimpleHandshakeResponseFromInput();
assertFalse(authenticationResponse.getAuthenticated());
verifyConnectionClosed();
}
@Test
public void simpleAuthenticationWithInvalidCreds() throws Exception {
cache = createCacheWithSecurityManagerTakingExpectedCreds();
setupCacheServerAndSocket();
ClientProtocol.Message authenticationRequest = ClientProtocol.Message.newBuilder()
.setHandshakeRequest(ConnectionAPI.HandshakeRequest.newBuilder()
.putCredentials(ResourceConstants.USER_NAME, TEST_USERNAME)
.putCredentials(ResourceConstants.PASSWORD, "wrong password"))
.build();
authenticationRequest.writeDelimitedTo(outputStream);
ConnectionAPI.HandshakeResponse authenticationResponse =
parseSimpleHandshakeResponseFromInput();
assertFalse(authenticationResponse.getAuthenticated());
Statistics[] stats = cache.getDistributedSystem()
.findStatisticsByType(cache.getDistributedSystem().findType(PROTOBUF_CLIENT_STATISTICS));
assertEquals(1, stats[0].getLong("authenticationFailures"));
verifyConnectionClosed();
}
@Test
public void noAuthenticatorSet() throws Exception {
cache = createNoSecurityCache();
setupCacheServerAndSocket();
// expect the cache to be in what we recognize as a no-security state.
assertFalse(((InternalCache) cache).getSecurityService().isIntegratedSecurity());
assertFalse(((InternalCache) cache).getSecurityService().isClientSecurityRequired());
assertFalse(((InternalCache) cache).getSecurityService().isPeerSecurityRequired());
}
@Test
public void legacyClientAuthenticatorSet() throws Exception {
createLegacyAuthCache("security-client-authenticator");
setupCacheServerAndSocket();
ClientProtocol.Message authenticationRequest = ClientProtocol.Message.newBuilder()
.setHandshakeRequest(ConnectionAPI.HandshakeRequest.newBuilder()).build();
authenticationRequest.writeDelimitedTo(outputStream);
ClientProtocol.Message errorResponse = protobufProtocolSerializer.deserialize(inputStream);
assertEquals(ClientProtocol.Message.MessageTypeCase.ERRORRESPONSE,
errorResponse.getMessageTypeCase());
assertEquals(BasicTypes.ErrorCode.AUTHENTICATION_FAILED,
errorResponse.getErrorResponse().getError().getErrorCode());
verifyConnectionClosed();
}
@Test
public void legacyPeerAuthenticatorSet() throws Exception {
createLegacyAuthCache("security-peer-authenticator");
setupCacheServerAndSocket();
ClientProtocol.Message authenticationRequest = ClientProtocol.Message.newBuilder()
.setHandshakeRequest(ConnectionAPI.HandshakeRequest.newBuilder()).build();
authenticationRequest.writeDelimitedTo(outputStream);
ClientProtocol.Message errorResponse = protobufProtocolSerializer.deserialize(inputStream);
assertEquals(ClientProtocol.Message.MessageTypeCase.ERRORRESPONSE,
errorResponse.getMessageTypeCase());
assertEquals(BasicTypes.ErrorCode.AUTHENTICATION_FAILED,
errorResponse.getErrorResponse().getError().getErrorCode());
verifyConnectionClosed();
}
private void verifyConnectionClosed() {
await().until(() -> {
try {
assertEquals(-1, socket.getInputStream().read()); // EOF implies disconnected.
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
private void createLegacyAuthCache(String authenticationProperty) {
String authenticatorLoadFunction =
"org.apache.geode.security.templates.DummyAuthenticator.create";
Properties properties = new Properties();
properties.setProperty(authenticationProperty, authenticatorLoadFunction);
CacheFactory cacheFactory = new CacheFactory(properties);
cacheFactory.set(ConfigurationProperties.MCAST_PORT, "0"); // sometimes it isn't due to other
// tests.
cacheFactory.set(ConfigurationProperties.USE_CLUSTER_CONFIGURATION, "false");
cacheFactory.set(ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION, "false");
cacheFactory.setSecurityManager(null);
cache = cacheFactory.create();
}
private ConnectionAPI.HandshakeResponse parseSimpleHandshakeResponseFromInput()
throws IOException {
ClientProtocol.Message authenticationResponseMessage =
ClientProtocol.Message.parseDelimitedFrom(inputStream);
assertEquals(ClientProtocol.Message.HANDSHAKERESPONSE_FIELD_NUMBER,
authenticationResponseMessage.getMessageTypeCase().getNumber());
return authenticationResponseMessage.getHandshakeResponse();
}
}