You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by mm...@apache.org on 2021/10/06 19:47:14 UTC
[geode-native] branch develop updated: GEODE-9631: New tests for
reauthentication (#876)
This is an automated email from the ASF dual-hosted git repository.
mmartell pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode-native.git
The following commit(s) were added to refs/heads/develop by this push:
new c84e107 GEODE-9631: New tests for reauthentication (#876)
c84e107 is described below
commit c84e10769105aad8d39337a4c3ba70ce5ad6ab87
Author: Michael Martell <mm...@pivotal.io>
AuthorDate: Wed Oct 6 12:47:06 2021 -0700
GEODE-9631: New tests for reauthentication (#876)
* New tests for reauthentication and durability
* Add new SimulatedExpirationSecurityManager for reauthentication testing
---
cppcache/integration/test/AuthInitializeTest.cpp | 69 ++++++++
.../integration/test/CqPlusAuthInitializeTest.cpp | 188 +++++++++++++++++++++
.../SimulatedExpirationSecurityManager.java | 77 +++++++++
3 files changed, 334 insertions(+)
diff --git a/cppcache/integration/test/AuthInitializeTest.cpp b/cppcache/integration/test/AuthInitializeTest.cpp
index 2fcefff..b1c18d3 100644
--- a/cppcache/integration/test/AuthInitializeTest.cpp
+++ b/cppcache/integration/test/AuthInitializeTest.cpp
@@ -65,6 +65,7 @@ using apache::geode::client::RegionShortcut;
using std::chrono::minutes;
const int32_t CQ_PLUS_AUTH_TEST_REGION_ENTRY_COUNT = 100000;
+const int32_t CQ_REAUTH_TEST_REGION_ENTRY_COUNT = 5000;
Cache createCache(std::shared_ptr<SimpleAuthInitialize> auth) {
auto cache = CacheFactory()
@@ -221,3 +222,71 @@ TEST(AuthInitializeTest, badCredentialsWithSubscriptionEnabled) {
ASSERT_GT(authInitialize->getGetCredentialsCallCount(), 0);
}
+
+TEST(AuthInitializeTest, verifyReAuthAfterCredsExpire) {
+ // Start a cluster with user expiration support
+
+ Cluster cluster(
+ Name(std::string(::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->test_suite_name()) +
+ "/" +
+ ::testing::UnitTest::GetInstance()->current_test_info()->name()),
+ Classpath{getFrameworkString(FrameworkVariable::JavaObjectJarPath)},
+ SecurityManager{"javaobject.SimulatedExpirationSecurityManager"},
+ User{"root"}, Password{"root-password"}, LocatorCount{1}, ServerCount{1});
+
+ cluster.start();
+
+ cluster.getGfsh()
+ .create()
+ .region()
+ .withName("region")
+ .withType("PARTITION")
+ .execute();
+
+ // Use a non-root user for reAuthentication testing.
+
+ auto authInitialize =
+ std::make_shared<SimpleAuthInitialize>("user", "user-password");
+
+ auto cache = createCache(authInitialize);
+ auto pool = createPool(cluster, cache, false);
+ auto region = setupRegion(cache, pool);
+
+ int numSuccessfulOps = 0;
+ int numFailedOps = 0;
+ std::string key;
+ std::string value;
+ for (int i = 0; i < CQ_REAUTH_TEST_REGION_ENTRY_COUNT; i++) {
+ try {
+ key = "foo" + std::to_string(i);
+ value = "bar" + std::to_string(i);
+ region->put(key, value);
+
+ auto retrievedValue = region->get(key);
+ auto retrivedValueAsString =
+ std::dynamic_pointer_cast<CacheableString>(retrievedValue)->value();
+ ASSERT_EQ(retrivedValueAsString, value);
+ numSuccessfulOps++;
+ } catch (const Exception& ex) {
+ std::cout << "Caught unexpected exception: " << ex.what() << std::endl;
+ numFailedOps++;
+ }
+ }
+
+ ASSERT_EQ(numSuccessfulOps, CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ ASSERT_EQ(numFailedOps, 0);
+
+ // SimulatedExpirationSecurityManager is set to throw
+ // AuthenticationExpiredException for 1% of operations.
+ // If the random number generator were perfect we'd expect reauth
+ // to happen for 1% of the operations. Back it off to .5% since
+ // it's not perfect.
+ //
+ // Number of operations is 2 * CQ_REAUTH_TEST_REGION_ENTRY_COUNT
+ // since doing put and get.
+
+ EXPECT_GT(authInitialize->getGetCredentialsCallCount(),
+ 2 * CQ_REAUTH_TEST_REGION_ENTRY_COUNT * .005);
+}
diff --git a/cppcache/integration/test/CqPlusAuthInitializeTest.cpp b/cppcache/integration/test/CqPlusAuthInitializeTest.cpp
index 0811f5a..da8c8d9 100644
--- a/cppcache/integration/test/CqPlusAuthInitializeTest.cpp
+++ b/cppcache/integration/test/CqPlusAuthInitializeTest.cpp
@@ -61,6 +61,7 @@ using apache::geode::client::RegionShortcut;
using std::chrono::minutes;
const int32_t CQ_PLUS_AUTH_TEST_REGION_ENTRY_COUNT = 50000;
+const int32_t CQ_REAUTH_TEST_REGION_ENTRY_COUNT = 5000;
Cache createCache(std::shared_ptr<SimpleAuthInitialize> auth) {
auto cache = CacheFactory()
@@ -72,6 +73,19 @@ Cache createCache(std::shared_ptr<SimpleAuthInitialize> auth) {
return cache;
}
+Cache createDurableCache(std::shared_ptr<SimpleAuthInitialize> auth) {
+ auto pp = Properties::create();
+ pp->insert("durable-client-id", "DurableClient-1111");
+ pp->insert("durable-timeout", std::chrono::seconds(600));
+ auto cache = CacheFactory(pp)
+ .set("log-level", "none")
+ .set("statistic-sampling-enabled", "false")
+ .setAuthInitialize(auth)
+ .create();
+
+ return cache;
+}
+
std::shared_ptr<Pool> createPool(Cluster& cluster, Cache& cache,
bool subscriptionEnabled) {
auto poolFactory = cache.getPoolManager().createFactory().setIdleTimeout(
@@ -202,4 +216,178 @@ TEST(CqPlusAuthInitializeTest, putInALoopWhileSubscribedAndAuthenticated) {
EXPECT_GT(authInitialize->getGetCredentialsCallCount(), 0);
}
+TEST(CqPlusAuthInitializeTest, reAuthenticateWithDurable) {
+ // Start a cluster with user expiration support
+
+ Cluster cluster(
+ Name(std::string(::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->test_suite_name()) +
+ "/" +
+ ::testing::UnitTest::GetInstance()->current_test_info()->name()),
+ Classpath{getFrameworkString(FrameworkVariable::JavaObjectJarPath)},
+ SecurityManager{"javaobject.SimulatedExpirationSecurityManager"},
+ User{"root"}, Password{"root-password"}, LocatorCount{1}, ServerCount{1});
+
+ cluster.start();
+
+ cluster.getGfsh()
+ .create()
+ .region()
+ .withName("region")
+ .withType("PARTITION")
+ .execute();
+
+ // Use a non-root user for reAuthentication testing.
+
+ auto authInitialize =
+ std::make_shared<SimpleAuthInitialize>("user", "user-password");
+ auto cache = createDurableCache(authInitialize);
+ auto pool = createPool(cluster, cache, true);
+ auto region = setupRegion(cache, pool);
+
+ try {
+ region->put("foo", "bar");
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "In initial region put" << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ auto queryService = cache.getQueryService();
+
+ auto createLatch =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto updateLatch =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto destroyLatch =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto testListener = std::make_shared<SimpleCqListener>(
+ createLatch, updateLatch, destroyLatch);
+
+ CqAttributesFactory attributesFactory;
+ attributesFactory.addCqListener(testListener);
+ auto cqAttributes = attributesFactory.create();
+
+ auto query = queryService->newCq("SimpleCQ", "SELECT * FROM /region",
+ cqAttributes, true);
+
+ try {
+ query->execute();
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "While executing Cq" << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ int32_t i = 0;
+
+ cache.readyForEvents();
+
+ try {
+ for (i = 0; i < CQ_REAUTH_TEST_REGION_ENTRY_COUNT; i++) {
+ region->put("key" + std::to_string(i), "value" + std::to_string(i));
+ std::this_thread::yield();
+ }
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "In value create loop, i=" << i << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ try {
+ for (i = 0; i < CQ_REAUTH_TEST_REGION_ENTRY_COUNT; i++) {
+ region->put("key" + std::to_string(i), "value" + std::to_string(i + 1));
+ std::this_thread::yield();
+ }
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "In value update loop, i=" << i << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ try {
+ for (i = 0; i < CQ_REAUTH_TEST_REGION_ENTRY_COUNT; i++) {
+ region->destroy("key" + std::to_string(i));
+ std::this_thread::yield();
+ }
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "In value destroy loop, i=" << i << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ createLatch->wait_for(boost::chrono::seconds(90)));
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ updateLatch->wait_for(boost::chrono::seconds(90)));
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ destroyLatch->wait_for(boost::chrono::seconds(90)));
+
+ // SimulatedExpirationSecurityManager is set to throw
+ // AuthenticationExpiredException for 1% of operations.
+ // If the random number generator were perfect we'd expect reauth
+ // to happen for 1% of the operations. Back it off to .5% since
+ // it's not perfect.
+ //
+ // Number of operations is 3 * CQ_REAUTH_TEST_REGION_ENTRY_COUNT
+ // since doing put, update, and destory.
+
+ EXPECT_GT(authInitialize->getGetCredentialsCallCount(),
+ 3 * CQ_REAUTH_TEST_REGION_ENTRY_COUNT * .005);
+
+ // Keep the durable messages so we can test getting them back
+ // on reconnect.
+
+ cache.close(true);
+
+ // Create a new durable cache with the same durable-client-id and
+ // ensure we get all the events that were created.
+
+ auto cacheRecovery = createDurableCache(authInitialize);
+ auto poolRecovery = createPool(cluster, cacheRecovery, true);
+ auto regionRecovery = setupRegion(cacheRecovery, poolRecovery);
+
+ auto queryServiceRecovery = cacheRecovery.getQueryService();
+
+ auto createLatchRecovery =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto updateLatchRecovery =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto destroyLatchRecovery =
+ std::make_shared<boost::latch>(CQ_REAUTH_TEST_REGION_ENTRY_COUNT);
+ auto testListenerRecovery = std::make_shared<SimpleCqListener>(
+ createLatchRecovery, updateLatchRecovery, destroyLatchRecovery);
+
+ CqAttributesFactory attributesFactoryRecovery;
+ attributesFactoryRecovery.addCqListener(testListenerRecovery);
+ auto cqAttributesRecovery = attributesFactoryRecovery.create();
+
+ auto queryRecovery = queryServiceRecovery->newCq(
+ "SimpleCQ", "SELECT * FROM /region", cqAttributesRecovery, true);
+
+ try {
+ queryRecovery->execute();
+ } catch (const Exception& ex) {
+ std::cerr << "Caught exception: " << ex.what() << std::endl;
+ std::cerr << "While executing Cq" << std::endl;
+ std::cerr << "Callstack" << ex.getStackTrace() << std::endl;
+ FAIL();
+ }
+
+ cacheRecovery.readyForEvents();
+
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ createLatchRecovery->wait_for(boost::chrono::seconds(90)));
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ updateLatchRecovery->wait_for(boost::chrono::seconds(90)));
+ EXPECT_EQ(boost::cv_status::no_timeout,
+ destroyLatchRecovery->wait_for(boost::chrono::seconds(90)));
+}
+
} // namespace
diff --git a/tests/javaobject/SimulatedExpirationSecurityManager.java b/tests/javaobject/SimulatedExpirationSecurityManager.java
new file mode 100644
index 0000000..872f59d
--- /dev/null
+++ b/tests/javaobject/SimulatedExpirationSecurityManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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 javaobject;
+
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.geode.management.internal.security.ResourceConstants;
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.AuthenticationExpiredException;
+import org.apache.geode.security.ResourcePermission;
+import org.apache.geode.security.SecurityManager;
+
+import java.lang.management.ManagementFactory;
+import java.util.Random;
+
+import javaobject.UserPasswordAuthInit;
+import javaobject.UsernamePrincipal;
+
+/**
+ This Security manager uses a random number generator to decide which users
+ are authenticated. It is designed to force reauthentication for roughly
+ one percent of the operations. Also, user "root" is always valid to allow
+ executing gfsh commands during test setup.
+*/
+public class SimulatedExpirationSecurityManager implements SecurityManager {
+
+ @Override
+ public Object authenticate(Properties credentials) throws AuthenticationFailedException {
+ String user = credentials.getProperty(ResourceConstants.USER_NAME);
+ String password = credentials.getProperty(ResourceConstants.PASSWORD);
+
+ if (getUserCredentials().containsKey(user) && getUserCredentials().get(user).equals(password)) {
+ return user;
+ }
+ throw new AuthenticationFailedException("Non-authenticated user: " + user);
+ }
+
+ @Override
+ public boolean authorize(Object principal, ResourcePermission permission) throws AuthenticationExpiredException {
+
+ // User "root" is allows authorized, and can be used to run gfsh commands to setup the cluster for testing.
+ if (principal.toString() == "root")
+ return true;
+
+ // Throw AuthenticationExpiredException 1% of the time to allow testing expiration
+ int numUsers = 100;
+ Random rand = new Random();
+ int userNumber = rand.nextInt(numUsers);
+
+ if (userNumber < 1)
+ throw new AuthenticationExpiredException("User authentication expired.");
+ else
+ return true;
+ }
+
+ private HashMap<String, String> getUserCredentials() {
+ HashMap<String, String> userCredentials = new HashMap<String, String>();
+ userCredentials.put("root", "root-password");
+ userCredentials.put("user", "user-password");
+ return userCredentials;
+ }
+}