blob: 12ba76176c3c7d0916b8117b82eaeafeaea1f359 [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.cache.wan;
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.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
import static org.apache.geode.test.dunit.VM.getVM;
import static org.assertj.core.api.Assertions.assertThat;
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.DiskStore;
import org.apache.geode.cache.DiskStoreFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.RegionShortcut;
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.security.AuthInitialize;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.Authenticator;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.rules.CacheRule;
import org.apache.geode.test.dunit.rules.DistributedRule;
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;
/**
* Gateway authentication should not throw NullPointerException.
*
* <p>
* GEODE-3117: "Gateway authentication throws NullPointerException"
*/
@Category({SecurityTest.class, WanTest.class})
public class GatewayLegacyAuthenticationRegressionTest implements Serializable {
private static final String REGION_NAME = "TheRegion";
private static final String USER_NAME = "security-username";
private static final String PASSWORD = "security-password";
private static final AtomicInteger AUTHENTICATE_COUNT = new AtomicInteger();
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 int londonReceiverPort;
private int newYorkReceiverPort;
@Rule
public DistributedRule distributedRule = new DistributedRule();
@Rule
public CacheRule cacheRule = new CacheRule();
@Rule
public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
@Before
public void before() {
AUTHENTICATE_COUNT.set(0);
londonLocatorVM = getVM(0);
newYorkLocatorVM = getVM(1);
londonServerVM = getVM(2);
newYorkServerVM = getVM(3);
londonName = "ln";
newYorkName = "ny";
londonId = 1;
newYorkId = 2;
int[] ports = getRandomAvailableTCPPorts(4);
londonLocatorPort = ports[0];
newYorkLocatorPort = ports[1];
londonReceiverPort = ports[2];
newYorkReceiverPort = ports[3];
}
/**
* Use of SECURITY_CLIENT_AUTHENTICATOR should result in the servers performing authentication
* during HandShake of Gateway Sender/Receiver connecting.
*/
@Test
public void gatewayHandShakeShouldAuthenticate() {
londonLocatorVM.invoke("start London locator", () -> {
Properties config = createLocatorConfig(londonId, londonLocatorPort, newYorkLocatorPort);
cacheRule.createCache(config);
});
newYorkLocatorVM.invoke("start New York locator", () -> {
Properties config = createLocatorConfig(newYorkId, newYorkLocatorPort, londonLocatorPort);
cacheRule.createCache(config);
});
londonServerVM.invoke("create London server", () -> {
assertThat(AUTHENTICATE_COUNT.get()).isEqualTo(0);
startServer(londonId, londonLocatorPort, newYorkId, newYorkName, londonReceiverPort);
});
newYorkServerVM.invoke("create New York server", () -> {
assertThat(AUTHENTICATE_COUNT.get()).isEqualTo(0);
startServer(newYorkId, newYorkLocatorPort, londonId, londonName, newYorkReceiverPort);
});
londonServerVM.invoke(() -> {
GatewaySender sender = cacheRule.getCache().getGatewaySender(newYorkName);
await().untilAsserted(() -> assertThat(isRunning(sender)).isTrue());
});
newYorkServerVM.invoke(() -> {
GatewaySender sender = cacheRule.getCache().getGatewaySender(londonName);
await().untilAsserted(() -> assertThat(isRunning(sender)).isTrue());
});
newYorkServerVM.invoke(() -> {
Region<Integer, Integer> region = cacheRule.getCache().getRegion(REGION_NAME);
assertThat(region).isNotNull();
assertThat(region.isEmpty()).isTrue();
});
londonServerVM.invoke(() -> {
Region<Integer, Integer> region = cacheRule.getCache().getRegion(REGION_NAME);
region.put(0, 0);
});
newYorkServerVM.invoke(() -> {
Region<Integer, Integer> region = cacheRule.getCache().getRegion(REGION_NAME);
assertThat(region).isNotNull();
await()
.untilAsserted(() -> assertThat(region.isEmpty()).isFalse());
});
newYorkLocatorVM.invoke(() -> {
assertThat(AUTHENTICATE_COUNT.get()).isEqualTo(0);
});
londonLocatorVM.invoke(() -> {
assertThat(AUTHENTICATE_COUNT.get()).isEqualTo(0);
});
newYorkServerVM.invoke(() -> {
assertThat(AUTHENTICATE_COUNT.get()).isGreaterThanOrEqualTo(1);
});
londonServerVM.invoke(() -> {
assertThat(AUTHENTICATE_COUNT.get()).isGreaterThanOrEqualTo(1);
});
}
private boolean isRunning(GatewaySender sender) {
return sender != null && sender.isRunning();
}
private Properties createLocatorConfig(int systemId, int locatorPort, int remoteLocatorPort) {
Properties config = new Properties();
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,
int receiverPort) throws IOException {
cacheRule.createCache(createServerConfig(locatorPort));
String uniqueName = "server-" + systemId;
File[] dirs = new File[] {temporaryFolder.newFolder(uniqueName)};
GatewaySenderFactory senderFactory = createGatewaySenderFactory(dirs, uniqueName);
GatewaySender sender = senderFactory.create(remoteName, remoteSystemId);
sender.start();
GatewayReceiverFactory receiverFactory = createGatewayReceiverFactory(receiverPort);
GatewayReceiver receiver = receiverFactory.create();
receiver.start();
RegionFactory<Integer, Integer> regionFactory =
cacheRule.getCache().createRegionFactory(RegionShortcut.REPLICATE);
regionFactory.addGatewaySenderId(remoteName);
regionFactory.create(REGION_NAME);
}
private GatewayReceiverFactory createGatewayReceiverFactory(int receiverPort) {
GatewayReceiverFactory receiverFactory = cacheRule.getCache().createGatewayReceiverFactory();
receiverFactory.setStartPort(receiverPort);
receiverFactory.setEndPort(receiverPort);
receiverFactory.setManualStart(true);
return receiverFactory;
}
private GatewaySenderFactory createGatewaySenderFactory(File[] dirs, String diskStoreName) {
InternalGatewaySenderFactory senderFactory =
(InternalGatewaySenderFactory) cacheRule.getCache().createGatewaySenderFactory();
senderFactory.setMaximumQueueMemory(100);
senderFactory.setBatchSize(10);
senderFactory.setBatchConflationEnabled(false);
senderFactory.setManualStart(true);
senderFactory.setDispatcherThreads(1);
senderFactory.setOrderPolicy(GatewaySender.DEFAULT_ORDER_POLICY);
DiskStoreFactory dsf = cacheRule.getCache().createDiskStoreFactory();
DiskStore store = dsf.setDiskDirs(dirs).create(diskStoreName);
senderFactory.setDiskStoreName(store.getName());
return senderFactory;
}
private static class TestPrincipal implements Principal, Serializable {
private final String userName;
private final UUID uuid;
TestPrincipal(String userName) {
this.userName = userName;
uuid = UUID.randomUUID();
}
@Override
public String getName() {
return userName;
}
@Override
public String toString() {
return userName + "->" + 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);
}
}
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;
}
}
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 {
AUTHENTICATE_COUNT.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);
}
}
}