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;
+  }
+}