You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2022/09/07 01:45:38 UTC

[solr] branch branch_9x updated: SOLR-16192: Add ZK credentials injectors support (#857)

This is an automated email from the ASF dual-hosted git repository.

houston pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 47a5ec2ff2c SOLR-16192: Add ZK credentials injectors support (#857)
47a5ec2ff2c is described below

commit 47a5ec2ff2c4077041e41050fa0ec563122ac466
Author: Lamine <10...@users.noreply.github.com>
AuthorDate: Tue Sep 6 19:35:42 2022 -0500

    SOLR-16192: Add ZK credentials injectors support (#857)
    
    (cherry picked from commit 9a40a737b3823bc1e61ad03c9e31b4f4808d78ca)
---
 solr/CHANGES.txt                                   |   4 +-
 solr/bin/solr.in.cmd                               |  16 +-
 solr/bin/solr.in.sh                                |  16 +-
 .../java/org/apache/solr/cloud/ZkController.java   |  36 +-
 .../src/java/org/apache/solr/core/CloudConfig.java |  21 +-
 .../java/org/apache/solr/core/SolrXmlConfig.java   |   3 +
 solr/core/src/test-files/solr/solr-50-all.xml      |   1 +
 .../OutOfBoxZkACLAndCredentialsProvidersTest.java  |   2 +-
 ...OverriddenZkACLAndCredentialsProvidersTest.java | 188 +++----
 .../VMParamsZkACLAndCredentialsProvidersTest.java  |   2 +-
 .../security/hadoop/SaslZkACLProviderTest.java     |   6 +-
 .../security/hadoop/TestZkAclsWithHadoopAuth.java  |   8 +-
 .../src/test-files/solr/solr.xml                   |   1 +
 solr/server/scripts/cloud-scripts/zkcli.bat        |  10 +-
 solr/server/scripts/cloud-scripts/zkcli.sh         |  10 +-
 solr/server/solr/solr.xml                          |   1 +
 .../pages/configuring-solr-xml.adoc                |   3 +-
 .../pages/zookeeper-access-control.adoc            | 452 +++++++++++++---
 .../pages/major-changes-in-solr-9.adoc             |   2 +-
 ...ider.java => DefaultZkCredentialsInjector.java} |   9 +-
 .../common/cloud/DefaultZkCredentialsProvider.java |  16 +-
 .../solr/common/cloud/DigestZkACLProvider.java     | 104 ++++
 .../common/cloud/DigestZkCredentialsProvider.java  |  65 +++
 .../common/cloud/SecurityAwareZkACLProvider.java   |  14 +
 .../org/apache/solr/common/cloud/SolrZkClient.java |  56 +-
 .../VMParamsAllAndReadonlyDigestZkACLProvider.java | 156 ++----
 ...eSetCredentialsDigestZkCredentialsProvider.java |  76 ++-
 .../cloud/VMParamsZkCredentialsInjector.java       | 138 +++++
 .../apache/solr/common/cloud/ZkACLProvider.java    |   7 +
 .../solr/common/cloud/ZkCredentialsInjector.java   |  94 ++++
 .../solr/common/cloud/ZkCredentialsProvider.java   |   4 +-
 ...DigestZkACLAndCredentialsProvidersTestBase.java | 569 +++++++++++++++++++++
 ...ParamsZkACLAndCredentialsProvidersTestBase.java | 429 ----------------
 33 files changed, 1709 insertions(+), 810 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index aa94e63ff91..29093056f23 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -40,6 +40,8 @@ Improvements
 
 * SOLR-16181: Initialize the LogWatcher earlier in CoreContainer#load() (janhoy)
 
+* SOLR-16192: Add ZK credentials injectors support (Lamine Idjeraoui with review by Anshum Gupta, Jason Gerlowski, Mike Drob and Houston Putman)
+
 * SOLR-16225: Upgrade dependencies (Carrot2, HPPC) (Dawid Weiss)
 
 * SOLR-16257: Improve ZkStateReader to avoid race condition between collectionWatches and watchedCollectionStates
@@ -100,7 +102,7 @@ Bug Fixes
 
 * SOLR-16142: Fix Admin UI's spatial parameter generation. (Arsal Jalib, Christine Poerschke)
 
-* SOLR-9661: Fix explanation of select() streaming expression that uses replace() operation  (Ahmet Can Kepenek via Eric Pugh) 
+* SOLR-9661: Fix explanation of select() streaming expression that uses replace() operation  (Ahmet Can Kepenek via Eric Pugh)
 
 * SOLR-16316: Fix debugQuery clicking on the Query UI does not show the debug.explain.structured option. (Shiming Li via Eric Pugh)
 
diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd
index 72c535be07e..76aef1d20c8 100755
--- a/solr/bin/solr.in.cmd
+++ b/solr/bin/solr.in.cmd
@@ -175,12 +175,24 @@ REM set SOLR_AUTH_TYPE=basic
 REM set SOLR_AUTHENTICATION_OPTS=-Dbasicauth=solr:SolrRocks
 
 REM Settings for ZK ACL
-REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider ^
-REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider ^
+REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+REM  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
 REM  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
 REM  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
 REM set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
 
+REM optionally, you can use using a a Java properties file 'zkDigestCredentialsFile'
+REM ...
+REM   -DzkDigestCredentialsFile=/path/to/zkDigestCredentialsFile.properties
+REM ...
+
+REM Use a custom injector to inject ZK credentials into DigestZkACLProvider
+REM -DzkCredentialsInjector expects a class implementing org.apache.solr.common.cloud.ZkCredentialsInjector
+REM  ...
+REM  -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName
+REM ...
+
 REM Jetty GZIP module enabled by default
 REM set SOLR_GZIP_ENABLED=true
 
diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh
index df7f435fde6..8b36f0e491f 100644
--- a/solr/bin/solr.in.sh
+++ b/solr/bin/solr.in.sh
@@ -199,12 +199,24 @@
 #SOLR_AUTHENTICATION_OPTS="-Dbasicauth=solr:SolrRocks"
 
 # Settings for ZK ACL
-#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \
-#  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \
+#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+#  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+#  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
 #  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
 #  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
 #SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS"
 
+# optionally, you can use using a a Java properties file 'zkDigestCredentialsFile'
+#...
+#   -DzkDigestCredentialsFile=/path/to/zkDigestCredentialsFile.properties
+#...
+
+# Use a custom injector to inject ZK credentials into DigestZkACLProvider
+# -DzkCredentialsInjector expects a class implementing org.apache.solr.common.cloud.ZkCredentialsInjector
+# ...
+#   -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName"
+# ...
+
 # Jetty GZIP module enabled by default
 #SOLR_GZIP_ENABLED=true
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 38fee4ab9ee..25bd458bc8e 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -72,10 +72,12 @@ import org.apache.solr.cloud.overseer.SliceMutator;
 import org.apache.solr.common.AlreadyClosedException;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.cloud.BeforeReconnect;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DefaultConnectionStrategy;
 import org.apache.solr.common.cloud.DefaultZkACLProvider;
+import org.apache.solr.common.cloud.DefaultZkCredentialsInjector;
 import org.apache.solr.common.cloud.DefaultZkCredentialsProvider;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.DocCollectionWatcher;
@@ -94,6 +96,7 @@ import org.apache.solr.common.cloud.ZkACLProvider;
 import org.apache.solr.common.cloud.ZkClientConnectionStrategy;
 import org.apache.solr.common.cloud.ZkCmdExecutor;
 import org.apache.solr.common.cloud.ZkCoreNodeProps;
+import org.apache.solr.common.cloud.ZkCredentialsInjector;
 import org.apache.solr.common.cloud.ZkCredentialsProvider;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
@@ -348,22 +351,29 @@ public class ZkController implements Closeable {
     ZkClientConnectionStrategy strat =
         ZkClientConnectionStrategy.forName(connectionStrategy, new DefaultConnectionStrategy());
 
+    String zkCredentialsInjectorClass = cloudConfig.getZkCredentialsInjectorClass();
+    ZkCredentialsInjector zkCredentialsInjector =
+        StringUtils.isEmpty(zkCredentialsInjectorClass)
+            ? new DefaultZkCredentialsInjector()
+            : cc.getResourceLoader()
+                .newInstance(zkCredentialsInjectorClass, ZkCredentialsInjector.class);
+
     String zkACLProviderClass = cloudConfig.getZkACLProviderClass();
-    ZkACLProvider zkACLProvider = null;
-    if (zkACLProviderClass != null && zkACLProviderClass.trim().length() > 0) {
-      zkACLProvider = cc.getResourceLoader().newInstance(zkACLProviderClass, ZkACLProvider.class);
-    } else {
-      zkACLProvider = new DefaultZkACLProvider();
-    }
+    ZkACLProvider zkACLProvider =
+        StringUtils.isEmpty(zkACLProviderClass)
+            ? new DefaultZkACLProvider()
+            : cc.getResourceLoader().newInstance(zkACLProviderClass, ZkACLProvider.class);
+    zkACLProvider.setZkCredentialsInjector(zkCredentialsInjector);
 
     String zkCredentialsProviderClass = cloudConfig.getZkCredentialsProviderClass();
-    if (zkCredentialsProviderClass != null && zkCredentialsProviderClass.trim().length() > 0) {
-      strat.setZkCredentialsToAddAutomatically(
-          cc.getResourceLoader()
-              .newInstance(zkCredentialsProviderClass, ZkCredentialsProvider.class));
-    } else {
-      strat.setZkCredentialsToAddAutomatically(new DefaultZkCredentialsProvider());
-    }
+    ZkCredentialsProvider zkCredentialsProvider =
+        StringUtils.isEmpty(zkCredentialsProviderClass)
+            ? new DefaultZkCredentialsProvider()
+            : cc.getResourceLoader()
+                .newInstance(zkCredentialsProviderClass, ZkCredentialsProvider.class);
+
+    zkCredentialsProvider.setZkCredentialsInjector(zkCredentialsInjector);
+    strat.setZkCredentialsToAddAutomatically(zkCredentialsProvider);
     addOnReconnectListener(getConfigDirListener());
 
     zkClient =
diff --git a/solr/core/src/java/org/apache/solr/core/CloudConfig.java b/solr/core/src/java/org/apache/solr/core/CloudConfig.java
index 4a5285e14bf..f7a7d605980 100644
--- a/solr/core/src/java/org/apache/solr/core/CloudConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/CloudConfig.java
@@ -40,6 +40,8 @@ public class CloudConfig {
 
   private final String zkACLProviderClass;
 
+  private final String zkCredentialsInjectorClass;
+
   private final int createCollectionWaitTimeTillActive;
 
   private final boolean createCollectionCheckLeaderActive;
@@ -63,6 +65,7 @@ public class CloudConfig {
       int leaderConflictResolveWait,
       String zkCredentialsProviderClass,
       String zkACLProviderClass,
+      String zkCredentialsInjectorClass,
       int createCollectionWaitTimeTillActive,
       boolean createCollectionCheckLeaderActive,
       String pkiHandlerPrivateKeyPath,
@@ -79,6 +82,7 @@ public class CloudConfig {
     this.leaderConflictResolveWait = leaderConflictResolveWait;
     this.zkCredentialsProviderClass = zkCredentialsProviderClass;
     this.zkACLProviderClass = zkACLProviderClass;
+    this.zkCredentialsInjectorClass = zkCredentialsInjectorClass;
     this.createCollectionWaitTimeTillActive = createCollectionWaitTimeTillActive;
     this.createCollectionCheckLeaderActive = createCollectionCheckLeaderActive;
     this.pkiHandlerPrivateKeyPath = pkiHandlerPrivateKeyPath;
@@ -129,6 +133,10 @@ public class CloudConfig {
     return zkACLProviderClass;
   }
 
+  public String getZkCredentialsInjectorClass() {
+    return zkCredentialsInjectorClass;
+  }
+
   public int getLeaderVoteWait() {
     return leaderVoteWait;
   }
@@ -183,6 +191,7 @@ public class CloudConfig {
     private int leaderConflictResolveWait = DEFAULT_LEADER_CONFLICT_RESOLVE_WAIT;
     private String zkCredentialsProviderClass;
     private String zkACLProviderClass;
+    private String zkCredentialsInjectorClass;
     private int createCollectionWaitTimeTillActive = DEFAULT_CREATE_COLLECTION_ACTIVE_WAIT;
     private boolean createCollectionCheckLeaderActive =
         DEFAULT_CREATE_COLLECTION_CHECK_LEADER_ACTIVE;
@@ -227,12 +236,19 @@ public class CloudConfig {
     }
 
     public CloudConfigBuilder setZkCredentialsProviderClass(String zkCredentialsProviderClass) {
-      this.zkCredentialsProviderClass = zkCredentialsProviderClass;
+      this.zkCredentialsProviderClass =
+          zkCredentialsProviderClass != null ? zkCredentialsProviderClass.trim() : null;
       return this;
     }
 
     public CloudConfigBuilder setZkACLProviderClass(String zkACLProviderClass) {
-      this.zkACLProviderClass = zkACLProviderClass;
+      this.zkACLProviderClass = zkACLProviderClass != null ? zkACLProviderClass.trim() : null;
+      return this;
+    }
+
+    public CloudConfigBuilder setZkCredentialsInjectorClass(String zkCredentialsInjectorClass) {
+      this.zkCredentialsInjectorClass =
+          zkCredentialsInjectorClass != null ? zkCredentialsInjectorClass.trim() : null;
       return this;
     }
 
@@ -282,6 +298,7 @@ public class CloudConfig {
           leaderConflictResolveWait,
           zkCredentialsProviderClass,
           zkACLProviderClass,
+          zkCredentialsInjectorClass,
           createCollectionWaitTimeTillActive,
           createCollectionCheckLeaderActive,
           pkiHandlerPrivateKeyPath,
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 93a9a662475..647c9c71019 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -538,6 +538,9 @@ public class SolrXmlConfig {
         case "zkCredentialsProvider":
           builder.setZkCredentialsProviderClass(value);
           break;
+        case "zkCredentialsInjector":
+          builder.setZkCredentialsInjectorClass(value);
+          break;
         case "createCollectionWaitTimeTillActive":
           builder.setCreateCollectionWaitTimeTillActive(parseInt(name, value));
           break;
diff --git a/solr/core/src/test-files/solr/solr-50-all.xml b/solr/core/src/test-files/solr/solr-50-all.xml
index b7f6f260075..6849ed85d44 100644
--- a/solr/core/src/test-files/solr/solr-50-all.xml
+++ b/solr/core/src/test-files/solr/solr-50-all.xml
@@ -49,6 +49,7 @@
     <str name="zkHost">testZkHost</str>
     <str name="zkACLProvider">DefaultZkACLProvider</str>
     <str name="zkCredentialsProvider">DefaultZkCredentialsProvider</str>
+    <str name="zkCredentialsInjector">DefaultZkCredentialsInjector</str>
   </solrcloud>
 
   <logging>
diff --git a/solr/core/src/test/org/apache/solr/cloud/OutOfBoxZkACLAndCredentialsProvidersTest.java b/solr/core/src/test/org/apache/solr/cloud/OutOfBoxZkACLAndCredentialsProvidersTest.java
index be182cb3fb7..59739a19066 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OutOfBoxZkACLAndCredentialsProvidersTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OutOfBoxZkACLAndCredentialsProvidersTest.java
@@ -109,7 +109,7 @@ public class OutOfBoxZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
   public void testOutOfBoxSolrZkClient() throws Exception {
     SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, true, true, true, true, true, true, true, true);
     } finally {
       zkClient.close();
diff --git a/solr/core/src/test/org/apache/solr/cloud/OverriddenZkACLAndCredentialsProvidersTest.java b/solr/core/src/test/org/apache/solr/cloud/OverriddenZkACLAndCredentialsProvidersTest.java
index 143524ff575..9ada4f014b2 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OverriddenZkACLAndCredentialsProvidersTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OverriddenZkACLAndCredentialsProvidersTest.java
@@ -16,24 +16,28 @@
  */
 package org.apache.solr.cloud;
 
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.ZkCredentialsInjector.ZkCredential.Perms;
+
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.StringUtils;
-import org.apache.solr.common.cloud.DefaultZkCredentialsProvider;
+import org.apache.solr.common.cloud.DigestZkACLProvider;
+import org.apache.solr.common.cloud.DigestZkCredentialsProvider;
 import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider;
-import org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider;
+import org.apache.solr.common.cloud.VMParamsZkCredentialsInjector;
 import org.apache.solr.common.cloud.ZkACLProvider;
+import org.apache.solr.common.cloud.ZkCredentialsInjector;
 import org.apache.solr.common.cloud.ZkCredentialsProvider;
 import org.apache.zookeeper.CreateMode;
-import org.apache.zookeeper.data.ACL;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -140,7 +144,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
         new SolrZkClientFactoryUsingCompletelyNewProviders(null, null, null, null)
             .getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, false, false, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -155,7 +159,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
                 "connectAndAllACLUsername", "connectAndAllACLPasswordWrong", null, null)
             .getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, false, false, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -169,7 +173,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
                 "connectAndAllACLUsername", "connectAndAllACLPassword", null, null)
             .getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, true, true, true, true, true, true, true, true);
     } finally {
       zkClient.close();
@@ -184,7 +188,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
                 "readonlyACLUsername", "readonlyACLPassword", null, null)
             .getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -201,7 +205,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
         new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(
             zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, false, false, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -218,7 +222,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
         new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(
             zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, false, false, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -235,7 +239,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
         new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(
             zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, true, true, true, true, true, true, true, true);
     } finally {
       zkClient.close();
@@ -252,7 +256,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
         new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(
             zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      VMParamsZkACLAndCredentialsProvidersTest.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
@@ -265,6 +269,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
     final String digestPassword;
     final String digestReadonlyUsername;
     final String digestReadonlyPassword;
+    private final ZkCredentialsInjector zkCredentialsInjector;
 
     public SolrZkClientFactoryUsingCompletelyNewProviders(
         final String digestUsername,
@@ -275,6 +280,18 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
       this.digestPassword = digestPassword;
       this.digestReadonlyUsername = digestReadonlyUsername;
       this.digestReadonlyPassword = digestReadonlyPassword;
+      zkCredentialsInjector =
+          () -> {
+            List<ZkCredentialsInjector.ZkCredential> zkCredentials = new ArrayList<>(2);
+            ZkCredentialsInjector.ZkCredential allCreds =
+                new ZkCredentialsInjector.ZkCredential(digestUsername, digestPassword, Perms.ALL);
+            ZkCredentialsInjector.ZkCredential readCreds =
+                new ZkCredentialsInjector.ZkCredential(
+                    digestReadonlyUsername, digestReadonlyPassword, Perms.READ);
+            zkCredentials.add(allCreds);
+            zkCredentials.add(readCreds);
+            return zkCredentials;
+          };
     }
 
     public SolrZkClient getSolrZkClient(String zkServerAddress, int zkClientTimeout) {
@@ -282,47 +299,12 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
 
         @Override
         protected ZkCredentialsProvider createZkCredentialsToAddAutomatically() {
-          return new DefaultZkCredentialsProvider() {
-            @Override
-            protected Collection<ZkCredentials> createCredentials() {
-              List<ZkCredentials> result = new ArrayList<>();
-              if (!StringUtils.isEmpty(digestUsername) && !StringUtils.isEmpty(digestPassword)) {
-                result.add(
-                    new ZkCredentials(
-                        "digest",
-                        (digestUsername + ":" + digestPassword).getBytes(StandardCharsets.UTF_8)));
-              }
-              return result;
-            }
-          };
+          return new DigestZkCredentialsProvider(zkCredentialsInjector);
         }
 
         @Override
         public ZkACLProvider createZkACLProvider() {
-          return new VMParamsAllAndReadonlyDigestZkACLProvider() {
-            @Override
-            protected List<ACL> createNonSecurityACLsToAdd() {
-              return createACLsToAdd(
-                  true,
-                  digestUsername,
-                  digestPassword,
-                  digestReadonlyUsername,
-                  digestReadonlyPassword);
-            }
-
-            /**
-             * @return Set of ACLs to return security-related znodes
-             */
-            @Override
-            protected List<ACL> createSecurityACLsToAdd() {
-              return createACLsToAdd(
-                  false,
-                  digestUsername,
-                  digestPassword,
-                  digestReadonlyUsername,
-                  digestReadonlyPassword);
-            }
-          };
+          return new DigestZkACLProvider(zkCredentialsInjector);
         }
       };
     }
@@ -331,37 +313,31 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
   private static class SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames
       extends SolrZkClient {
 
+    private static final VMParamsZkCredentialsInjector vmParamsZkCredentialsInjector =
+        new VMParamsZkCredentialsInjector(
+            "alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
+            "alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
+            "alternative" + DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
+            "alternative" + DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
+
     public SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(
         String zkServerAddress, int zkClientTimeout) {
       super(zkServerAddress, zkClientTimeout);
     }
 
+    @Override
+    protected ZkCredentialsInjector createZkCredentialsInjector() {
+      return vmParamsZkCredentialsInjector;
+    }
+
     @Override
     protected ZkCredentialsProvider createZkCredentialsToAddAutomatically() {
-      return new VMParamsSingleSetCredentialsDigestZkCredentialsProvider(
-          "alternative"
-              + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                  .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-          "alternative"
-              + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                  .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
+      return new DigestZkCredentialsProvider(vmParamsZkCredentialsInjector);
     }
 
     @Override
     public ZkACLProvider createZkACLProvider() {
-      return new VMParamsAllAndReadonlyDigestZkACLProvider(
-          "alternative"
-              + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                  .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-          "alternative"
-              + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                  .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-          "alternative"
-              + VMParamsAllAndReadonlyDigestZkACLProvider
-                  .DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
-          "alternative"
-              + VMParamsAllAndReadonlyDigestZkACLProvider
-                  .DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
+      return new DigestZkACLProvider(vmParamsZkCredentialsInjector);
     }
   }
 
@@ -373,86 +349,44 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
     clearSecuritySystemProperties();
 
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
+        "alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername");
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPasswordWrong");
+        "alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPasswordWrong");
   }
 
   public void useAllCredentials() {
     clearSecuritySystemProperties();
 
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
+        "alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername");
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPassword");
+        "alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPassword");
   }
 
   public void useReadonlyCredentials() {
     clearSecuritySystemProperties();
 
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "readonlyACLUsername");
+        "alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "readonlyACLUsername");
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "readonlyACLPassword");
+        "alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "readonlyACLPassword");
   }
 
   public void setSecuritySystemProperties() {
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
+        "alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername");
     System.setProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPassword");
+        "alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPassword");
     System.setProperty(
-        "alternative"
-            + VMParamsAllAndReadonlyDigestZkACLProvider
-                .DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
-        "readonlyACLUsername");
+        "alternative" + DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME, "readonlyACLUsername");
     System.setProperty(
-        "alternative"
-            + VMParamsAllAndReadonlyDigestZkACLProvider
-                .DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME,
-        "readonlyACLPassword");
+        "alternative" + DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME, "readonlyACLPassword");
   }
 
   public void clearSecuritySystemProperties() {
-    System.clearProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME);
-    System.clearProperty(
-        "alternative"
-            + VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-                .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
-    System.clearProperty(
-        "alternative"
-            + VMParamsAllAndReadonlyDigestZkACLProvider
-                .DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME);
-    System.clearProperty(
-        "alternative"
-            + VMParamsAllAndReadonlyDigestZkACLProvider
-                .DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
+    System.clearProperty("alternative" + DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME);
+    System.clearProperty("alternative" + DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
+    System.clearProperty("alternative" + DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME);
+    System.clearProperty("alternative" + DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java b/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
index addbe737e29..82b60068d83 100644
--- a/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
@@ -17,4 +17,4 @@
 package org.apache.solr.cloud;
 
 public class VMParamsZkACLAndCredentialsProvidersTest
-    extends AbstractVMParamsZkACLAndCredentialsProvidersTestBase {}
+    extends AbstractDigestZkACLAndCredentialsProvidersTestBase {}
diff --git a/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/SaslZkACLProviderTest.java b/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/SaslZkACLProviderTest.java
index bb656f19297..2d18e84c283 100644
--- a/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/SaslZkACLProviderTest.java
+++ b/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/SaslZkACLProviderTest.java
@@ -27,7 +27,7 @@ import org.apache.lucene.tests.util.QuickPatchThreadsFilter;
 import org.apache.lucene.util.Constants;
 import org.apache.solr.SolrIgnoredThreadsFilter;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.cloud.AbstractVMParamsZkACLAndCredentialsProvidersTestBase;
+import org.apache.solr.cloud.AbstractDigestZkACLAndCredentialsProvidersTestBase;
 import org.apache.solr.cloud.AbstractZkTestCase;
 import org.apache.solr.cloud.ZkTestServer;
 import org.apache.solr.common.cloud.DefaultZkACLProvider;
@@ -155,7 +155,7 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
     SolrZkClient zkClient =
         new SolrZkClientWithACLs(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      AbstractVMParamsZkACLAndCredentialsProvidersTestBase.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, true, true, true, true, true, true, true, true);
     } finally {
       zkClient.close();
@@ -166,7 +166,7 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
     System.setProperty("zookeeper.sasl.client", "false");
     zkClient = new SolrZkClientNoACLs(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
     try {
-      AbstractVMParamsZkACLAndCredentialsProvidersTestBase.doTest(
+      AbstractDigestZkACLAndCredentialsProvidersTestBase.doTest(
           zkClient, true, true, false, false, false, false, false, false, false, false);
     } finally {
       zkClient.close();
diff --git a/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/TestZkAclsWithHadoopAuth.java b/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/TestZkAclsWithHadoopAuth.java
index ae1c1c89e77..aca0f7e59f5 100644
--- a/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/TestZkAclsWithHadoopAuth.java
+++ b/solr/modules/hadoop-auth/src/test/org/apache/solr/security/hadoop/TestZkAclsWithHadoopAuth.java
@@ -16,10 +16,10 @@
  */
 package org.apache.solr.security.hadoop;
 
-import static org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME;
-import static org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME;
-import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
-import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
 
 import java.nio.charset.StandardCharsets;
 import java.security.NoSuchAlgorithmException;
diff --git a/solr/modules/jaegertracer-configurator/src/test-files/solr/solr.xml b/solr/modules/jaegertracer-configurator/src/test-files/solr/solr.xml
index 25e51924b19..8a4c18eed64 100644
--- a/solr/modules/jaegertracer-configurator/src/test-files/solr/solr.xml
+++ b/solr/modules/jaegertracer-configurator/src/test-files/solr/solr.xml
@@ -47,6 +47,7 @@
     <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:340000}</int>
     <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
     <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
+    <str name="zkCredentialsInjector">${zkCredentialsInjector:org.apache.solr.common.cloud.DefaultZkCredentialsInjector}</str>
   </solrcloud>
   <metrics>
     <reporter name="default" class="org.apache.solr.metrics.reporters.SolrJmxReporter">
diff --git a/solr/server/scripts/cloud-scripts/zkcli.bat b/solr/server/scripts/cloud-scripts/zkcli.bat
index 0dcf8179e6d..63c0eb108e7 100644
--- a/solr/server/scripts/cloud-scripts/zkcli.bat
+++ b/solr/server/scripts/cloud-scripts/zkcli.bat
@@ -12,10 +12,14 @@ if "%SDIR:~-1%"=="\" set SDIR=%SDIR:~0,-1%
 set "LOG4J_CONFIG=file:///%SDIR%\..\..\resources\log4j2-console.xml"
 
 REM Settings for ZK ACL
-REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider ^
-REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider ^
+REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+REM  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
 REM  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
 REM  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
-
+REM optionally, you can use using a a Java properties file 'zkDigestCredentialsFile'
+REM ...
+REM   -DzkDigestCredentialsFile=/path/to/zkDigestCredentialsFile.properties
+REM ...
 "%JVM%" %SOLR_ZK_CREDS_AND_ACLS% %ZKCLI_JVM_FLAGS% -Dlog4j.configurationFile="%LOG4J_CONFIG%" ^
 -classpath "%SDIR%\..\..\solr-webapp\webapp\WEB-INF\lib\*;%SDIR%\..\..\lib\ext\*;%SDIR%\..\..\lib\*" org.apache.solr.cloud.ZkCLI %*
diff --git a/solr/server/scripts/cloud-scripts/zkcli.sh b/solr/server/scripts/cloud-scripts/zkcli.sh
index 844548a75e9..fa4e4556bfa 100755
--- a/solr/server/scripts/cloud-scripts/zkcli.sh
+++ b/solr/server/scripts/cloud-scripts/zkcli.sh
@@ -12,11 +12,15 @@ sdir="`dirname \"$0\"`"
 log4j_config="file:$sdir/../../resources/log4j2-console.xml"
 
 # Settings for ZK ACL
-#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \
-#  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \
+#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+#  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+#  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
 #  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
 #  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
-
+# optionally, you can use using a a Java properties file 'zkDigestCredentialsFile'
+#...
+#   -DzkDigestCredentialsFile=/path/to/zkDigestCredentialsFile.properties
+#...
 PATH=$JAVA_HOME/bin:$PATH $JVM $SOLR_ZK_CREDS_AND_ACLS $ZKCLI_JVM_FLAGS -Dlog4j.configurationFile=$log4j_config \
 -classpath "$sdir/../../solr-webapp/webapp/WEB-INF/lib/*:$sdir/../../lib/ext/*:$sdir/../../lib/*" org.apache.solr.cloud.ZkCLI ${1+"$@"}
 
diff --git a/solr/server/solr/solr.xml b/solr/server/solr/solr.xml
index eb53d399048..136154acfd6 100644
--- a/solr/server/solr/solr.xml
+++ b/solr/server/solr/solr.xml
@@ -47,6 +47,7 @@
     <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
     <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
     <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
+    <str name="zkCredentialsInjector">${zkCredentialsInjector:org.apache.solr.common.cloud.DefaultZkCredentialsInjector}</str>
     <bool name="distributedClusterStateUpdates">${distributedClusterStateUpdates:false}</bool>
     <bool name="distributedCollectionConfigSetExecution">${distributedCollectionConfigSetExecution:false}</bool>
 
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
index 37de392bd36..d75e89322b7 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
@@ -50,6 +50,7 @@ The default `solr.xml` file is found in `$SOLR_TIP/server/solr/solr.xml` and loo
     <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
     <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
     <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
+    <str name="zkCredentialsInjector">${zkCredentialsInjector:org.apache.solr.common.cloud.DefaultZkCredentialsInjector}</str>
     <bool name="distributedClusterStateUpdates">${distributedClusterStateUpdates:false}</bool>
     <bool name="distributedCollectionConfigSetExecution">${distributedCollectionConfigSetExecution:false}</bool>
 
@@ -403,7 +404,7 @@ In SolrCloud mode, the URL of the ZooKeeper host that Solr should use for cluste
 If `true`, node names are not based on the address of the node, but on a generic name that identifies the core.
 When a different machine takes over serving that core things will be much easier to understand.
 
-`zkCredentialsProvider` & `zkACLProvider`::
+`zkCredentialsProvider`, `zkACLProvider` & `zkCredentialsInjector`::
 +
 [%autowidth,frame=none]
 |===
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-access-control.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-access-control.adoc
index 8aee24c4a8d..47ef33be8bd 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-access-control.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-access-control.adoc
@@ -56,9 +56,38 @@ ACLs describe who is allowed to read, update, delete, create, etc.
 Each piece of information (znode/content) in ZooKeeper has its own set of ACLs, and inheritance or sharing is not possible.
 The default behavior in Solr is to add one ACL on all the content it creates - one ACL that gives anyone the permission to do anything (in ZooKeeper terms this is called "the open-unsafe ACL").
 
+
+
+== Solr to Zookeeper ACLs Workflow
+
+* Solr to Zookeeper credentials and ACLs are controlled through 3 interfaces: {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsInjector.html[`ZkCredentialsInjector`],  {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsProvider.html[`ZkCredentialsProvider`] and {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkACLProvider.html[`ZkACLProvider`].
+
+* The classes implementing these 3 interfaces are passed to Solr via System Properties using the properties names
+defined in `solr.xml` (see xref:configuration-guide:configuring-solr-xml.adoc[] for details). The following are the default properties names:
+`zkCredentialsInjector`, `zkACLProvider` and `zkCredentialsProvider`. See below sections for details.
+
+* The dataflow is as follow: Credentials source → `ZkCredentialsInjector` → `ZkCredentialsProvider/ZkACLProvider` → Zookeeper.
+
+`ZkCredentialsInjector` gets the credentials from some source which in turn get injected into `ZkCredentialsProvider`
+and `ZkACLProvider`. The "source" here can be System Properties, a file, a Secret Manager, or any other local or remote source.
+
+* Two sets of roles are supported:
+** `ALL` user: A user that is allowed to do everything (corresponding to all of `CREATE`, `READ`, `WRITE`, `DELETE`, and `ADMIN`).
+** `READ` user: A ready-only user that is only allowed to perform read operations.
+
+
+* We always protect access to content by limiting to two users - an admin-user and a readonly-user - AND we always connect with
+credentials corresponding to this same admin-user, basically so that we can do anything to the content/znodes we create ourselves.
+
+You can give the readonly credentials to "clients" of your SolrCloud cluster - e.g., to be used by SolrJ clients. They will
+be able to read whatever is necessary to run a functioning SolrJ client, but they will not be able to modify any content in ZooKeeper.
+
+
+
+
 == How to Enable ACLs
 
-We want to be able to:
+* We want to be able to:
 
 . Control the credentials Solr uses for its ZooKeeper connections.
 The credentials are used to get permission to perform operations in ZooKeeper.
@@ -69,64 +98,361 @@ Solr nodes, clients, and tools (e.g., ZkCLI) always use a java class called {sol
 The implementation of the solution described here is all about changing `SolrZkClient`.
 If you use `SolrZkClient` in your application, the descriptions below will be true for your application too.
 
-=== Controlling Credentials
 
-You control which credentials provider will be used by configuring the `zkCredentialsProvider` property in the `<solrcloud>` section of xref:configuration-guide:configuring-solr-xml.adoc[`solr.xml`] to the name of a class (on the classpath) implementing the {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsProvider.html[`ZkCredentialsProvider`] interface.
+* Controlling credentials and ACLs is done in 3 steps: Set a `ZkCredentialsInjector` that reads the credentials from
+some source and then inject them into a `ZkCredentialsProvider` that Solr uses to connect to Zookeeper. ZkACLProvider
+uses the same credentials to set the ACLs.
+
+
+We will describe these 3 steps in details before giving some ready to use examples.
+
+
+. Set the `ZkCredentialsInjector`.
+. Set the `ZkCredentialsProvider`.
+. Set the `ZkACLProvider`.
+
+
+=== Set a Credentials Injector
+
+* A credentials injector gets the credentials from an external source and injects them into Solr.
+
+
+** You control which credentials will be injected by configuring `zkCredentialsInjector` property in the `<solrcloud>` section of xref:configuration-guide:configuring-solr-xml.adoc[`solr.xml`] to the name of a class (on the classpath) implementing the {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsInjector.html[`ZkCredentialsInjector`] interface. +
+`server/solr/solr.xml` file in the Solr distribution defines the`zkCredentialsInjector` such that it will take on the value
+of the same-named `zkCredentialsInjector` system property if it is defined (e.g., by uncommenting
+the `SOLR_ZK_CREDS_AND_ACLS` environment variable definition in `solr.in.sh/.cmd`- see below), or if not, default
+to the `DefaultZkCredentialsInjector` implementation.
+
+==== Out of the Box Credentials Injector Implementations
+
+
+*  Solr comes with the following `ZkCredentialsInjectors`:
 
-`solr.xml` defines the `zkCredentialsProvider` such that it will take on the value of the same-named `zkCredentialsProvider` system property if it is defined (in `solr.in.sh/.cmd` - see <<ZooKeeper ACLs in Solr Scripts,below>>), or if not, default to the `DefaultZkCredentialsProvider` implementation.
+** `org.apache.solr.common.cloud.DefaultZkCredentialsInjector`: Its `getCredentials()` method returns a list of length zero,
+or "no credentials used". This is the default.
 
-==== Out of the Box Credential Implementations
+** `org.apache.solr.common.cloud.VMParamsZkCredentialsInjector`: The username and password are defined by system
+properties name:`zkDigestUsername` and `zkDigestPassword`. This set of credentials will be added to the list of credentials
+returned by `getCredentials()` if both username and password are provided.
+
+*** If the one set of credentials above is not added to the list, this implementation will fall back to default
+behavior and use the (empty) credentials list from `DefaultZkCredentialsInjector`.
+
+*** Alternatively, you can set the `zkDigestCredentialsFile` system property to load `zkDigestUsername` and
+`zkDigestPassword` from a file instead of exposing the credentials as system properties. The provided file must be a
+Java properties file and contain both the `zkDigestUsername` and `zkDigestPassword` properties.
+
+*** Usage (See full example later in the page):
+
+----
+-DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector
+-DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD
+-DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+
+# Or using a Java property file containing the credentials:
+-DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector
+-DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties
+----
+
+** You can create your own credentials injector by
+implementing {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsInjector.html[`ZkCredentialsInjector`] and pass it through System Properties using `zkCredentialsInjector` name:
+
+----
+-DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName
+----
+
+
+After the credentials are injected they are then used in the `ZkCredentialsProvider`.
+
+
+
+=== Set a Credential Provider
+
+
+
+
+`ZkCredentialsProvider` gets the credentials from the `ZkCredentialsInjector` and uses them to connect to Zookeeper.
+
+
+** You control which credentials will be used by configuring `zkCredentialsProvider` property in the `<solrcloud>` section of xref:configuration-guide:configuring-solr-xml.adoc[`solr.xml`] to the name of a class (on the classpath) implementing the {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsProvider.html[`ZkCredentialsProvider`] interface. +
+`server/solr/solr.xml` file in the Solr distribution defines the`zkCredentialsProvider`such that it will take on the value
+of the same-named `zkCredentialsProvider` system property if it is defined (e.g., by uncommenting
+the `SOLR_ZK_CREDS_AND_ACLS` environment variable definition in `solr.in.sh/.cmd`- see below), or if not, default
+to the `DefaultZkCredentialsProvider` implementation.
+
+
+==== Out of the Box credentials Implementations
 
 You can always make you own implementation, but Solr comes with two implementations:
 
-* `org.apache.solr.common.cloud.DefaultZkCredentialsProvider`: Its `getCredentials()` returns a list of length zero, or "no credentials used".
-This is the default.
-* `org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider`: This lets you define your credentials using system properties.
-It supports at most one set of credentials.
-** The schema is "digest".
-The username and password are defined by system properties `zkDigestUsername` and `zkDigestPassword`.
-This set of credentials will be added to the list of credentials returned by `getCredentials()` if both username and password are provided.
-** If the one set of credentials above is not added to the list, this implementation will fall back to default behavior and use the (empty) credentials list from `DefaultZkCredentialsProvider`.
-** Alternatively, you can set the `zkDigestCredentialsFile` system property to load the `zkDigestUsername` and `zkDigestPassword` from a file instead of exposing the credentials as system properties. The provided file must be a Java properties file and contain both the `zkDigestUsername` and `zkDigestPassword` properties.
+* No credentials:
 
-=== Controlling ACLs
+`org.apache.solr.common.cloud.DefaultZkCredentialsProvider`: Its `getCredentials()` returns a list of length
+zero, or "no credentials used". This is the default.
 
-You control which ACLs will be added by configuring `zkACLProvider` property in the `<solrcloud>` section of xref:configuration-guide:configuring-solr-xml.adoc[`solr.xml`] to the name of a class (on the classpath) implementing the {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkACLProvider.html[`ZkACLProvider`] interface.
 
-`solr.xml` defines the `zkACLProvider` such that it will take on the value of the same-named `zkACLProvider` system property if it is defined (in `solr.in.sh/.cmd` - see <<ZooKeeper ACLs in Solr Scripts,below>>), or if not, default to the `DefaultZkACLProvider` implementation.
+* `digest` scheme based credentialsProvider:
 
-==== Out of the Box ACL Implementations
+`org.apache.solr.common.cloud.DigestZkCredentialsProvider`: The used scheme is `digest` and it gets the `ALL` user
+credentials  (perms=all) from the specified `ZkCredentialsInjector`.
+
+If a `ZkCredentialsInjector` with an `ALL` user ( having both username and password provided) is not defined, it will fall
+back to default behavior and use the (empty) credentials list from `DefaultZkCredentialsProvider`.
 
+
+=== Set an ACL Provider
+
+
+** You control which ACLs will be added by configuring `zkACLProvider` property in the `<solrcloud>` section of xref:configuration-guide:configuring-solr-xml.adoc[`solr.xml`] to the name of a class (on the classpath) implementing the {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkACLProvider.html[`ZkACLProvider`] interface. +
+`server/solr/solr.xml` file in the Solr distribution defines the`zkACLProvider`such that it will take on the value
+of the same-named `zkACLProvider` system property if it is defined (e.g., by uncommenting
+the `SOLR_ZK_CREDS_AND_ACLS` environment variable definition in `solr.in.sh/.cmd`- see below), or if not, default
+to the `DefaultZkACLProvider` implementation.
+
+
+==== Out of the Box ACL Implementations
 You can always make you own implementation, but Solr comes with:
 
-* `org.apache.solr.common.cloud.DefaultZkACLProvider`: It returns a list of length one for all `zNodePath`-s.
-The single ACL entry in the list is "open-unsafe".
-This is the default.
-* `org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider`: This lets you define your ACLs using system properties.
-The `getACLsToAdd()` implementation will apply only admin ACLs to pre-defined sensitive paths as defined by `SecurityAwareZkACLProvider` (`/security.json` and `/security/*`) and both admin and user ACLs to the rest of the contents.
-The two sets of roles will be defined as:
-** A user that is allowed to do everything.
-*** The permission is `ALL` (corresponding to all of `CREATE`, `READ`, `WRITE`, `DELETE`, and `ADMIN`), and the schema is "digest".
-*** The username and password are defined by system properties `zkDigestUsername` and `zkDigestPassword`, respectively.
-*** This ACL will not be added to the list of ACLs unless both username and password are provided.
-** A user that is only allowed to perform read operations.
-*** The permission is `READ` and the schema is `digest`.
-*** The username and password are defined by system properties `zkDigestReadonlyUsername` and `zkDigestReadonlyPassword`, respectively.
-*** This ACL will not be added to the list of ACLs unless both username and password are provided.
-** Alternatively, you can set the `zkDigestCredentialsFile` system property to load the `zkDigestUsername` and `zkDigestPassword` from a file instead of exposing the credentials as system properties. The provided file must be a Java properties file and contain both the `zkDigestUsername` and `zkDigestPassword` properties for the `ALL` user, as well as the `zkDigestReadonlyUsername` and `zkDigestReadonlyPassword` properties for the `READONLY` user.
-* `org.apache.solr.common.cloud.SaslZkACLProvider`: Requires SASL authentication.
-Gives all permissions for the user specified in system property `solr.authorization.superuser` (default: `solr`) when using SASL, and gives read permissions for anyone else.
-Designed for a setup where configurations have already been set up and will not be modified, or where configuration changes are controlled via Solr APIs.
-This provider will be useful for administration in a Kerberos environment.
-In such an environment, the administrator wants Solr to authenticate to ZooKeeper using SASL, since this is only way to authenticate with ZooKeeper via Kerberos.
-
-If none of the above ACLs is added to the list, the (empty) ACL list of `DefaultZkACLProvider` will be used by default.
-
-Notice the overlap in system property names with credentials provider `VMParamsSingleSetCredentialsDigestZkCredentialsProvider` (described above).
-This is to let the two providers collaborate in a nice and perhaps common way: we always protect access to content by limiting to two users - an admin-user and a readonly-user - AND we always connect with credentials corresponding to this same admin-user, basically so that we can do anything to the content/znodes we create ourselves.
-
-You can give the readonly credentials to "clients" of your SolrCloud cluster - e.g., to be used by SolrJ clients.
-They will be able to read whatever is necessary to run a functioning SolrJ client, but they will not be able to modify any content in ZooKeeper.
+* `org.apache.solr.common.cloud.DefaultZkACLProvider`: It returns a list of length one for all `zNodePath`-s. The single ACL entry
+in the list is "open-unsafe". This is the default.
+
+* `org.apache.solr.common.cloud.DigestZkACLProvider`: This lets you define your ACLs using the defined `ZkCredentialsInjector`. Its `getACLsToAdd()`
+implementation will apply only admin ACLs to pre-defined sensitive paths as defined
+by `SecurityAwareZkACLProvider` (`/security.json` and `/security/*`) and both admin and user ACLs to the rest of the contents.
+The `all` and `read` users are injected through the `ZkCredentialsInjector` described earlier in the page.
+
+* `org.apache.solr.common.cloud.SaslZkACLProvider`: Requires SASL authentication. Gives all permissions for the user specified in system
+property `solr.authorization.superuser` (default:`solr`) when using SASL, and gives read permissions for anyone else. Designed for
+a setup where configurations have already been set up and will not be modified, or where configuration changes are
+controlled via Solr APIs. This provider will be useful for administration in a kerberos environment. In such
+an environment, the administrator wants Solr to authenticate to ZooKeeper using SASL, since this is only way to
+authenticate with ZooKeeper via Kerberos.
+
+* If none of the above ACLs is added to the list, the (empty) ACL list of `DefaultZkACLProvider` will be used by default.
+
+
+
+=== Examples
+
+
+Below examples are for `digest` scheme.
+
+* xref:#through-system-properties[System Properties]
+* xref:#through-a-file[Through a File]
+* xref:#through-a-custom-credentials-injector[Custom Credentials Injector]
+
+* Note: If you are reusing an existing 'solr.xml' make sure to add the following line to '<solrcloud>' block:
+----
+ <str name="zkCredentialsInjector">${zkCredentialsInjector:org.apache.solr.common.cloud.DefaultZkCredentialsInjector}</str>
+----
+
+==== Through System Properties
+:sectanchors:
+
+* ZK credentials are passed through System Properties via `DzkDigestUsername`, `DzkDigestPassword`, `DzkDigestReadonlyUsername`
+and `DzkDigestReadonlyPassword` properties names.
+
+[.dynamic-tabs]
+--
+[example.tab-pane#system-props-nix]
+====
+[.tab-label]**nix*
+
+.solr.in.sh
+[source,bash]
+----
+
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
+  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
+SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS"
+
+----
+
+
+.zkcli.sh
+[source,bash]
+----
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
+  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
+----
+====
+
+[example.tab-pane#system-props-windows]
+====
+[.tab-label]*Windows*
+
+.solr.in.cmd
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
+ -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
+ -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
+----
+
+.zkcli.bat
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
+ -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
+ -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+----
+====
+--
+
+
+
+==== Through a File
+
+* Create a Java property files, for example named `zookeepercredentials.properties` containing the credentials in the following format:
+----
+zkDigestUsername=admin-user
+zkDigestPassword=CHANGEME-ADMIN-PASSWORD
+zkDigestReadonlyUsername=readonly-user
+zkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+----
+
+* Pass the file path via System Properties:
+
+
+[.dynamic-tabs]
+--
+[example.tab-pane#file-system-props-nix]
+====
+[.tab-label]**nix*
+
+.solr.in.sh
+[source,bash]
+----
+
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+  -DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties"
+SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS"
+
+----
+
+
+.zkcli.sh
+[source,bash]
+----
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+  -DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties"
+----
+====
+
+[example.tab-pane#file-system-props-windows]
+====
+[.tab-label]*Windows*
+
+.solr.in.cmd
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
+ -DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties
+set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
+----
+
+.zkcli.bat
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
+ -DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties
+----
+====
+--
+
+
+
+==== Through a Custom Credentials Injector
+
+
+* Alternatively, you can create your own credentials injector by
+implementing {solr-javadocs}/solrj-zookeeper/org/apache/solr/common/cloud/ZkCredentialsInjector.html[`ZkCredentialsInjector`] and pass
+it through system props using DzkCredentialsInjector variable name.
+
+
+
+[.dynamic-tabs]
+--
+[example.tab-pane#custom-injector-nix]
+====
+[.tab-label]**nix*
+
+.solr.in.sh
+[source,bash]
+----
+
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName"
+SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS"
+
+----
+
+
+.zkcli.sh
+[source,bash]
+----
+# Settings for ZK ACL
+SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+  -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName
+----
+====
+
+[example.tab-pane#-custom-injector-windows]
+====
+[.tab-label]*Windows*
+
+.solr.in.cmd
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName
+set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
+----
+
+.zkcli.bat
+[source,powershell]
+----
+REM Settings for ZK ACL
+set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+ -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+ -DzkCredentialsInjector=fully.qualified.class.CustomInjectorClassName
+----
+====
+--
+
+
 
 === ZooKeeper ACLs in Solr Scripts
 
@@ -140,7 +466,11 @@ Both the solr.in.* and the zkcli.* files will need to be updated with the same p
 The contents may appear redundant, but the scripts will not consult each other during operations.
 
 These Solr scripts can enable use of ZooKeeper ACLs by setting the appropriate system properties.
-Uncomment the following and replace the passwords with ones you choose to enable the VM parameters and ACL credentials providers in the following files:
+
+* Example using VMParamsZkCredentialsInjector:
+
+Uncomment the following and replace the passwords with ones you choose to enable the parameters and ACL credentials providers
+in the following files:
 
 [.dynamic-tabs]
 --
@@ -152,8 +482,9 @@ Uncomment the following and replace the passwords with ones you choose to enable
 [source,bash]
 ----
 # Settings for ZK ACL
-#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \
-#  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \
+#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+#  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+#  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
 #  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
 #  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
 #SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS"
@@ -163,8 +494,9 @@ Uncomment the following and replace the passwords with ones you choose to enable
 [source,bash]
 ----
 # Settings for ZK ACL
-#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \
-#  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \
+#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider \
+#  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider \
+#  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
 #  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
 #  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
 ----
@@ -178,8 +510,9 @@ Uncomment the following and replace the passwords with ones you choose to enable
 [source,powershell]
 ----
 REM Settings for ZK ACL
-REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider ^
-REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider ^
+REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+REM  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
 REM  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
 REM  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
 REM set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
@@ -189,8 +522,9 @@ REM set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS%
 [source,powershell]
 ----
 REM Settings for ZK ACL
-REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider ^
-REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider ^
+REM set SOLR_ZK_CREDS_AND_ACLS=-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider ^
+REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider ^
+REM  -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector ^
 REM  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
 REM  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
 ----
@@ -231,11 +565,13 @@ C:\\ server\scripts\cloud-scripts\zkcli.bat cmd updateacls /zk-path
 Changing ACLs in ZooKeeper should only be done while your SolrCloud cluster is stopped.
 Attempting to do so while Solr is running may result in inconsistent state and some nodes becoming inaccessible.
 
-The VM properties `zkACLProvider` and `zkCredentialsProvider`, included in the `SOLR_ZK_CREDS_AND_ACLS` environment variable in `zkcli.sh/.bat`, control the conversion:
+The VM properties `zkCredentialsInjector`, `zkACLProvider` and `zkCredentialsProvider`, included in the `SOLR_ZK_CREDS_AND_ACLS` environment variable in `zkcli.sh/.bat`, control the conversion:
 
-* The Credentials Provider must be one that has current admin privileges on the nodes.
+* The Credentials Injector reads the credentials and pass them to the Credentials Provider.
+When omitted, the process will use no credentials (suitable for an unsecure configuration).
+* The Credentials Provider uses the credentials of the user with admin privileges on the nodes.
 When omitted, the process will use no credentials (suitable for an unsecure configuration).
 * The ACL Provider will be used to compute the new ACLs.
 When omitted, the process will set all permissions to all users, removing any security present.
 
-The uncommented `SOLR_ZK_CREDS_AND_ACLS` environment variable in `zkcli.sh/.bat` sets the credentials and ACL providers to the `VMParamsSingleSetCredentialsDigestZkCredentialsProvider` and `VMParamsAllAndReadonlyDigestZkACLProvider` implementations, described earlier in the page.
+The uncommented `SOLR_ZK_CREDS_AND_ACLS` environment variable in `zkcli.sh/.bat` sets the credentials and ACL providers to the `VMParamsZkCredentialsInjector`, `DigestZkCredentialsProvider` and `DigestZkACLProvider` implementations, described earlier in the page.
diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
index 6a33fddd934..d0aeafb9c23 100644
--- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
+++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
@@ -297,7 +297,7 @@ This should help provide performance improvements in busy clusters being monitor
 *ZooKeeper Credentials*
 
 ZooKeeper credentials can now be stored in a file whose location is defined with a system property instead of being passed in plain-text.
-See xref:deployment-guide:zookeeper-access-control.adoc#out-of-the-box-credential-implementations[Out of the Box Credential Implementations] for how to set this up.
+See xref:deployment-guide:zookeeper-access-control.adoc#out-of-the-box-credentials-implementations[Out of the Box Credential Implementations] for how to set this up.
 
 === Solr 8.9
 
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsInjector.java
old mode 100644
new mode 100755
similarity index 80%
copy from solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java
copy to solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsInjector.java
index 4b3ad36ed42..d78d4da83ef
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsInjector.java
@@ -16,10 +16,13 @@
  */
 package org.apache.solr.common.cloud;
 
+import java.util.Collections;
 import java.util.List;
-import org.apache.zookeeper.data.ACL;
 
-public interface ZkACLProvider {
+public class DefaultZkCredentialsInjector implements ZkCredentialsInjector {
 
-  List<ACL> getACLsToAdd(String zNodePath);
+  @Override
+  public List<ZkCredential> getZkCredentials() {
+    return Collections.emptyList();
+  }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsProvider.java
index a2ef37b7b1c..e855052f6a6 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DefaultZkCredentialsProvider.java
@@ -22,6 +22,20 @@ import java.util.Collection;
 public class DefaultZkCredentialsProvider implements ZkCredentialsProvider {
 
   private Collection<ZkCredentials> zkCredentials;
+  protected ZkCredentialsInjector zkCredentialsInjector;
+
+  public DefaultZkCredentialsProvider() {
+    this(new DefaultZkCredentialsInjector());
+  }
+
+  public DefaultZkCredentialsProvider(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+  }
+
+  @Override
+  public void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+  }
 
   @Override
   public Collection<ZkCredentials> getCredentials() {
@@ -34,6 +48,6 @@ public class DefaultZkCredentialsProvider implements ZkCredentialsProvider {
   }
 
   protected Collection<ZkCredentials> createCredentials() {
-    return new ArrayList<ZkCredentials>();
+    return new ArrayList<>();
   }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkACLProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkACLProvider.java
new file mode 100755
index 00000000000..eb3f3c2b1c6
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkACLProvider.java
@@ -0,0 +1,104 @@
+/*
+ * 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.solr.common.cloud;
+
+import static org.apache.zookeeper.server.auth.DigestAuthenticationProvider.generateDigest;
+
+import java.lang.invoke.MethodHandles;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.StringUtils;
+import org.apache.solr.common.cloud.ZkCredentialsInjector.ZkCredential;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class that expects a {@link ZkCredentialsInjector} to create Zookeeper ACLs using digest scheme
+ */
+public class DigestZkACLProvider extends SecurityAwareZkACLProvider {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /** Called by reflective instantiation */
+  public DigestZkACLProvider() {}
+
+  public DigestZkACLProvider(ZkCredentialsInjector zkCredentialsInjector) {
+    super(zkCredentialsInjector);
+  }
+
+  /**
+   * @return Set of ACLs to return for non-security related znodes
+   */
+  @Override
+  protected List<ACL> createNonSecurityACLsToAdd() {
+    return createACLsToAdd(true);
+  }
+
+  /**
+   * Provider ACL Credential
+   *
+   * @return Set of ACLs to return security-related znodes
+   */
+  @Override
+  protected List<ACL> createSecurityACLsToAdd() {
+    return createACLsToAdd(false);
+  }
+
+  protected List<ACL> createACLsToAdd(boolean includeReadOnly) {
+    List<ACL> result = new ArrayList<>();
+    List<ZkCredential> zkCredentials = zkCredentialsInjector.getZkCredentials();
+    log.debug("createACLsToAdd using ZkCredentials: {}", zkCredentials);
+    for (ZkCredential zkCredential : zkCredentials) {
+      if (StringUtils.isEmpty(zkCredential.getUsername())
+          || StringUtils.isEmpty(zkCredential.getPassword())) {
+        continue;
+      }
+      Id id = createACLId(zkCredential.getUsername(), zkCredential.getPassword());
+      int perms;
+      if (zkCredential.isAll()) {
+        // Not to have to provide too much credentials and ACL information to the process it is
+        // assumed that you want "ALL"-acls added to the user you are using to connect to ZK
+        perms = ZooDefs.Perms.ALL;
+      } else if (includeReadOnly && zkCredential.isReadonly()) {
+        // Besides, that support for adding additional "READONLY"-acls for another user
+        perms = ZooDefs.Perms.READ;
+      } else {
+        // ignore unsupported perms (neither All nor READONLY)
+        continue;
+      }
+      result.add(new ACL(perms, id));
+    }
+    if (result.isEmpty()) {
+      result = ZooDefs.Ids.OPEN_ACL_UNSAFE;
+    }
+    return result;
+  }
+
+  protected Id createACLId(String username, String password) {
+    try {
+      return new Id("digest", generateDigest(username + ":" + password));
+    } catch (NoSuchAlgorithmException e) {
+      throw new SolrException(
+          SolrException.ErrorCode.SERVER_ERROR, "JVM mis-configured: missing SHA-1 algorithm", e);
+    }
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkCredentialsProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkCredentialsProvider.java
new file mode 100755
index 00000000000..1b8092c8733
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/DigestZkCredentialsProvider.java
@@ -0,0 +1,65 @@
+/*
+ * 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.solr.common.cloud;
+
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.solr.common.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class that expects a {@link ZkCredentialsInjector} to create Zookeeper credentials using Digest
+ * scheme
+ */
+public class DigestZkCredentialsProvider extends DefaultZkCredentialsProvider {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /** Called by reflective instantiation */
+  public DigestZkCredentialsProvider() {}
+
+  public DigestZkCredentialsProvider(ZkCredentialsInjector zkCredentialsInjector) {
+    super(zkCredentialsInjector);
+  }
+
+  @Override
+  protected Collection<ZkCredentials> createCredentials() {
+    List<ZkCredentials> result = new ArrayList<>(1);
+    List<ZkCredentialsInjector.ZkCredential> zkCredentials =
+        zkCredentialsInjector.getZkCredentials();
+    log.debug("createCredentials using zkCredentials: {}", zkCredentials);
+    for (ZkCredentialsInjector.ZkCredential zkCredential : zkCredentials) {
+      if (zkCredential.isAll()) {
+        // this is the "user" with all perms that SolrZooKeeper uses to connect to zookeeper
+        if (!StringUtils.isEmpty(zkCredential.getUsername())
+            && !StringUtils.isEmpty(zkCredential.getPassword())) {
+          result.add(
+              new ZkCredentials(
+                  "digest",
+                  (zkCredential.getUsername() + ":" + zkCredential.getPassword())
+                      .getBytes(StandardCharsets.UTF_8)));
+          break; // single credentials set
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java
index 392771661af..1868f53308a 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java
@@ -28,6 +28,20 @@ public abstract class SecurityAwareZkACLProvider implements ZkACLProvider {
 
   private List<ACL> nonSecurityACLsToAdd;
   private List<ACL> securityACLsToAdd;
+  protected ZkCredentialsInjector zkCredentialsInjector;
+
+  public SecurityAwareZkACLProvider() {
+    this(new DefaultZkCredentialsInjector());
+  }
+
+  public SecurityAwareZkACLProvider(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+  }
+
+  @Override
+  public void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+  }
 
   @Override
   public final List<ACL> getACLsToAdd(String zNodePath) {
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
index 4a602a944cc..05df58bac00 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
@@ -92,6 +92,7 @@ public class SolrZkClient implements Closeable {
   private ZkClientConnectionStrategy zkClientConnectionStrategy;
   private int zkClientTimeout;
   private ZkACLProvider zkACLProvider;
+  private ZkCredentialsInjector zkCredentialsInjector;
   private String zkServerAddress;
 
   private IsClosed higherLevelIsClosed;
@@ -163,6 +164,7 @@ public class SolrZkClient implements Closeable {
     this.zkClientConnectionStrategy = strat;
 
     if (!strat.hasZkCredentialsToAddAutomatically()) {
+      zkCredentialsInjector = createZkCredentialsInjector();
       ZkCredentialsProvider zkCredentialsToAddAutomatically =
           createZkCredentialsToAddAutomatically();
       strat.setZkCredentialsToAddAutomatically(zkCredentialsToAddAutomatically);
@@ -246,13 +248,18 @@ public class SolrZkClient implements Closeable {
     if (!StringUtils.isEmpty(zkCredentialsProviderClassName)) {
       try {
         log.info("Using ZkCredentialsProvider: {}", zkCredentialsProviderClassName);
-        return (ZkCredentialsProvider)
-            Class.forName(zkCredentialsProviderClassName).getConstructor().newInstance();
-      } catch (Throwable t) {
+        ZkCredentialsProvider zkCredentialsProvider =
+            Class.forName(zkCredentialsProviderClassName)
+                .asSubclass(ZkCredentialsProvider.class)
+                .getConstructor()
+                .newInstance();
+        zkCredentialsProvider.setZkCredentialsInjector(zkCredentialsInjector);
+        return zkCredentialsProvider;
+      } catch (Exception e) {
         // just ignore - go default
         log.warn(
             "VM param zkCredentialsProvider does not point to a class implementing ZkCredentialsProvider and with a non-arg constructor",
-            t);
+            e);
       }
     }
     log.debug("Using default ZkCredentialsProvider");
@@ -266,18 +273,51 @@ public class SolrZkClient implements Closeable {
     if (!StringUtils.isEmpty(zkACLProviderClassName)) {
       try {
         log.info("Using ZkACLProvider: {}", zkACLProviderClassName);
-        return (ZkACLProvider) Class.forName(zkACLProviderClassName).getConstructor().newInstance();
-      } catch (Throwable t) {
+        ZkACLProvider zkACLProvider =
+            Class.forName(zkACLProviderClassName)
+                .asSubclass(ZkACLProvider.class)
+                .getConstructor()
+                .newInstance();
+        zkACLProvider.setZkCredentialsInjector(zkCredentialsInjector);
+        return zkACLProvider;
+      } catch (Exception e) {
         // just ignore - go default
         log.warn(
             "VM param zkACLProvider does not point to a class implementing ZkACLProvider and with a non-arg constructor",
-            t);
+            e);
       }
     }
-    log.debug("Using default ZkACLProvider");
+    log.warn(
+        "Using default ZkACLProvider. DefaultZkACLProvider is not secure, it creates 'OPEN_ACL_UNSAFE' ACLs to Zookeeper nodes");
     return new DefaultZkACLProvider();
   }
 
+  public static final String ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME =
+      "zkCredentialsInjector";
+
+  protected ZkCredentialsInjector createZkCredentialsInjector() {
+    String zkCredentialsInjectorClassName =
+        System.getProperty(ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME);
+    if (!StringUtils.isEmpty(zkCredentialsInjectorClassName)) {
+      try {
+        log.info("Using ZkCredentialsInjector: {}", zkCredentialsInjectorClassName);
+        return Class.forName(zkCredentialsInjectorClassName)
+            .asSubclass(ZkCredentialsInjector.class)
+            .getConstructor()
+            .newInstance();
+      } catch (Exception e) {
+        // just ignore - go default
+        log.warn(
+            "VM param ZkCredentialsInjector does not point to a class implementing ZkCredentialsInjector and with a non-arg constructor",
+            e);
+      }
+    }
+    log.warn(
+        "Using default ZkCredentialsInjector. ZkCredentialsInjector is not secure, it creates an empty list of "
+            + "credentials which leads to 'OPEN_ACL_UNSAFE' ACLs to Zookeeper nodes");
+    return new DefaultZkCredentialsInjector();
+  }
+
   /** Returns true if client is connected */
   public boolean isConnected() {
     return keeper != null && keeper.getState() == ZooKeeper.States.CONNECTED;
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
index 3d67cd16833..744026ee808 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
@@ -16,40 +16,51 @@
  */
 package org.apache.solr.common.cloud;
 
-import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME;
-import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.readCredentialsFile;
-
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Properties;
-import org.apache.solr.common.StringUtils;
-import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.data.ACL;
-import org.apache.zookeeper.data.Id;
-import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
 
-public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkACLProvider {
+/**
+ * Deprecated in favor of a combination of {@link DigestZkACLProvider} and {@link
+ * VMParamsZkCredentialsInjector}.
+ *
+ * <pre>
+ * Current implementation delegates to {@link DigestZkACLProvider} with an injected {@link VMParamsZkCredentialsInjector}
+ * </pre>
+ */
+@Deprecated
+public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkACLProvider
+    implements ZkACLProvider {
+
+  @Deprecated
+  public static final String DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME =
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
+
+  @Deprecated
+  public static final String DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME =
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
 
+  @Deprecated
   public static final String DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME =
-      "zkDigestReadonlyUsername";
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME;
+
+  @Deprecated
   public static final String DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME =
-      "zkDigestReadonlyPassword";
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME;
 
-  final String zkDigestAllUsernameVMParamName;
-  final String zkDigestAllPasswordVMParamName;
-  final String zkDigestReadonlyUsernameVMParamName;
-  final String zkDigestReadonlyPasswordVMParamName;
-  final Properties credentialsProps;
+  @Deprecated
+  public static final String DEFAULT_DIGEST_FILE_VM_PARAM_NAME =
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_FILE_VM_PARAM_NAME;
+
+  private ZkCredentialsInjector zkCredentialsInjector;
+  private DigestZkACLProvider digestZkACLProvider;
 
   public VMParamsAllAndReadonlyDigestZkACLProvider() {
-    this(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
-        DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
+    this(new VMParamsZkCredentialsInjector());
+  }
+
+  public VMParamsAllAndReadonlyDigestZkACLProvider(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+    this.digestZkACLProvider = new DigestZkACLProvider(zkCredentialsInjector);
   }
 
   public VMParamsAllAndReadonlyDigestZkACLProvider(
@@ -57,93 +68,30 @@ public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkAC
       String zkDigestAllPasswordVMParamName,
       String zkDigestReadonlyUsernameVMParamName,
       String zkDigestReadonlyPasswordVMParamName) {
-    this.zkDigestAllUsernameVMParamName = zkDigestAllUsernameVMParamName;
-    this.zkDigestAllPasswordVMParamName = zkDigestAllPasswordVMParamName;
-    this.zkDigestReadonlyUsernameVMParamName = zkDigestReadonlyUsernameVMParamName;
-    this.zkDigestReadonlyPasswordVMParamName = zkDigestReadonlyPasswordVMParamName;
+    this(
+        new VMParamsZkCredentialsInjector(
+            zkDigestAllUsernameVMParamName,
+            zkDigestAllPasswordVMParamName,
+            zkDigestReadonlyUsernameVMParamName,
+            zkDigestReadonlyPasswordVMParamName));
+  }
 
-    String pathToFile = System.getProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
-    credentialsProps =
-        (pathToFile != null) ? readCredentialsFile(pathToFile) : System.getProperties();
+  @Override
+  public void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector =
+        zkCredentialsInjector != null && !zkCredentialsInjector.getZkCredentials().isEmpty()
+            ? zkCredentialsInjector
+            : new VMParamsZkCredentialsInjector();
+    this.digestZkACLProvider = new DigestZkACLProvider(this.zkCredentialsInjector);
   }
 
-  /**
-   * @return Set of ACLs to return for non-security related znodes
-   */
   @Override
   protected List<ACL> createNonSecurityACLsToAdd() {
-    return createACLsToAdd(true);
+    return digestZkACLProvider.createNonSecurityACLsToAdd();
   }
 
-  /**
-   * @return Set of ACLs to return security-related znodes
-   */
   @Override
   protected List<ACL> createSecurityACLsToAdd() {
-    return createACLsToAdd(false);
-  }
-
-  protected List<ACL> createACLsToAdd(boolean includeReadOnly) {
-    String digestAllUsername = credentialsProps.getProperty(zkDigestAllUsernameVMParamName);
-    String digestAllPassword = credentialsProps.getProperty(zkDigestAllPasswordVMParamName);
-    String digestReadonlyUsername =
-        credentialsProps.getProperty(zkDigestReadonlyUsernameVMParamName);
-    String digestReadonlyPassword =
-        credentialsProps.getProperty(zkDigestReadonlyPasswordVMParamName);
-
-    return createACLsToAdd(
-        includeReadOnly,
-        digestAllUsername,
-        digestAllPassword,
-        digestReadonlyUsername,
-        digestReadonlyPassword);
-  }
-
-  /** Note: only used for tests */
-  protected List<ACL> createACLsToAdd(
-      boolean includeReadOnly,
-      String digestAllUsername,
-      String digestAllPassword,
-      String digestReadonlyUsername,
-      String digestReadonlyPassword) {
-
-    try {
-      List<ACL> result = new ArrayList<>(2);
-
-      // Not to have to provide too much credentials and ACL information to the process it is
-      // assumed that you want "ALL"-acls added to the user you are using to connect to ZK (if you
-      // are using VMParamsSingleSetCredentialsDigestZkCredentialsProvider)
-      if (!StringUtils.isEmpty(digestAllUsername) && !StringUtils.isEmpty(digestAllPassword)) {
-        result.add(
-            new ACL(
-                ZooDefs.Perms.ALL,
-                new Id(
-                    "digest",
-                    DigestAuthenticationProvider.generateDigest(
-                        digestAllUsername + ":" + digestAllPassword))));
-      }
-
-      if (includeReadOnly) {
-        // Besides that support for adding additional "READONLY"-acls for another user
-        if (!StringUtils.isEmpty(digestReadonlyUsername)
-            && !StringUtils.isEmpty(digestReadonlyPassword)) {
-          result.add(
-              new ACL(
-                  ZooDefs.Perms.READ,
-                  new Id(
-                      "digest",
-                      DigestAuthenticationProvider.generateDigest(
-                          digestReadonlyUsername + ":" + digestReadonlyPassword))));
-        }
-      }
-
-      if (result.isEmpty()) {
-        result = ZooDefs.Ids.OPEN_ACL_UNSAFE;
-      }
-
-      return result;
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("JVM mis-configured: missing SHA-1 algorithm", e);
-    }
+    return digestZkACLProvider.createSecurityACLsToAdd();
   }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
index 3d6b2a35ede..d437e5b6682 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
@@ -16,64 +16,56 @@
  */
 package org.apache.solr.common.cloud;
 
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
-import java.util.Properties;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.StringUtils;
 
+/**
+ * Deprecated in favor of a combination of {@link DigestZkCredentialsProvider} and {@link
+ * VMParamsZkCredentialsInjector}.
+ *
+ * <pre>
+ * Current implementation delegates to {@link DigestZkCredentialsProvider} with an injected {@link VMParamsZkCredentialsInjector}
+ * </pre>
+ */
+@Deprecated
 public class VMParamsSingleSetCredentialsDigestZkCredentialsProvider
     extends DefaultZkCredentialsProvider {
 
-  public static final String DEFAULT_DIGEST_FILE_VM_PARAM_NAME = "zkDigestCredentialsFile";
-  public static final String DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME = "zkDigestUsername";
-  public static final String DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME = "zkDigestPassword";
-
-  static Properties readCredentialsFile(String pathToFile) throws SolrException {
-    Properties props = new Properties();
-    try (Reader reader =
-        new InputStreamReader(new FileInputStream(pathToFile), StandardCharsets.UTF_8)) {
-      props.load(reader);
-    } catch (IOException ioExc) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ioExc);
-    }
-    return props;
-  }
+  public static final String DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME =
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
+  public static final String DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME =
+      VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
 
-  final String zkDigestUsernameVMParamName;
-  final String zkDigestPasswordVMParamName;
+  private ZkCredentialsInjector zkCredentialsInjector;
+  private DigestZkCredentialsProvider digestZkCredentialsProvider;
 
   public VMParamsSingleSetCredentialsDigestZkCredentialsProvider() {
     this(DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
   }
 
+  public VMParamsSingleSetCredentialsDigestZkCredentialsProvider(
+      ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector = zkCredentialsInjector;
+    this.digestZkCredentialsProvider = new DigestZkCredentialsProvider(zkCredentialsInjector);
+  }
+
   public VMParamsSingleSetCredentialsDigestZkCredentialsProvider(
       String zkDigestUsernameVMParamName, String zkDigestPasswordVMParamName) {
-    this.zkDigestUsernameVMParamName = zkDigestUsernameVMParamName;
-    this.zkDigestPasswordVMParamName = zkDigestPasswordVMParamName;
+    this(
+        new VMParamsZkCredentialsInjector(
+            zkDigestUsernameVMParamName, zkDigestPasswordVMParamName, null, null));
   }
 
   @Override
-  protected Collection<ZkCredentials> createCredentials() {
-    List<ZkCredentials> result = new ArrayList<>();
-
-    String pathToFile = System.getProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
-    Properties props =
-        (pathToFile != null) ? readCredentialsFile(pathToFile) : System.getProperties();
+  public void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector) {
+    this.zkCredentialsInjector =
+        zkCredentialsInjector != null && !zkCredentialsInjector.getZkCredentials().isEmpty()
+            ? zkCredentialsInjector
+            : new VMParamsZkCredentialsInjector();
+    this.digestZkCredentialsProvider = new DigestZkCredentialsProvider(this.zkCredentialsInjector);
+  }
 
-    String digestUsername = props.getProperty(zkDigestUsernameVMParamName);
-    String digestPassword = props.getProperty(zkDigestPasswordVMParamName);
-    if (!StringUtils.isEmpty(digestUsername) && !StringUtils.isEmpty(digestPassword)) {
-      result.add(
-          new ZkCredentials(
-              "digest", (digestUsername + ":" + digestPassword).getBytes(StandardCharsets.UTF_8)));
-    }
-    return result;
+  @Override
+  protected Collection<ZkCredentials> createCredentials() {
+    return digestZkCredentialsProvider.createCredentials();
   }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsZkCredentialsInjector.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsZkCredentialsInjector.java
new file mode 100755
index 00000000000..649c3afce9c
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/VMParamsZkCredentialsInjector.java
@@ -0,0 +1,138 @@
+/*
+ * 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.solr.common.cloud;
+
+import static org.apache.solr.common.cloud.ZkCredentialsInjector.ZkCredential.Perms;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.apache.solr.common.SolrException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reads credentials from System Properties and injects them into {@link
+ * DigestZkCredentialsProvider} &amp; {@link DigestZkACLProvider} Usage:
+ *
+ * <pre>
+ *   -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+ *   -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
+ *   -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+ * </pre>
+ *
+ * Or from a Java property file:
+ *
+ * <pre>
+ *   -DzkCredentialsInjector=org.apache.solr.common.cloud.VMParamsZkCredentialsInjector \
+ *   -DzkDigestCredentialsFile=SOLR_HOME_DIR/server/etc/zookeepercredentials.properties
+ * </pre>
+ *
+ * Example of a Java property file:
+ *
+ * <pre>
+ * zkDigestUsername=admin-user
+ * zkDigestPassword=CHANGEME-ADMIN-PASSWORD
+ * zkDigestReadonlyUsername=readonly-user
+ * zkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
+ * </pre>
+ */
+public class VMParamsZkCredentialsInjector implements ZkCredentialsInjector {
+
+  public static final String DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME = "zkDigestUsername";
+  public static final String DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME = "zkDigestPassword";
+  public static final String DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME =
+      "zkDigestReadonlyUsername";
+  public static final String DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME =
+      "zkDigestReadonlyPassword";
+  public static final String DEFAULT_DIGEST_FILE_VM_PARAM_NAME = "zkDigestCredentialsFile";
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  final String zkDigestAllUsernameVMParamName;
+  final String zkDigestAllPasswordVMParamName;
+  final String zkDigestReadonlyUsernameVMParamName;
+  final String zkDigestReadonlyPasswordVMParamName;
+  final Properties credentialsProps;
+
+  public VMParamsZkCredentialsInjector() {
+    this(
+        DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
+        DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
+        DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
+        DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
+  }
+
+  public VMParamsZkCredentialsInjector(
+      String zkDigestAllUsernameVMParamName,
+      String zkDigestAllPasswordVMParamName,
+      String zkDigestReadonlyUsernameVMParamName,
+      String zkDigestReadonlyPasswordVMParamName) {
+    this.zkDigestAllUsernameVMParamName = zkDigestAllUsernameVMParamName;
+    this.zkDigestAllPasswordVMParamName = zkDigestAllPasswordVMParamName;
+    this.zkDigestReadonlyUsernameVMParamName = zkDigestReadonlyUsernameVMParamName;
+    this.zkDigestReadonlyPasswordVMParamName = zkDigestReadonlyPasswordVMParamName;
+
+    String pathToFile = System.getProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
+    credentialsProps =
+        (pathToFile != null) ? readCredentialsFile(pathToFile) : System.getProperties();
+  }
+
+  public static Properties readCredentialsFile(String pathToFile) throws SolrException {
+    Properties props = new Properties();
+    try (Reader reader =
+        new InputStreamReader(new FileInputStream(pathToFile), StandardCharsets.UTF_8)) {
+      props.load(reader);
+    } catch (IOException ioExc) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ioExc);
+    }
+    return props;
+  }
+
+  @Override
+  public List<ZkCredential> getZkCredentials() {
+    final String digestAllUsername = credentialsProps.getProperty(zkDigestAllUsernameVMParamName);
+    final String digestAllPassword = credentialsProps.getProperty(zkDigestAllPasswordVMParamName);
+    final String digestReadonlyUsername =
+        credentialsProps.getProperty(zkDigestReadonlyUsernameVMParamName);
+    final String digestReadonlyPassword =
+        credentialsProps.getProperty(zkDigestReadonlyPasswordVMParamName);
+
+    List<ZkCredential> zkCredentials =
+        new ArrayList<>(2) {
+          {
+            ZkCredential allUser =
+                new ZkCredential(digestAllUsername, digestAllPassword, Perms.ALL);
+            ZkCredential readUser =
+                new ZkCredential(digestReadonlyUsername, digestReadonlyPassword, Perms.READ);
+            add(allUser);
+            add(readUser);
+          }
+        };
+    log.info(
+        "Using zkCredentials: digestAllUsername: {}, digestReadonlyUsername: {}",
+        digestAllUsername,
+        digestReadonlyUsername);
+    return zkCredentials;
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java
index 4b3ad36ed42..9c1fab51766 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkACLProvider.java
@@ -22,4 +22,11 @@ import org.apache.zookeeper.data.ACL;
 public interface ZkACLProvider {
 
   List<ACL> getACLsToAdd(String zNodePath);
+
+  /**
+   * @param zkCredentialsInjector The ZkCredentialsInjector that injects ZK credentials
+   */
+  default void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector) {
+    // no-op
+  }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsInjector.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsInjector.java
new file mode 100755
index 00000000000..2a0a2d16b37
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsInjector.java
@@ -0,0 +1,94 @@
+/*
+ * 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.solr.common.cloud;
+
+import java.util.List;
+
+/**
+ * A class that retrieves Zookeeper credentials from some source to be injected into {@link
+ * DigestZkCredentialsProvider} and {@link DigestZkACLProvider}. The source here can be System
+ * Props, a file, a Secret Manager, or any other local or remote source.
+ */
+public interface ZkCredentialsInjector {
+
+  /**
+   * @return List of {@link ZkCredential}s representing Zookeeper credentials including the
+   *     username, the password and the permissions (ALL or READ)
+   */
+  List<ZkCredential> getZkCredentials();
+
+  class ZkCredential {
+
+    public enum Perms {
+      ALL,
+      READ
+    }
+
+    private String username;
+    private String password;
+    private String perms;
+
+    public ZkCredential() {}
+
+    public ZkCredential(String username, String password, Perms perms) {
+      this(username, password, String.valueOf(perms));
+    }
+
+    public ZkCredential(String username, String password, String perms) {
+      this.username = username;
+      this.password = password;
+      this.perms = perms;
+    }
+
+    public String getUsername() {
+      return username;
+    }
+
+    public String getPassword() {
+      return password;
+    }
+
+    public void setPassword(String password) {
+      this.password = password;
+    }
+
+    public String getPerms() {
+      return perms;
+    }
+
+    public boolean isAll() {
+      return Perms.ALL.toString().equalsIgnoreCase(perms);
+    }
+
+    public boolean isReadonly() {
+      return Perms.READ.toString().equalsIgnoreCase(perms);
+    }
+
+    @Override
+    public String toString() {
+      return "ZkCredential{"
+          + "username='"
+          + username
+          + '\''
+          + ", password=[hidden]"
+          + ", perms='"
+          + perms
+          + '\''
+          + '}';
+    }
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsProvider.java
index b34998d32a0..8614ce6880c 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCredentialsProvider.java
@@ -20,7 +20,7 @@ import java.util.Collection;
 
 public interface ZkCredentialsProvider {
 
-  public class ZkCredentials {
+  class ZkCredentials {
     String scheme;
     byte[] auth;
 
@@ -40,4 +40,6 @@ public interface ZkCredentialsProvider {
   }
 
   Collection<ZkCredentials> getCredentials();
+
+  void setZkCredentialsInjector(ZkCredentialsInjector zkCredentialsInjector);
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDigestZkACLAndCredentialsProvidersTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDigestZkACLAndCredentialsProvidersTestBase.java
new file mode 100644
index 00000000000..fae427ca8a0
--- /dev/null
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDigestZkACLAndCredentialsProvidersTestBase.java
@@ -0,0 +1,569 @@
+/*
+ * 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.solr.cloud;
+
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_FILE_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME;
+import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.cloud.DigestZkACLProvider;
+import org.apache.solr.common.cloud.DigestZkCredentialsProvider;
+import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.VMParamsZkCredentialsInjector;
+import org.apache.solr.common.cloud.ZkCredentialsInjector;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException.NoAuthException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AbstractDigestZkACLAndCredentialsProvidersTestBase extends SolrTestCaseJ4 {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final Charset DATA_ENCODING = StandardCharsets.UTF_8;
+
+  private static final String ALL_USERNAME = "connectAndAllACLUsername";
+  private static final String ALL_PASSWORD = "connectAndAllACLPassword";
+  private static final String READONLY_USERNAME = "readonlyACLUsername";
+  private static final String READONLY_PASSWORD = "readonlyACLPassword";
+
+  public static final String SECRET_NAME = "zkCredentialsSecret";
+
+  protected ZkTestServer zkServer;
+
+  protected Path zkDir;
+
+  @BeforeClass
+  public static void beforeClass() {
+    System.setProperty("solrcloud.skip.autorecovery", "true");
+  }
+
+  @AfterClass
+  public static void afterClass() {
+    System.clearProperty("solrcloud.skip.autorecovery");
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    // TODO: Does all of this setup need to happen for each test case, or can it be done once for
+    // the class? (i.e. @BeforeClass) and maybe some minor reset logic in setup, instead of full
+    // startup and teardown of a new ZkTestServer in each cycle?
+    super.setUp();
+    if (log.isInfoEnabled()) {
+      log.info("####SETUP_START {}", getTestName());
+    }
+    createTempDir();
+
+    zkDir = createTempDir().resolve("zookeeper/server1/data");
+    log.info("ZooKeeper dataDir:{}", zkDir);
+    setSecuritySystemProperties();
+    zkServer = new ZkTestServer(zkDir);
+    zkServer.run(false);
+
+    System.setProperty("zkHost", zkServer.getZkAddress());
+
+    setDigestZkSystemProps();
+    System.setProperty(
+        SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME,
+        AllAndReadonlyCredentialZkCredentialsInjector.class.getName());
+
+    SolrZkClient zkClient =
+        new SolrZkClient(
+            zkServer.getZkHost(), AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT);
+
+    zkClient.makePath("/solr", false, true);
+    zkClient.close();
+
+    zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
+    zkClient.create(
+        "/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
+    zkClient.makePath(
+        "/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
+
+    zkClient.create(
+        SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH,
+        "content".getBytes(DATA_ENCODING),
+        CreateMode.PERSISTENT,
+        false);
+    zkClient.close();
+
+    clearSecuritySystemProperties();
+
+    zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
+    // Currently, no credentials on ZK connection, because those same VM-params are used for adding
+    // ACLs, and here we want
+    // no (or completely open) ACLs added. Therefore, hack your way into being authorized for
+    // creating anyway
+    zkClient
+        .getZooKeeper()
+        .addAuthInfo(
+            "digest", (ALL_USERNAME + ":" + ALL_PASSWORD).getBytes(StandardCharsets.UTF_8));
+    zkClient.create(
+        "/unprotectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
+    zkClient.makePath(
+        "/unprotectedMakePathNode",
+        "content".getBytes(DATA_ENCODING),
+        CreateMode.PERSISTENT,
+        false);
+    zkClient.close();
+
+    setDigestZkSystemProps();
+    if (log.isInfoEnabled()) {
+      log.info("####SETUP_END {}", getTestName());
+    }
+  }
+
+  private void setDigestZkSystemProps() {
+    System.setProperty(
+        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
+        DigestZkCredentialsProvider.class.getName());
+    System.setProperty(
+        SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME, DigestZkACLProvider.class.getName());
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    zkServer.shutdown();
+
+    clearSecuritySystemProperties();
+
+    super.tearDown();
+  }
+
+  @Test
+  public void testNoCredentials() throws Exception {
+    List<TestZkCredentialsInjector> testZkCredentialsInjectors =
+        new ArrayList<>() {
+          {
+            add(new TestZkCredentialsInjector(NoCredentialZkCredentialsInjector.class));
+            add(new TestZkCredentialsInjector(VMParamsZkCredentialsInjector.class));
+          }
+        };
+
+    testInjectors(
+        testZkCredentialsInjectors,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false);
+  }
+
+  @Test
+  public void testWrongCredentials() throws Exception {
+    List<TestZkCredentialsInjector> testZkCredentialsInjectors =
+        new ArrayList<>() {
+          {
+            add(new TestZkCredentialsInjector(WrongAllCredentialZkCredentialsInjector.class));
+            add(
+                new TestZkCredentialsInjector(
+                    VMParamsZkCredentialsInjector.class,
+                    Arrays.asList(
+                        DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
+                        DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME),
+                    Arrays.asList(ALL_USERNAME, ALL_PASSWORD + "Wrong")));
+          }
+        };
+
+    testInjectors(
+        testZkCredentialsInjectors,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false);
+  }
+
+  @Test
+  public void testAllCredentials() throws Exception {
+    List<TestZkCredentialsInjector> testZkCredentialsInjectors =
+        new ArrayList<>() {
+          {
+            add(new TestZkCredentialsInjector(AllCredentialZkCredentialsInjector.class));
+            add(
+                new TestZkCredentialsInjector(
+                    VMParamsZkCredentialsInjector.class,
+                    Arrays.asList(
+                        DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
+                        DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME),
+                    Arrays.asList(ALL_USERNAME, ALL_PASSWORD)));
+          }
+        };
+
+    testInjectors(
+        testZkCredentialsInjectors, true, true, true, true, true, true, true, true, true, true);
+  }
+
+  @Test
+  public void testReadonlyCredentials() throws Exception {
+    List<TestZkCredentialsInjector> testZkCredentialsInjectors =
+        new ArrayList<>() {
+          {
+            add(new TestZkCredentialsInjector(ConnectWithReadonlyCredsInjector.class));
+            add(
+                new TestZkCredentialsInjector(
+                    VMParamsZkCredentialsInjector.class,
+                    Arrays.asList(
+                        DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
+                        DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME),
+                    Arrays.asList(READONLY_USERNAME, READONLY_PASSWORD)));
+          }
+        };
+    testInjectors(
+        testZkCredentialsInjectors,
+        true,
+        true,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false);
+  }
+
+  protected void testInjectors(
+      List<TestZkCredentialsInjector> testZkCredentialsInjectors,
+      boolean getData,
+      boolean list,
+      boolean create,
+      boolean setData,
+      boolean delete,
+      boolean secureGet,
+      boolean secureList,
+      boolean secureCreate,
+      boolean secureSet,
+      boolean secureDelete)
+      throws Exception {
+    for (TestZkCredentialsInjector testZkCredentialsInjector : testZkCredentialsInjectors) {
+      tearDown();
+      setUp();
+      testZkCredentialsInjector.setSystemProps();
+      try (SolrZkClient zkClient =
+          new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+        doTest(
+            zkClient,
+            getData,
+            list,
+            create,
+            setData,
+            delete,
+            secureGet,
+            secureList,
+            secureCreate,
+            secureSet,
+            secureDelete);
+      }
+    }
+  }
+
+  @Test
+  public void testRepairACL() throws Exception {
+    clearSecuritySystemProperties();
+    try (SolrZkClient zkClient =
+        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+      // Currently, no credentials on ZK connection, because those same VM-params are used for
+      // adding ACLs, and here we want
+      // no (or completely open) ACLs added. Therefore, hack your way into being authorized for
+      // creating anyway
+      zkClient
+          .getZooKeeper()
+          .addAuthInfo(
+              "digest",
+              ("connectAndAllACLUsername:connectAndAllACLPassword")
+                  .getBytes(StandardCharsets.UTF_8));
+
+      zkClient.create(
+          "/security.json", "{}".getBytes(StandardCharsets.UTF_8), CreateMode.PERSISTENT, false);
+      assertEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false));
+    }
+
+    setSecuritySystemProperties();
+    try (SolrZkClient zkClient =
+        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+      ZkController.createClusterZkNodes(zkClient);
+      assertNotEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false));
+    }
+
+    useZkCredentialsInjector(ConnectWithReadonlyCredsInjector.class);
+    // useReadonlyCredentials();
+    try (SolrZkClient zkClient =
+        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+      NoAuthException e =
+          assertThrows(
+              NoAuthException.class, () -> zkClient.getData("/security.json", null, null, false));
+      assertEquals("/security.json", e.getPath());
+    }
+  }
+
+  private void useZkCredentialsInjector(Class<?> zkCredentialsInjectorClass) {
+    clearSecuritySystemProperties();
+    setDigestZkSystemProps();
+    System.setProperty(
+        SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME,
+        zkCredentialsInjectorClass.getName());
+  }
+
+  private void setSecuritySystemProperties() {
+    System.setProperty(
+        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
+        DigestZkCredentialsProvider.class.getName());
+    System.setProperty(
+        SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME, DigestZkACLProvider.class.getName());
+    System.setProperty(
+        SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME,
+        AllAndReadonlyCredentialZkCredentialsInjector.class.getName());
+  }
+
+  private void clearSecuritySystemProperties() {
+    System.clearProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME);
+    System.clearProperty(SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME);
+    System.clearProperty(SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME);
+  }
+
+  public static void doTest(
+      SolrZkClient zkClient,
+      boolean getData,
+      boolean list,
+      boolean create,
+      boolean setData,
+      boolean delete,
+      boolean secureGet,
+      boolean secureList,
+      boolean secureCreate,
+      boolean secureSet,
+      boolean secureDelete)
+      throws Exception {
+    doTest(zkClient, "/protectedCreateNode", getData, list, create, setData, delete);
+    doTest(zkClient, "/protectedMakePathNode", getData, list, create, setData, delete);
+    doTest(zkClient, "/unprotectedCreateNode", true, true, true, true, delete);
+    doTest(zkClient, "/unprotectedMakePathNode", true, true, true, true, delete);
+    doTest(
+        zkClient,
+        SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH,
+        secureGet,
+        secureList,
+        secureCreate,
+        secureSet,
+        secureDelete);
+  }
+
+  protected static void doTest(
+      SolrZkClient zkClient,
+      String path,
+      boolean getData,
+      boolean list,
+      boolean create,
+      boolean setData,
+      boolean delete)
+      throws Exception {
+    doTest(getData, () -> zkClient.getData(path, null, null, false));
+    doTest(list, () -> zkClient.getChildren(path, null, false));
+
+    doTest(
+        create,
+        () -> {
+          zkClient.create(path + "/subnode", null, CreateMode.PERSISTENT, false);
+          zkClient.delete(path + "/subnode", -1, false);
+        });
+    doTest(
+        create,
+        () -> {
+          zkClient.makePath(path + "/subnode/subsubnode", false);
+          zkClient.delete(path + "/subnode/subsubnode", -1, false);
+          zkClient.delete(path + "/subnode", -1, false);
+        });
+
+    doTest(setData, () -> zkClient.setData(path, (byte[]) null, false));
+
+    // Actually about the ACLs on /solr, but that is protected
+    doTest(delete, () -> zkClient.delete(path, -1, false));
+  }
+
+  interface ExceptingRunnable {
+    void run() throws Exception;
+  }
+
+  private static void doTest(boolean shouldSucceed, ExceptingRunnable action) throws Exception {
+    if (shouldSucceed) {
+      action.run();
+    } else {
+      expectThrows(NoAuthException.class, action::run);
+    }
+  }
+
+  @Test
+  public void testVMParamsAllCredentialsFromFile() throws Exception {
+    useVMParamsAllCredentialsFromFile();
+
+    try (SolrZkClient zkClient =
+        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+      doTest(zkClient, true, true, true, true, true, true, true, true, true, true);
+    }
+  }
+
+  @Test
+  public void testVMParamsReadonlyCredentialsFromFile() throws Exception {
+    useVMParamsReadonlyCredentialsFromFile();
+
+    try (SolrZkClient zkClient =
+        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
+      doTest(zkClient, true, true, false, false, false, false, false, false, false, false);
+    }
+  }
+
+  private void useVMParamsAllCredentialsFromFile() throws IOException {
+    useVMParamsCredentialsFromFile("connectAndAllACLUsername", "connectAndAllACLPassword");
+  }
+
+  private void useVMParamsReadonlyCredentialsFromFile() throws IOException {
+    useVMParamsCredentialsFromFile("readonlyACLUsername", "readonlyACLPassword");
+  }
+
+  private void useVMParamsCredentialsFromFile(String username, String password) throws IOException {
+    Properties props = new Properties();
+    props.setProperty(
+        VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, username);
+    props.setProperty(
+        VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, password);
+    String credsFile = saveCredentialsFile(props);
+
+    useZkCredentialsInjector(VMParamsZkCredentialsInjector.class);
+    System.setProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME, credsFile);
+  }
+
+  private String saveCredentialsFile(Properties props) throws IOException {
+    Path tmp = createTempFile("zk-creds", "properties");
+    try (FileWriter w = new FileWriter(tmp.toFile(), StandardCharsets.UTF_8)) {
+      props.store(w, "test");
+    }
+    return tmp.toAbsolutePath().toString();
+  }
+
+  public static class NoCredentialZkCredentialsInjector implements ZkCredentialsInjector {
+    @Override
+    public List<ZkCredential> getZkCredentials() {
+      return Collections.emptyList();
+    }
+  }
+
+  public static class AllAndReadonlyCredentialZkCredentialsInjector
+      implements ZkCredentialsInjector {
+    @Override
+    public List<ZkCredential> getZkCredentials() {
+      return new ArrayList<>() {
+        {
+          add(new ZkCredential(ALL_USERNAME, ALL_PASSWORD, ZkCredential.Perms.ALL));
+          add(new ZkCredential(READONLY_USERNAME, READONLY_PASSWORD, ZkCredential.Perms.READ));
+        }
+      };
+    }
+  }
+
+  public static class ConnectWithReadonlyCredsInjector implements ZkCredentialsInjector {
+    @Override
+    public List<ZkCredential> getZkCredentials() {
+      return new ArrayList<>() {
+        {
+          // uses readonly creds to connect to zookeeper, hence "all"
+          add(new ZkCredential(READONLY_USERNAME, READONLY_PASSWORD, ZkCredential.Perms.ALL));
+        }
+      };
+    }
+  }
+
+  public static class AllCredentialZkCredentialsInjector implements ZkCredentialsInjector {
+    @Override
+    public List<ZkCredential> getZkCredentials() {
+      return new ArrayList<>() {
+        {
+          add(new ZkCredential(ALL_USERNAME, ALL_PASSWORD, ZkCredential.Perms.ALL));
+        }
+      };
+    }
+  }
+
+  public static class WrongAllCredentialZkCredentialsInjector implements ZkCredentialsInjector {
+    @Override
+    public List<ZkCredential> getZkCredentials() {
+      return new ArrayList<>() {
+        {
+          add(new ZkCredential(ALL_USERNAME, ALL_PASSWORD + "Wrong", ZkCredential.Perms.ALL));
+        }
+      };
+    }
+  }
+
+  class TestZkCredentialsInjector {
+    private final Class<?> zkCredentialsInjectorClass;
+    private final List<String> systemPropsKeys;
+    private final List<String> systemPropsValues;
+
+    public TestZkCredentialsInjector(Class<?> zkCredentialsInjectorClass) {
+      this(zkCredentialsInjectorClass, Collections.emptyList(), Collections.emptyList());
+    }
+
+    public TestZkCredentialsInjector(
+        Class<?> zkCredentialsInjectorClass,
+        List<String> systemPropsKeys,
+        List<String> systemPropsValues) {
+      this.zkCredentialsInjectorClass = zkCredentialsInjectorClass;
+      this.systemPropsKeys = systemPropsKeys;
+      this.systemPropsValues = systemPropsValues;
+    }
+
+    private void setSystemProps() {
+      clearSecuritySystemProperties();
+      setDigestZkSystemProps();
+      System.setProperty(
+          SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME,
+          zkCredentialsInjectorClass.getName());
+      int i = 0;
+      for (String key : systemPropsKeys) {
+        System.setProperty(key, systemPropsValues.get(i++));
+      }
+    }
+  }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractVMParamsZkACLAndCredentialsProvidersTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractVMParamsZkACLAndCredentialsProvidersTestBase.java
deleted file mode 100644
index 21e9b134e12..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractVMParamsZkACLAndCredentialsProvidersTestBase.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * 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.solr.cloud;
-
-import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;
-
-import java.io.FileWriter;
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.util.Properties;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
-import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider;
-import org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider;
-import org.apache.zookeeper.CreateMode;
-import org.apache.zookeeper.KeeperException.NoAuthException;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class AbstractVMParamsZkACLAndCredentialsProvidersTestBase extends SolrTestCaseJ4 {
-
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private static final Charset DATA_ENCODING = StandardCharsets.UTF_8;
-
-  protected ZkTestServer zkServer;
-
-  protected Path zkDir;
-
-  @BeforeClass
-  public static void beforeClass() {
-    System.setProperty("solrcloud.skip.autorecovery", "true");
-  }
-
-  @AfterClass
-  public static void afterClass() throws InterruptedException {
-    System.clearProperty("solrcloud.skip.autorecovery");
-  }
-
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    if (log.isInfoEnabled()) {
-      log.info("####SETUP_START {}", getTestName());
-    }
-    createTempDir();
-
-    zkDir = createTempDir().resolve("zookeeper/server1/data");
-    log.info("ZooKeeper dataDir:{}", zkDir);
-    zkServer = new ZkTestServer(zkDir);
-    zkServer.run(false);
-
-    System.setProperty("zkHost", zkServer.getZkAddress());
-
-    setSecuritySystemProperties();
-
-    SolrZkClient zkClient =
-        new SolrZkClient(
-            zkServer.getZkHost(), AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT);
-    zkClient.makePath("/solr", false, true);
-    zkClient.close();
-
-    zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
-    zkClient.create(
-        "/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
-    zkClient.makePath(
-        "/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
-
-    zkClient.create(
-        SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH,
-        "content".getBytes(DATA_ENCODING),
-        CreateMode.PERSISTENT,
-        false);
-    zkClient.close();
-
-    clearSecuritySystemProperties();
-
-    zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
-    // Currently no credentials on ZK connection, because those same VM-params are used for adding
-    // ACLs, and here we want
-    // no (or completely open) ACLs added. Therefore hack your way into being authorized for
-    // creating anyway
-    zkClient
-        .getZooKeeper()
-        .addAuthInfo(
-            "digest",
-            ("connectAndAllACLUsername:connectAndAllACLPassword").getBytes(StandardCharsets.UTF_8));
-    zkClient.create(
-        "/unprotectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
-    zkClient.makePath(
-        "/unprotectedMakePathNode",
-        "content".getBytes(DATA_ENCODING),
-        CreateMode.PERSISTENT,
-        false);
-    zkClient.close();
-
-    if (log.isInfoEnabled()) {
-      log.info("####SETUP_END {}", getTestName());
-    }
-  }
-
-  @Override
-  public void tearDown() throws Exception {
-    zkServer.shutdown();
-
-    clearSecuritySystemProperties();
-
-    super.tearDown();
-  }
-
-  @Test
-  public void testNoCredentials() throws Exception {
-    useNoCredentials();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, false, false, false, false, false, false, false, false, false, false);
-    }
-  }
-
-  @Test
-  public void testWrongCredentials() throws Exception {
-    useWrongCredentials();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, false, false, false, false, false, false, false, false, false, false);
-    }
-  }
-
-  @Test
-  public void testAllCredentials() throws Exception {
-    useAllCredentials();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, true, true, true, true, true, true, true, true, true, true);
-    }
-  }
-
-  @Test
-  public void testReadonlyCredentials() throws Exception {
-    useReadonlyCredentials();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, true, true, false, false, false, false, false, false, false, false);
-    }
-  }
-
-  @Test
-  public void testReadonlyCredentialsFromFile() throws Exception {
-    useReadonlyCredentialsFromFile();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, true, true, false, false, false, false, false, false, false, false);
-    }
-  }
-
-  @Test
-  public void testAllCredentialsFromFile() throws Exception {
-    useAllCredentialsFromFile();
-
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      doTest(zkClient, true, true, true, true, true, true, true, true, true, true);
-    }
-  }
-
-  @Test
-  public void testRepairACL() throws Exception {
-    clearSecuritySystemProperties();
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      // Currently no credentials on ZK connection, because those same VM-params are used for adding
-      // ACLs, and here we want
-      // no (or completely open) ACLs added. Therefore hack your way into being authorized for
-      // creating anyway
-      zkClient
-          .getZooKeeper()
-          .addAuthInfo(
-              "digest",
-              ("connectAndAllACLUsername:connectAndAllACLPassword")
-                  .getBytes(StandardCharsets.UTF_8));
-
-      zkClient.create(
-          "/security.json", "{}".getBytes(StandardCharsets.UTF_8), CreateMode.PERSISTENT, false);
-      assertEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false));
-    }
-
-    setSecuritySystemProperties();
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      ZkController.createClusterZkNodes(zkClient);
-      assertNotEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false));
-    }
-
-    useReadonlyCredentials();
-    try (SolrZkClient zkClient =
-        new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) {
-      NoAuthException e =
-          assertThrows(
-              NoAuthException.class, () -> zkClient.getData("/security.json", null, null, false));
-      assertEquals("/security.json", e.getPath());
-    }
-  }
-
-  public static void doTest(
-      SolrZkClient zkClient,
-      boolean getData,
-      boolean list,
-      boolean create,
-      boolean setData,
-      boolean delete,
-      boolean secureGet,
-      boolean secureList,
-      boolean secureCreate,
-      boolean secureSet,
-      boolean secureDelete)
-      throws Exception {
-    doTest(zkClient, "/protectedCreateNode", getData, list, create, setData, delete);
-    doTest(zkClient, "/protectedMakePathNode", getData, list, create, setData, delete);
-    doTest(zkClient, "/unprotectedCreateNode", true, true, true, true, delete);
-    doTest(zkClient, "/unprotectedMakePathNode", true, true, true, true, delete);
-    doTest(
-        zkClient,
-        SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH,
-        secureGet,
-        secureList,
-        secureCreate,
-        secureSet,
-        secureDelete);
-  }
-
-  protected static void doTest(
-      SolrZkClient zkClient,
-      String path,
-      boolean getData,
-      boolean list,
-      boolean create,
-      boolean setData,
-      boolean delete)
-      throws Exception {
-    doTest(getData, () -> zkClient.getData(path, null, null, false));
-    doTest(list, () -> zkClient.getChildren(path, null, false));
-
-    doTest(
-        create,
-        () -> {
-          zkClient.create(path + "/subnode", null, CreateMode.PERSISTENT, false);
-          zkClient.delete(path + "/subnode", -1, false);
-        });
-    doTest(
-        create,
-        () -> {
-          zkClient.makePath(path + "/subnode/subsubnode", false);
-          zkClient.delete(path + "/subnode/subsubnode", -1, false);
-          zkClient.delete(path + "/subnode", -1, false);
-        });
-
-    doTest(setData, () -> zkClient.setData(path, (byte[]) null, false));
-
-    // Actually about the ACLs on /solr, but that is protected
-    doTest(delete, () -> zkClient.delete(path, -1, false));
-  }
-
-  interface ExceptingRunnable {
-    void run() throws Exception;
-  }
-
-  private static void doTest(boolean shouldSucceed, ExceptingRunnable action) throws Exception {
-    if (shouldSucceed) {
-      action.run();
-    } else {
-      expectThrows(NoAuthException.class, action::run);
-    }
-  }
-
-  private void useNoCredentials() {
-    clearSecuritySystemProperties();
-  }
-
-  private void useWrongCredentials() {
-    clearSecuritySystemProperties();
-
-    System.setProperty(
-        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName());
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPasswordWrong");
-  }
-
-  private void useAllCredentials() {
-    clearSecuritySystemProperties();
-
-    System.setProperty(
-        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName());
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPassword");
-  }
-
-  private void useReadonlyCredentials() {
-    clearSecuritySystemProperties();
-
-    System.setProperty(
-        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName());
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "readonlyACLUsername");
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "readonlyACLPassword");
-  }
-
-  private void useReadonlyCredentialsFromFile() throws IOException {
-    useCredentialsFromFile("readonlyACLUsername", "readonlyACLPassword");
-  }
-
-  private void useAllCredentialsFromFile() throws IOException {
-    useCredentialsFromFile("connectAndAllACLUsername", "connectAndAllACLPassword");
-  }
-
-  private void useCredentialsFromFile(String username, String password) throws IOException {
-    clearSecuritySystemProperties();
-
-    System.setProperty(
-        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName());
-
-    Properties props = new Properties();
-    props.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        username);
-    props.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        password);
-    saveCredentialsFile(props);
-  }
-
-  private void saveCredentialsFile(Properties props) throws IOException {
-    Path tmp = createTempFile("zk-creds", "properties");
-    try (FileWriter w = new FileWriter(tmp.toFile(), StandardCharsets.UTF_8)) {
-      props.store(w, "test");
-    }
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME,
-        tmp.toAbsolutePath().toString());
-  }
-
-  private void setSecuritySystemProperties() {
-    System.setProperty(
-        SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsAllAndReadonlyDigestZkACLProvider.class.getName());
-    System.setProperty(
-        SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName());
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME,
-        "connectAndAllACLUsername");
-    System.setProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME,
-        "connectAndAllACLPassword");
-    System.setProperty(
-        VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME,
-        "readonlyACLUsername");
-    System.setProperty(
-        VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME,
-        "readonlyACLPassword");
-  }
-
-  private void clearSecuritySystemProperties() {
-    System.clearProperty(SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME);
-    System.clearProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME);
-    System.clearProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME);
-    System.clearProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider
-            .DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
-    System.clearProperty(
-        VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME);
-    System.clearProperty(
-        VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME);
-    System.clearProperty(
-        VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
-  }
-}