Repository: geode Updated Branches: refs/heads/develop 4ed790621 -> 9d5cf8ea6
GEODE-3117: fix Gateway authentication with legacy security * add GatewayLegacyAuthenticationRegressionTest to reproduce bug * fix authentication of Gateway sender/receiver with SECURITY_CLIENT_AUTHENTICATOR This closes #601 Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/9d5cf8ea Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/9d5cf8ea Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/9d5cf8ea Branch: refs/heads/develop Commit: 9d5cf8ea6058d1d069309c6a6128b4614e31074e Parents: 4ed7906 Author: Kirk Lund <kl...@apache.org> Authored: Fri Jun 16 16:33:17 2017 -0700 Committer: Kirk Lund <kl...@apache.org> Committed: Thu Jul 6 09:38:01 2017 -0700 ---------------------------------------------------------------------- .../client/internal/ConnectionFactoryImpl.java | 2 +- .../internal/cache/tier/sockets/HandShake.java | 8 +- ...tewayLegacyAuthenticationRegressionTest.java | 423 +++++++++++++++++++ 3 files changed, 429 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/9d5cf8ea/geode-core/src/main/java/org/apache/geode/cache/client/internal/ConnectionFactoryImpl.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/ConnectionFactoryImpl.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/ConnectionFactoryImpl.java index a419d57..dea8644 100644 --- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/ConnectionFactoryImpl.java +++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/ConnectionFactoryImpl.java @@ -79,7 +79,7 @@ public class ConnectionFactoryImpl implements ConnectionFactory { InternalDistributedSystem sys, int socketBufferSize, int handShakeTimeout, int readTimeout, ClientProxyMembershipID proxyId, CancelCriterion cancelCriterion, boolean usedByGateway, GatewaySender sender, long pingInterval, boolean multiuserSecureMode, PoolImpl pool) { - this.handshake = new HandShake(proxyId, sys); + this.handshake = new HandShake(proxyId, sys, sys.getSecurityService()); this.handshake.setClientReadTimeout(readTimeout); this.source = source; this.endpointManager = endpointManager; http://git-wip-us.apache.org/repos/asf/geode/blob/9d5cf8ea/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/HandShake.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/HandShake.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/HandShake.java index 32735b9..690fd83 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/HandShake.java +++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/HandShake.java @@ -333,13 +333,14 @@ public class HandShake implements ClientHandShake { /** * Client-side handshake. This form of HandShake can communicate with a server */ - public HandShake(ClientProxyMembershipID id, DistributedSystem sys) { + public HandShake(ClientProxyMembershipID id, DistributedSystem sys, + SecurityService securityService) { this.id = id; this.code = REPLY_OK; this.system = sys; setOverrides(); this.credentials = null; - this.securityService = SecurityServiceFactory.create(); + this.securityService = securityService; } public void updateProxyID(InternalDistributedMember idm) { @@ -1703,8 +1704,9 @@ public class HandShake implements ClientHandShake { } catch (Exception ex) { throw new AuthenticationFailedException(ex.getMessage(), ex); } finally { - if (auth != null) + if (auth != null) { auth.close(); + } } } http://git-wip-us.apache.org/repos/asf/geode/blob/9d5cf8ea/geode-wan/src/test/java/org/apache/geode/internal/cache/wan/misc/GatewayLegacyAuthenticationRegressionTest.java ---------------------------------------------------------------------- diff --git a/geode-wan/src/test/java/org/apache/geode/internal/cache/wan/misc/GatewayLegacyAuthenticationRegressionTest.java b/geode-wan/src/test/java/org/apache/geode/internal/cache/wan/misc/GatewayLegacyAuthenticationRegressionTest.java new file mode 100644 index 0000000..c7ce6bd --- /dev/null +++ b/geode-wan/src/test/java/org/apache/geode/internal/cache/wan/misc/GatewayLegacyAuthenticationRegressionTest.java @@ -0,0 +1,423 @@ +/* + * 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.cache.wan.misc; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.geode.distributed.ConfigurationProperties.DISTRIBUTED_SYSTEM_ID; +import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS; +import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.REMOTE_LOCATORS; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTHENTICATOR; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_PEER_AUTHENTICATOR; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_PEER_AUTH_INIT; +import static org.apache.geode.distributed.ConfigurationProperties.START_LOCATOR; +import static org.apache.geode.test.dunit.Host.getHost; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.waitAtMost; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.security.Principal; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.LogWriter; +import org.apache.geode.cache.AttributesFactory; +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.DiskStoreFactory; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.Scope; +import org.apache.geode.cache.wan.GatewayReceiver; +import org.apache.geode.cache.wan.GatewayReceiverFactory; +import org.apache.geode.cache.wan.GatewaySender; +import org.apache.geode.cache.wan.GatewaySenderFactory; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.DistributedSystem; +import org.apache.geode.internal.AvailablePortHelper; +import org.apache.geode.internal.cache.wan.InternalGatewaySenderFactory; +import org.apache.geode.security.AuthInitialize; +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.Authenticator; +import org.apache.geode.test.dunit.DistributedTestCase; +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.junit.categories.DistributedTest; +import org.apache.geode.test.junit.categories.SecurityTest; +import org.apache.geode.test.junit.categories.WanTest; +import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder; + +/** + * Reproduces bug GEODE-3117: "Gateway authentication throws NullPointerException" and validates the + * fix. + */ +@Category({DistributedTest.class, SecurityTest.class, WanTest.class}) +public class GatewayLegacyAuthenticationRegressionTest extends DistributedTestCase { + + private static final String USER_NAME = "security-username"; + private static final String PASSWORD = "security-password"; + + private static final AtomicInteger invokeAuthenticateCount = new AtomicInteger(); + + private static Cache cache; + private static GatewaySender sender; + + private VM londonLocatorVM; + private VM newYorkLocatorVM; + private VM londonServerVM; + private VM newYorkServerVM; + + private String londonName; + private String newYorkName; + + private int londonId; + private int newYorkId; + + private int londonLocatorPort; + private int newYorkLocatorPort; + + private String regionName; + + @Rule + public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder(); + + @Before + public void before() { + invokeAuthenticateCount.set(0); + + this.londonLocatorVM = getHost(0).getVM(0); + this.newYorkLocatorVM = getHost(0).getVM(1); + this.londonServerVM = getHost(0).getVM(2); + this.newYorkServerVM = getHost(0).getVM(3); + + this.londonName = "ln"; + this.newYorkName = "ny"; + + this.londonId = 1; + this.newYorkId = 2; + + this.regionName = getTestMethodName() + "_RR"; + + int[] ports = AvailablePortHelper.getRandomAvailableTCPPorts(2); + this.londonLocatorPort = ports[0]; + this.newYorkLocatorPort = ports[1]; + } + + /** + * Use of SECURITY_CLIENT_AUTHENTICATOR should result in the servers performing authentication + * during HandShake of Gateway Sender/Receiver connecting. + */ + @Test + public void gatewayHandShakeShouldAuthenticate() { + this.londonLocatorVM.invoke("start London locator", () -> { + getSystem( + createLocatorConfig(this.londonId, this.londonLocatorPort, this.newYorkLocatorPort)); + }); + + this.newYorkLocatorVM.invoke("start New York locator", () -> { + getSystem( + createLocatorConfig(this.newYorkId, this.newYorkLocatorPort, this.londonLocatorPort)); + }); + + this.londonServerVM.invoke("create London server", () -> { + assertThat(invokeAuthenticateCount.get()).isEqualTo(0); + startServer(this.londonId, this.londonLocatorPort, this.newYorkId, this.newYorkName); + }); + + this.newYorkServerVM.invoke("create New York server", () -> { + assertThat(invokeAuthenticateCount.get()).isEqualTo(0); + startServer(this.newYorkId, this.newYorkLocatorPort, this.londonId, this.londonName); + }); + + this.londonServerVM.invoke(() -> { + await().atMost(1, MINUTES).until(() -> assertThat(isRunning(sender)).isTrue()); + }); + + this.newYorkServerVM.invoke(() -> { + await().atMost(1, MINUTES).until(() -> assertThat(isRunning(sender)).isTrue()); + }); + + this.newYorkServerVM.invoke(() -> { + Region region = cache.getRegion(Region.SEPARATOR + this.regionName); + assertThat(region).isNotNull(); + assertThat(region.isEmpty()).isTrue(); + }); + + this.londonServerVM.invoke(() -> { + Region region = cache.getRegion(Region.SEPARATOR + this.regionName); + region.put(0, 0); + }); + + this.newYorkServerVM.invoke(() -> { + Region region = cache.getRegion(Region.SEPARATOR + this.regionName); + assertThat(region).isNotNull(); + waitAtMost(1, MINUTES).until(() -> assertThat(region.isEmpty()).isFalse()); + }); + + this.newYorkLocatorVM.invoke(() -> { + assertThat(invokeAuthenticateCount.get()).isEqualTo(0); + }); + this.londonLocatorVM.invoke(() -> { + assertThat(invokeAuthenticateCount.get()).isEqualTo(0); + }); + this.newYorkServerVM.invoke(() -> { + assertThat(invokeAuthenticateCount.get()).isGreaterThanOrEqualTo(1); + }); + this.londonServerVM.invoke(() -> { + assertThat(invokeAuthenticateCount.get()).isGreaterThanOrEqualTo(1); + }); + } + + private boolean isRunning(GatewaySender sender) { + return sender != null && sender.isRunning(); + } + + private Properties createLocatorConfig(int systemId, int locatorPort, int remoteLocatorPort) { + Properties config = getDistributedSystemProperties(); + config.setProperty(MCAST_PORT, "0"); + config.setProperty(DISTRIBUTED_SYSTEM_ID, String.valueOf(systemId)); + config.setProperty(LOCATORS, "localhost[" + locatorPort + ']'); + config.setProperty(REMOTE_LOCATORS, "localhost[" + remoteLocatorPort + ']'); + config.setProperty(START_LOCATOR, + "localhost[" + locatorPort + "],server=true,peer=true,hostname-for-clients=localhost"); + config.setProperty(SECURITY_PEER_AUTH_INIT, TestPeerAuthInitialize.class.getName() + ".create"); + config.setProperty(SECURITY_PEER_AUTHENTICATOR, + TestPeerAuthenticator.class.getName() + ".create"); + config.setProperty(USER_NAME, "user"); + config.setProperty(PASSWORD, "user"); + return config; + } + + private Properties createServerConfig(int locatorPort) { + Properties config = new Properties(); + config.setProperty(MCAST_PORT, "0"); + config.setProperty(LOCATORS, "localhost[" + locatorPort + ']'); + config.setProperty(SECURITY_PEER_AUTH_INIT, TestPeerAuthInitialize.class.getName() + ".create"); + config.setProperty(SECURITY_PEER_AUTHENTICATOR, + TestPeerAuthenticator.class.getName() + ".create"); + config.setProperty(SECURITY_CLIENT_AUTHENTICATOR, + TestClientOrReceiverAuthenticator.class.getName() + ".create"); + config.setProperty(USER_NAME, "user"); + config.setProperty(PASSWORD, "user"); + return config; + } + + private void startServer(int systemId, int locatorPort, int remoteSystemId, String remoteName) + throws IOException { + DistributedSystem system = getSystem(createServerConfig(locatorPort)); + cache = CacheFactory.create(system); + + String uniqueName = "server-" + systemId; + File[] dirs = new File[] {this.temporaryFolder.newFolder(uniqueName)}; + + GatewaySenderFactory senderFactory = createGatewaySenderFactory(dirs, uniqueName); + sender = senderFactory.create(remoteName, remoteSystemId); + sender.start(); + + GatewayReceiverFactory receiverFactory = createGatewayReceiverFactory(); + GatewayReceiver receiver = receiverFactory.create(); + receiver.start(); + + AttributesFactory attributesFactory = new AttributesFactory(); + attributesFactory.addGatewaySenderId(remoteName); + attributesFactory.setDataPolicy(DataPolicy.REPLICATE); + attributesFactory.setScope(Scope.DISTRIBUTED_ACK); + cache.createRegionFactory(attributesFactory.create()).create(this.regionName); + } + + private GatewayReceiverFactory createGatewayReceiverFactory() { + GatewayReceiverFactory receiverFactory = cache.createGatewayReceiverFactory(); + + int port = AvailablePortHelper.getRandomAvailableTCPPort(); + + receiverFactory.setStartPort(port); + receiverFactory.setEndPort(port); + receiverFactory.setManualStart(true); + return receiverFactory; + } + + private GatewaySenderFactory createGatewaySenderFactory(File[] dirs, String diskStoreName) { + InternalGatewaySenderFactory senderFactory = + (InternalGatewaySenderFactory) cache.createGatewaySenderFactory(); + + senderFactory.setMaximumQueueMemory(100); + senderFactory.setBatchSize(10); + senderFactory.setBatchConflationEnabled(false); + senderFactory.setManualStart(true); + senderFactory.setDispatcherThreads(1); + senderFactory.setOrderPolicy(GatewaySender.DEFAULT_ORDER_POLICY); + + DiskStoreFactory dsf = cache.createDiskStoreFactory(); + DiskStore store = dsf.setDiskDirs(dirs).create(diskStoreName); + senderFactory.setDiskStoreName(store.getName()); + + return senderFactory; + } + + static class TestPrincipal implements Principal, Serializable { + + private final String userName; + private final UUID uuid; + + TestPrincipal(String userName) { + this.userName = userName; + this.uuid = UUID.randomUUID(); + } + + @Override + public String getName() { + return this.userName; + } + + public UUID getUUID() { + return this.uuid; + } + + @Override + public String toString() { + return this.userName + "->" + this.uuid; + } + } + + public static class TestPeerAuthenticator implements Authenticator { + + public static Authenticator create() { + return new TestPeerAuthenticator(); + } + + @Override + public void init(Properties securityProps, LogWriter systemLogger, LogWriter securityLogger) + throws AuthenticationFailedException { + // nothing + } + + @Override + public Principal authenticate(Properties props, DistributedMember member) + throws AuthenticationFailedException { + System.out + .println(Thread.currentThread().getName() + ": TestPeerAuthenticator authenticating " + + member + " at " + System.currentTimeMillis()); + + // Get the user name and password + String userName = props.getProperty(USER_NAME); + String password = props.getProperty(PASSWORD); + + // If they are not equal, throw an exception + if (!userName.equals(password)) { + String msg = "Invalid user name and password combination supplied for user " + userName; + throw new AuthenticationFailedException(msg); + } + return new TestPrincipal(userName); + } + + @Override + public void close() { + // nothing + } + } + + public static class TestPeerAuthInitialize implements AuthInitialize { + + public static AuthInitialize create() { + return new TestPeerAuthInitialize(); + } + + @Override + public void init(LogWriter systemLogger, LogWriter securityLogger) + throws AuthenticationFailedException { + // nothing + } + + @Override + public Properties getCredentials(Properties securityProps, DistributedMember server, + boolean isPeer) throws AuthenticationFailedException { + String userName = securityProps.getProperty(USER_NAME); + if (userName == null) { + throw new AuthenticationFailedException( + "TestPeerAuthInitialize: The user name property [" + USER_NAME + "] not set"); + } + + Properties newProps = new Properties(); + newProps.setProperty(USER_NAME, userName); + + String passwd = securityProps.getProperty(PASSWORD); + if (passwd == null) { + throw new AuthenticationFailedException( + "TestPeerAuthInitialize: The password property [" + PASSWORD + "] not set"); + } + newProps.setProperty(PASSWORD, passwd); + + System.out.println(Thread.currentThread().getName() + + ": TestPeerAuthInitialize providing credentials for " + (isPeer ? "peer " : "client ") + + server + ": " + newProps + " at " + System.currentTimeMillis()); + + return newProps; + } + + @Override + public void close() { + // nothing + } + } + + public static class TestClientOrReceiverAuthenticator implements Authenticator { + + public static Authenticator create() { + return new TestClientOrReceiverAuthenticator(); + } + + @Override + public void init(Properties securityProps, LogWriter systemLogger, LogWriter securityLogger) + throws AuthenticationFailedException { + // nothing + } + + @Override + public Principal authenticate(Properties props, DistributedMember member) + throws AuthenticationFailedException { + invokeAuthenticateCount.incrementAndGet(); + + System.out.println( + Thread.currentThread().getName() + ": TestClientOrReceiverAuthenticator authenticating " + + member + " at " + System.currentTimeMillis()); + + // Get the user name and password + String userName = props.getProperty(USER_NAME); + String password = props.getProperty(PASSWORD); + + // If they are not equal, throw an exception + if (!userName.equals(password)) { + String msg = "Invalid user name and password combination supplied for user " + userName; + throw new AuthenticationFailedException(msg); + } + return new TestPrincipal(userName); + } + + @Override + public void close() { + // nothing + } + } + +}