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

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


The following commit(s) were added to refs/heads/main by this push:
     new ddfe2f1b444 SOLR-14496: Add Basic Auth support to SolrCLI (#1954)
ddfe2f1b444 is described below

commit ddfe2f1b4444ed7d019360d5d675dff3edb3e1e7
Author: Eric Pugh <[email protected]>
AuthorDate: Tue Oct 17 06:43:55 2023 -0400

    SOLR-14496: Add Basic Auth support to SolrCLI (#1954)
    
    Introduce parameters to the various Solr CLI commands (bin/solr) that allow 
them to interact with a Solr that is protected with Basic Authentication.
---
 solr/CHANGES.txt                                   |   2 +-
 .../core/src/java/org/apache/solr/cli/ApiTool.java |   9 +-
 .../src/java/org/apache/solr/cli/AssertTool.java   |  63 ++++--
 .../src/java/org/apache/solr/cli/AuthTool.java     |  15 +-
 .../src/java/org/apache/solr/cli/ConfigTool.java   |   7 +-
 .../src/java/org/apache/solr/cli/CreateTool.java   |  21 +-
 .../src/java/org/apache/solr/cli/DeleteTool.java   |  14 +-
 .../src/java/org/apache/solr/cli/ExportTool.java   |  28 ++-
 .../java/org/apache/solr/cli/HealthcheckTool.java  |   9 +-
 .../src/java/org/apache/solr/cli/PackageTool.java  |  11 +-
 .../src/java/org/apache/solr/cli/PostLogsTool.java |  11 +-
 .../src/java/org/apache/solr/cli/PostTool.java     |  18 +-
 .../java/org/apache/solr/cli/RunExampleTool.java   |  38 ++--
 .../java/org/apache/solr/cli/SimplePostTool.java   |  33 ++-
 .../core/src/java/org/apache/solr/cli/SolrCLI.java |  72 +++++--
 .../java/org/apache/solr/cli/SolrLogPostTool.java  |   2 +-
 .../src/java/org/apache/solr/cli/StatusTool.java   |  36 ++--
 .../src/test/org/apache/solr/cli/ApiToolTest.java  |   7 +-
 .../test/org/apache/solr/cli/CreateToolTest.java   |  94 +++++++++
 .../test/org/apache/solr/cli/DeleteToolTest.java   | 132 ++++++++++++
 .../PackageToolTest.java}                          | 228 +++++++++++++++++----
 .../src/test/org/apache/solr/cli/PostToolTest.java |  69 ++++++-
 .../src/test/org/apache/solr/cli/SolrCLITest.java  |   3 +
 .../test/org/apache/solr/cli/TestExportTool.java   |  16 +-
 .../org/apache/solr/cli/TestSolrCLIRunExample.java |   2 +-
 .../apache/solr/cloud/SolrCloudExampleTest.java    |   2 +-
 solr/packaging/test/test_basic_auth.bats           |  92 +++++++++
 solr/packaging/test/test_bats.bats                 |   2 +-
 .../configuration-guide/pages/package-manager.adoc |   2 +
 .../pages/solr-control-script-reference.adoc       |  83 +++++++-
 .../modules/indexing-guide/pages/post-tool.adoc    |   2 +-
 .../query-guide/pages/document-transformers.adoc   |   2 +-
 .../modules/query-guide/pages/logs.adoc            |  14 +-
 .../solr/client/solrj/impl/Http2SolrClient.java    |  20 ++
 .../client/solrj/impl/Http2SolrClientTest.java     |  65 ++++++
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |  18 +-
 36 files changed, 1025 insertions(+), 217 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index edbbfd0b9ec..5feaa5bf4e0 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -71,7 +71,7 @@ Other Changes
 ==================  9.5.0 ==================
 New Features
 ---------------------
-(No changes)
+* SOLR-14496: Solr CLI commands now can interact with a Solr secured using 
Basic Authentication.  (Eric Pugh)
 
 Improvements
 ---------------------
diff --git a/solr/core/src/java/org/apache/solr/cli/ApiTool.java 
b/solr/core/src/java/org/apache/solr/cli/ApiTool.java
index 8ecfcd36a40..2b760cc7f76 100644
--- a/solr/core/src/java/org/apache/solr/cli/ApiTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ApiTool.java
@@ -57,7 +57,8 @@ public class ApiTool extends ToolBase {
             .hasArg()
             .required(true)
             .desc("Send a GET request to a Solr API endpoint.")
-            .build());
+            .build(),
+        SolrCLI.OPTION_CREDENTIALS);
   }
 
   @Override
@@ -65,7 +66,7 @@ public class ApiTool extends ToolBase {
     String response = null;
     String getUrl = cli.getOptionValue("get");
     if (getUrl != null) {
-      response = callGet(getUrl);
+      response = callGet(getUrl, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     }
     if (response != null) {
       // pretty-print the response to stdout
@@ -73,11 +74,11 @@ public class ApiTool extends ToolBase {
     }
   }
 
-  protected String callGet(String url) throws Exception {
+  protected String callGet(String url, String credentials) throws Exception {
     URI uri = new URI(url.replace("+", "%20"));
     String solrUrl = getSolrUrlFromUri(uri);
     String path = uri.getPath();
-    try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
+    try (var solrClient = SolrCLI.getSolrClient(solrUrl, credentials)) {
       // For path parameter we need the path without the root so from the 
second / char
       // (because root can be configured)
       // E.g URL is http://localhost:8983/solr/admin/info/system path is
diff --git a/solr/core/src/java/org/apache/solr/cli/AssertTool.java 
b/solr/core/src/java/org/apache/solr/cli/AssertTool.java
index 44b4dce1250..8976212778f 100644
--- a/solr/core/src/java/org/apache/solr/cli/AssertTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/AssertTool.java
@@ -34,8 +34,10 @@ import org.apache.solr.common.util.NamedList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Supports assert command in the bin/solr script. */
-/** Asserts various conditions and exists with error code if fails, else 
continues with no output */
+/**
+ * Supports assert command in the bin/solr script. Asserts various conditions 
and exists with error
+ * code if there are failures, else continues with no output.
+ */
 public class AssertTool extends ToolBase {
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private static String message = null;
@@ -123,6 +125,14 @@ public class AssertTool extends ToolBase {
         Option.builder("e")
             .desc("Return an exit code instead of printing error message on 
assert fail.")
             .longOpt("exitcode")
+            .build(),
+        // u was taken, can we change that instead?
+        Option.builder("credentials")
+            .argName("credentials")
+            .hasArg()
+            .required(false)
+            .desc(
+                "Credentials in the format username:password. Example: 
--credentials solr:SolrRocks")
             .build());
   }
 
@@ -190,24 +200,34 @@ public class AssertTool extends ToolBase {
       ret += sameUser(cli.getOptionValue("u"));
     }
     if (cli.hasOption("s")) {
-      ret += assertSolrRunning(cli.getOptionValue("s"));
+      ret +=
+          assertSolrRunning(
+              cli.getOptionValue("s"), 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     }
     if (cli.hasOption("S")) {
-      ret += assertSolrNotRunning(cli.getOptionValue("S"));
+      ret +=
+          assertSolrNotRunning(
+              cli.getOptionValue("S"), 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     }
     if (cli.hasOption("c")) {
-      ret += 
assertSolrRunningInCloudMode(SolrCLI.normalizeSolrUrl(cli.getOptionValue("c")));
+      ret +=
+          assertSolrRunningInCloudMode(
+              SolrCLI.normalizeSolrUrl(cli.getOptionValue("c")),
+              cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     }
     if (cli.hasOption("C")) {
-      ret += 
assertSolrNotRunningInCloudMode(SolrCLI.normalizeSolrUrl(cli.getOptionValue("C")));
+      ret +=
+          assertSolrNotRunningInCloudMode(
+              SolrCLI.normalizeSolrUrl(cli.getOptionValue("C")),
+              cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     }
     return ret;
   }
 
-  public static int assertSolrRunning(String url) throws Exception {
+  public static int assertSolrRunning(String url, String credentials) throws 
Exception {
     StatusTool status = new StatusTool();
     try {
-      status.waitToSeeSolrUp(url, timeoutMs, TimeUnit.MILLISECONDS);
+      status.waitToSeeSolrUp(url, credentials, timeoutMs, 
TimeUnit.MILLISECONDS);
     } catch (Exception se) {
       if (SolrCLI.exceptionIsAuthRelated(se)) {
         throw se;
@@ -222,11 +242,11 @@ public class AssertTool extends ToolBase {
     return 0;
   }
 
-  public static int assertSolrNotRunning(String url) throws Exception {
+  public static int assertSolrNotRunning(String url, String credentials) 
throws Exception {
     StatusTool status = new StatusTool();
     long timeout =
         System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutMs, 
TimeUnit.MILLISECONDS);
-    try (SolrClient solrClient = SolrCLI.getSolrClient(url)) {
+    try (SolrClient solrClient = SolrCLI.getSolrClient(url, credentials)) {
       NamedList<Object> response = solrClient.request(new 
HealthCheckRequest());
       Integer statusCode = (Integer) response.findRecursive("responseHeader", 
"status");
       SolrCLI.checkCodeForAuthError(statusCode);
@@ -236,7 +256,7 @@ public class AssertTool extends ToolBase {
     }
     while (System.nanoTime() < timeout) {
       try {
-        status.waitToSeeSolrUp(url, 1, TimeUnit.SECONDS);
+        status.waitToSeeSolrUp(url, credentials, 1, TimeUnit.SECONDS);
         try {
           log.debug("Solr still up. Waiting before trying again to see if it 
was stopped");
           Thread.sleep(1000L);
@@ -258,8 +278,8 @@ public class AssertTool extends ToolBase {
             + " seconds");
   }
 
-  public static int assertSolrRunningInCloudMode(String url) throws Exception {
-    if (!isSolrRunningOn(url)) {
+  public static int assertSolrRunningInCloudMode(String url, String 
credentials) throws Exception {
+    if (!isSolrRunningOn(url, credentials)) {
       return exitOrException(
           "Solr is not running on url "
               + url
@@ -268,14 +288,15 @@ public class AssertTool extends ToolBase {
               + " seconds");
     }
 
-    if (!runningSolrIsCloud(url)) {
+    if (!runningSolrIsCloud(url, credentials)) {
       return exitOrException("Solr is not running in cloud mode on " + url);
     }
     return 0;
   }
 
-  public static int assertSolrNotRunningInCloudMode(String url) throws 
Exception {
-    if (!isSolrRunningOn(url)) {
+  public static int assertSolrNotRunningInCloudMode(String url, String 
credentials)
+      throws Exception {
+    if (!isSolrRunningOn(url, credentials)) {
       return exitOrException(
           "Solr is not running on url "
               + url
@@ -284,7 +305,7 @@ public class AssertTool extends ToolBase {
               + " seconds");
     }
 
-    if (runningSolrIsCloud(url)) {
+    if (runningSolrIsCloud(url, credentials)) {
       return exitOrException("Solr is not running in standalone mode on " + 
url);
     }
     return 0;
@@ -352,10 +373,10 @@ public class AssertTool extends ToolBase {
     }
   }
 
-  private static boolean isSolrRunningOn(String url) throws Exception {
+  private static boolean isSolrRunningOn(String url, String credentials) 
throws Exception {
     StatusTool status = new StatusTool();
     try {
-      status.waitToSeeSolrUp(url, timeoutMs, TimeUnit.MILLISECONDS);
+      status.waitToSeeSolrUp(url, credentials, timeoutMs, 
TimeUnit.MILLISECONDS);
       return true;
     } catch (Exception se) {
       if (SolrCLI.exceptionIsAuthRelated(se)) {
@@ -365,8 +386,8 @@ public class AssertTool extends ToolBase {
     }
   }
 
-  private static boolean runningSolrIsCloud(String url) throws Exception {
-    try (final SolrClient client = SolrCLI.getSolrClient(url)) {
+  private static boolean runningSolrIsCloud(String url, String credentials) 
throws Exception {
+    try (final SolrClient client = SolrCLI.getSolrClient(url, credentials)) {
       return SolrCLI.isCloudMode(client);
     }
   }
diff --git a/solr/core/src/java/org/apache/solr/cli/AuthTool.java 
b/solr/core/src/java/org/apache/solr/cli/AuthTool.java
index 2afd71706b6..abaeb0a84f9 100644
--- a/solr/core/src/java/org/apache/solr/cli/AuthTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/AuthTool.java
@@ -74,12 +74,7 @@ public class AuthTool extends ToolBase {
             .desc(
                 "The authentication mechanism to enable (basicAuth or 
kerberos). Defaults to 'basicAuth'.")
             .build(),
-        Option.builder("credentials")
-            .argName("credentials")
-            .hasArg()
-            .desc(
-                "Credentials in the format username:password. Example: 
-credentials solr:SolrRocks")
-            .build(),
+        SolrCLI.OPTION_CREDENTIALS,
         Option.builder("prompt")
             .argName("prompt")
             .hasArg()
@@ -292,8 +287,8 @@ public class AuthTool extends ToolBase {
               .printHelp("bin/solr auth <enable|disable> [OPTIONS]", 
SolrCLI.getToolOptions(this));
           SolrCLI.exit(1);
         } else if (!prompt
-            && (cli.getOptionValue("credentials") == null
-                || !cli.getOptionValue("credentials").contains(":"))) {
+            && (cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()) == 
null
+                || 
!cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()).contains(":"))) {
           CLIO.out("Option -credentials is not in correct format.");
           new HelpFormatter()
               .printHelp("bin/solr auth <enable|disable> [OPTIONS]", 
SolrCLI.getToolOptions(this));
@@ -337,8 +332,8 @@ public class AuthTool extends ToolBase {
         }
 
         String username, password;
-        if (cli.hasOption("credentials")) {
-          String credentials = cli.getOptionValue("credentials");
+        if (cli.hasOption(SolrCLI.OPTION_CREDENTIALS.getLongOpt())) {
+          String credentials = 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt());
           username = credentials.split(":")[0];
           password = credentials.split(":")[1];
         } else {
diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigTool.java 
b/solr/core/src/java/org/apache/solr/cli/ConfigTool.java
index 5221a28325f..39ed60697c0 100644
--- a/solr/core/src/java/org/apache/solr/cli/ConfigTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ConfigTool.java
@@ -79,7 +79,8 @@ public class ConfigTool extends ToolBase {
             .desc("Set the property to this value; accepts JSON objects and 
strings.")
             .build(),
         SolrCLI.OPTION_SOLRURL,
-        SolrCLI.OPTION_ZKHOST);
+        SolrCLI.OPTION_ZKHOST,
+        SolrCLI.OPTION_CREDENTIALS);
   }
 
   @Override
@@ -108,7 +109,9 @@ public class ConfigTool extends ToolBase {
     echo("\nPOSTing request to Config API: " + solrUrl + updatePath);
     echo(jsonBody);
 
-    try (SolrClient solrClient = SolrCLI.getSolrClient(solrUrl)) {
+    try (SolrClient solrClient =
+        SolrCLI.getSolrClient(
+            solrUrl, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
       NamedList<Object> result = SolrCLI.postJsonToSolr(solrClient, 
updatePath, jsonBody);
       Integer statusCode = (Integer) result.findRecursive("responseHeader", 
"status");
       if (statusCode == 0) {
diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java 
b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
index e62c42d185a..5eca1c272ed 100644
--- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
@@ -112,15 +112,15 @@ public class CreateTool extends ToolBase {
             .required(false)
             .desc("Configuration name; default is the collection name.")
             .build(),
+        SolrCLI.OPTION_CREDENTIALS,
         SolrCLI.OPTION_VERBOSE);
   }
 
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
-    String solrUrl = SolrCLI.normalizeSolrUrl(cli);
 
-    try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
+    try (var solrClient = SolrCLI.getSolrClient(cli)) {
       if (SolrCLI.isCloudMode(solrClient)) {
         createCollection(cli);
       } else {
@@ -155,7 +155,8 @@ public class CreateTool extends ToolBase {
     // convert raw JSON into user-friendly output
     coreRootDirectory = (String) systemInfo.get("core_root");
 
-    if (SolrCLI.safeCheckCoreExists(solrUrl, coreName)) {
+    if (SolrCLI.safeCheckCoreExists(
+        solrUrl, coreName, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
       throw new IllegalArgumentException(
           "\nCore '"
               + coreName
@@ -197,13 +198,16 @@ public class CreateTool extends ToolBase {
   }
 
   protected void createCollection(CommandLine cli) throws Exception {
+    Http2SolrClient.Builder builder =
+        new Http2SolrClient.Builder()
+            .withIdleTimeout(30, TimeUnit.SECONDS)
+            .withConnectionTimeout(15, TimeUnit.SECONDS)
+            .withOptionalBasicAuthCredentials(
+                cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
     String zkHost = SolrCLI.getZkHost(cli);
     try (CloudSolrClient cloudSolrClient =
         new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), 
Optional.empty())
-            .withInternalClientBuilder(
-                new Http2SolrClient.Builder()
-                    .withIdleTimeout(30, TimeUnit.SECONDS)
-                    .withConnectionTimeout(15, TimeUnit.SECONDS))
+            .withInternalClientBuilder(builder)
             .build()) {
       echoIfVerbose("Connecting to ZooKeeper at " + zkHost, cli);
       cloudSolrClient.connect();
@@ -275,7 +279,8 @@ public class CreateTool extends ToolBase {
     }
 
     // since creating a collection is a heavy-weight operation, check for 
existence first
-    if (SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
+    if (SolrCLI.safeCheckCollectionExists(
+        solrUrl, collectionName, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
       throw new IllegalStateException(
           "\nCollection '"
               + collectionName
diff --git a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java 
b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
index bae371226ae..f709c9defa2 100644
--- a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
@@ -84,6 +84,7 @@ public class DeleteTool extends ToolBase {
                 "Skip safety checks when deleting the configuration directory 
used by a collection.")
             .build(),
         SolrCLI.OPTION_ZKHOST,
+        SolrCLI.OPTION_CREDENTIALS,
         SolrCLI.OPTION_VERBOSE);
   }
 
@@ -92,7 +93,7 @@ public class DeleteTool extends ToolBase {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String solrUrl = SolrCLI.normalizeSolrUrl(cli);
 
-    try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
+    try (var solrClient = SolrCLI.getSolrClient(cli)) {
       if (SolrCLI.isCloudMode(solrClient)) {
         deleteCollection(cli);
       } else {
@@ -102,13 +103,16 @@ public class DeleteTool extends ToolBase {
   }
 
   protected void deleteCollection(CommandLine cli) throws Exception {
+    Http2SolrClient.Builder builder =
+        new Http2SolrClient.Builder()
+            .withIdleTimeout(30, TimeUnit.SECONDS)
+            .withConnectionTimeout(15, TimeUnit.SECONDS)
+            
.withOptionalBasicAuthCredentials(cli.getOptionValue(("credentials")));
+
     String zkHost = SolrCLI.getZkHost(cli);
     try (CloudSolrClient cloudSolrClient =
         new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), 
Optional.empty())
-            .withInternalClientBuilder(
-                new Http2SolrClient.Builder()
-                    .withIdleTimeout(30, TimeUnit.SECONDS)
-                    .withConnectionTimeout(15, TimeUnit.SECONDS))
+            .withInternalClientBuilder(builder)
             .build()) {
       echoIfVerbose("Connecting to ZooKeeper at " + zkHost, cli);
       cloudSolrClient.connect();
diff --git a/solr/core/src/java/org/apache/solr/cli/ExportTool.java 
b/solr/core/src/java/org/apache/solr/cli/ExportTool.java
index 9ea5435e071..e48d4ea5f91 100644
--- a/solr/core/src/java/org/apache/solr/cli/ExportTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ExportTool.java
@@ -126,11 +126,13 @@ public class ExportTool extends ToolBase {
             .hasArg()
             .required(false)
             .desc("Comma separated list of fields to export. By default all 
fields are fetched.")
-            .build());
+            .build(),
+        SolrCLI.OPTION_CREDENTIALS);
   }
 
   public abstract static class Info {
     String baseurl;
+    String credentials;
     String format;
     boolean compress;
     String query;
@@ -145,8 +147,9 @@ public class ExportTool extends ToolBase {
     CloudSolrClient solrClient;
     DocsSink sink;
 
-    public Info(String url) {
+    public Info(String url, String credentials) {
       setUrl(url);
+      setCredentials(credentials);
       setOutFormat(null, "jsonl", false);
     }
 
@@ -157,6 +160,10 @@ public class ExportTool extends ToolBase {
       query = "*:*";
     }
 
+    public void setCredentials(String credentials) {
+      this.credentials = credentials;
+    }
+
     public void setLimit(String maxDocsStr) {
       limit = Long.parseLong(maxDocsStr);
       if (limit == -1) limit = Long.MAX_VALUE;
@@ -202,7 +209,13 @@ public class ExportTool extends ToolBase {
     abstract void exportDocs() throws Exception;
 
     void fetchUniqueKey() throws SolrServerException, IOException {
-      solrClient = new 
CloudHttp2SolrClient.Builder(Collections.singletonList(baseurl)).build();
+      Http2SolrClient.Builder builder =
+          new 
Http2SolrClient.Builder().withOptionalBasicAuthCredentials(credentials);
+
+      solrClient =
+          new CloudHttp2SolrClient.Builder(Collections.singletonList(baseurl))
+              .withInternalClientBuilder(builder)
+              .build();
       NamedList<Object> response =
           solrClient.request(
               new GenericSolrRequest(
@@ -234,7 +247,8 @@ public class ExportTool extends ToolBase {
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     String url = cli.getOptionValue("url");
-    Info info = new MultiThreadedRunner(url);
+    String credentials = 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt());
+    Info info = new MultiThreadedRunner(url, credentials);
     info.query = cli.getOptionValue("query", "*:*");
     info.setOutFormat(
         cli.getOptionValue("out"), cli.getOptionValue("format"), 
cli.hasOption("compress"));
@@ -491,8 +505,8 @@ public class ExportTool extends ToolBase {
     private final long startTime;
 
     @SuppressForbidden(reason = "Need to print out time")
-    public MultiThreadedRunner(String url) {
-      super(url);
+    public MultiThreadedRunner(String url, String credentials) {
+      super(url, credentials);
       startTime = System.currentTimeMillis();
     }
 
@@ -604,7 +618,7 @@ public class ExportTool extends ToolBase {
 
       boolean exportDocsFromCore() throws IOException, SolrServerException {
 
-        try (SolrClient client = new Http2SolrClient.Builder(baseurl).build()) 
{
+        try (SolrClient client = SolrCLI.getSolrClient(baseurl, credentials)) {
           expectedDocs = getDocCount(replica.getCoreName(), client, query);
           GenericSolrRequest request;
           ModifiableSolrParams params = new ModifiableSolrParams();
diff --git a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java 
b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
index a657eca84bb..90b00239f48 100644
--- a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
@@ -170,10 +170,15 @@ public class HealthcheckTool extends ToolBase {
           q = new SolrQuery("*:*");
           q.setRows(0);
           q.set(DISTRIB, "false");
-          try (var solrClientForCollection = SolrCLI.getSolrClient(coreUrl)) {
+          try (var solrClientForCollection =
+              SolrCLI.getSolrClient(
+                  coreUrl, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
             qr = solrClientForCollection.query(q);
             numDocs = qr.getResults().getNumFound();
-            try (var solrClient = 
SolrCLI.getSolrClient(replicaCoreProps.getBaseUrl())) {
+            try (var solrClient =
+                SolrCLI.getSolrClient(
+                    replicaCoreProps.getBaseUrl(),
+                    
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
               NamedList<Object> systemInfo =
                   solrClient.request(
                       new GenericSolrRequest(
diff --git a/solr/core/src/java/org/apache/solr/cli/PackageTool.java 
b/solr/core/src/java/org/apache/solr/cli/PackageTool.java
index 97ed80e7be3..a8bc4131ef4 100644
--- a/solr/core/src/java/org/apache/solr/cli/PackageTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/PackageTool.java
@@ -30,7 +30,6 @@ import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.lucene.util.SuppressForbidden;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.Pair;
@@ -85,7 +84,7 @@ public class PackageTool extends ToolBase {
 
       log.info("ZK: {}", zkHost);
 
-      try (SolrClient solrClient = new 
Http2SolrClient.Builder(solrUrl).build()) {
+      try (SolrClient solrClient = SolrCLI.getSolrClient(cli, true)) {
         packageManager = new PackageManager(solrClient, solrUrl, zkHost);
         try {
           repositoryManager = new RepositoryManager(solrClient, 
packageManager);
@@ -342,6 +341,14 @@ public class PackageTool extends ToolBase {
             .required(false)
             .desc("Don't prompt for input; accept all default choices, 
defaults to false.")
             .longOpt("noprompt")
+            .build(),
+        // u was taken, can we change that instead?
+        Option.builder("credentials")
+            .argName("credentials")
+            .hasArg()
+            .required(false)
+            .desc(
+                "Credentials in the format username:password. Example: 
--credentials solr:SolrRocks")
             .build());
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java 
b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java
index 91e1a4e6fbf..7a6c21649f2 100644
--- a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java
@@ -75,19 +75,22 @@ public class PostLogsTool extends ToolBase {
             .hasArg()
             .required(true)
             .desc("All files found at or below the root directory will be 
indexed.")
-            .build());
+            .build(),
+        SolrCLI.OPTION_CREDENTIALS);
   }
 
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     String url = cli.getOptionValue("url");
     String rootDir = cli.getOptionValue("rootdir");
-    runCommand(url, rootDir);
+    String credentials = cli.getOptionValue("credentials", null);
+    runCommand(url, rootDir, credentials);
   }
 
-  public void runCommand(String baseUrl, String root) throws IOException {
+  public void runCommand(String baseUrl, String root, String credentials) 
throws IOException {
 
-    Http2SolrClient.Builder builder = new Http2SolrClient.Builder(baseUrl);
+    Http2SolrClient.Builder builder =
+        new 
Http2SolrClient.Builder(baseUrl).withOptionalBasicAuthCredentials(credentials);
     try (SolrClient client = builder.build()) {
       int rec = 0;
       UpdateRequest request = new UpdateRequest();
diff --git a/solr/core/src/java/org/apache/solr/cli/PostTool.java 
b/solr/core/src/java/org/apache/solr/cli/PostTool.java
index b44bfe397a6..4edc2a8f3b8 100644
--- a/solr/core/src/java/org/apache/solr/cli/PostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/PostTool.java
@@ -95,7 +95,8 @@ public class PostTool extends ToolBase {
             .required(false)
             .desc(
                 "sends application/json content as Solr commands to /update 
instead of /update/json/docs")
-            .build());
+            .build(),
+        SolrCLI.OPTION_CREDENTIALS);
   }
 
   @Override
@@ -132,11 +133,24 @@ public class PostTool extends ToolBase {
     boolean commit = cli.hasOption("commit");
     boolean optimize = cli.hasOption("optimize");
 
+    String credentials = 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt());
+
     String[] args = cli.getArgs();
 
     SimplePostTool spt =
         new SimplePostTool(
-            mode, solrUrl, auto, type, format, recursive, delay, fileTypes, 
out, commit, optimize,
+            mode,
+            solrUrl,
+            credentials,
+            auto,
+            type,
+            format,
+            recursive,
+            delay,
+            fileTypes,
+            out,
+            commit,
+            optimize,
             args);
 
     spt.execute();
diff --git a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java 
b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
index 4a6ad5d5351..f77ae4cb202 100644
--- a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
@@ -267,7 +267,8 @@ public class RunExampleTool extends ToolBase {
     // safe check if core / collection already exists
     boolean alreadyExists = false;
     if (nodeStatus.get("cloud") != null) {
-      if (SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
+      if (SolrCLI.safeCheckCollectionExists(
+          solrUrl, collectionName, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
         alreadyExists = true;
         echo(
             "\nWARNING: Collection '"
@@ -276,7 +277,8 @@ public class RunExampleTool extends ToolBase {
       }
     } else {
       String coreName = collectionName;
-      if (SolrCLI.safeCheckCoreExists(solrUrl, coreName)) {
+      if (SolrCLI.safeCheckCoreExists(
+          solrUrl, coreName, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()))) {
         alreadyExists = true;
         echo(
             "\nWARNING: Core '"
@@ -536,7 +538,13 @@ public class RunExampleTool extends ToolBase {
     waitToSeeLiveNodes(zkHost, numNodes);
 
     // create the collection
-    String collectionName = createCloudExampleCollection(numNodes, readInput, 
prompt, solrUrl);
+    String collectionName =
+        createCloudExampleCollection(
+            numNodes,
+            readInput,
+            prompt,
+            solrUrl,
+            cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()));
 
     echo("\n\nSolrCloud example running, please visit: " + solrUrl + " \n");
   }
@@ -640,7 +648,9 @@ public class RunExampleTool extends ToolBase {
         String.format(
             Locale.ROOT, "%s://%s:%d/solr", urlScheme, (host != null ? host : 
"localhost"), port);
 
-    Map<String, Object> nodeStatus = checkPortConflict(solrUrl, solrHomeDir, 
port);
+    String credentials = null; // for now we don't need it for example tool.  
But we should.
+
+    Map<String, Object> nodeStatus = checkPortConflict(solrUrl, credentials, 
solrHomeDir, port);
     if (nodeStatus != null)
       return nodeStatus; // the server they are trying to start is already 
running
 
@@ -687,16 +697,18 @@ public class RunExampleTool extends ToolBase {
     }
     if (code != 0) throw new Exception("Failed to start Solr using command: " 
+ startCmd);
 
-    return getNodeStatus(solrUrl, maxWaitSecs);
+    return getNodeStatus(
+        solrUrl, cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()), 
maxWaitSecs);
   }
 
-  protected Map<String, Object> checkPortConflict(String solrUrl, File 
solrHomeDir, int port) {
+  protected Map<String, Object> checkPortConflict(
+      String solrUrl, String credentials, File solrHomeDir, int port) {
     // quickly check if the port is in use
     if (isPortAvailable(port)) return null; // not in use ... try to start
 
     Map<String, Object> nodeStatus = null;
     try {
-      nodeStatus = (new StatusTool()).getStatus(solrUrl);
+      nodeStatus = (new StatusTool()).getStatus(solrUrl, credentials);
     } catch (Exception ignore) {
       /* just trying to determine if this example is already running. */
     }
@@ -745,7 +757,8 @@ public class RunExampleTool extends ToolBase {
   }
 
   protected String createCloudExampleCollection(
-      int numNodes, Scanner readInput, boolean prompt, String solrUrl) throws 
Exception {
+      int numNodes, Scanner readInput, boolean prompt, String solrUrl, String 
credentials)
+      throws Exception {
     // yay! numNodes SolrCloud nodes running
     int numShards = 2;
     int replicationFactor = 2;
@@ -769,7 +782,7 @@ public class RunExampleTool extends ToolBase {
 
         // Test for existence and then prompt to either create another 
collection or skip the
         // creation step
-        if (SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
+        if (SolrCLI.safeCheckCollectionExists(solrUrl, credentials, 
collectionName)) {
           echo("\nCollection '" + collectionName + "' already exists!");
           int oneOrTwo =
               promptForInt(
@@ -825,7 +838,7 @@ public class RunExampleTool extends ToolBase {
       }
     } else {
       // must verify if default collection exists
-      if (SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
+      if (SolrCLI.safeCheckCollectionExists(solrUrl, collectionName, 
credentials)) {
         echo(
             "\nCollection '"
                 + collectionName
@@ -868,13 +881,14 @@ public class RunExampleTool extends ToolBase {
     return configDir.isDirectory();
   }
 
-  protected Map<String, Object> getNodeStatus(String solrUrl, int maxWaitSecs) 
throws Exception {
+  protected Map<String, Object> getNodeStatus(String solrUrl, String 
credentials, int maxWaitSecs)
+      throws Exception {
     StatusTool statusTool = new StatusTool();
     if (verbose) echo("\nChecking status of Solr at " + solrUrl + " ...");
 
     URL solrURL = new URL(solrUrl);
     Map<String, Object> nodeStatus =
-        statusTool.waitToSeeSolrUp(solrUrl, maxWaitSecs, TimeUnit.SECONDS);
+        statusTool.waitToSeeSolrUp(solrUrl, credentials, maxWaitSecs, 
TimeUnit.SECONDS);
     nodeStatus.put("baseUrl", solrUrl);
     CharArr arr = new CharArr();
     new JSONWriter(arr, 2).write(nodeStatus);
diff --git a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java 
b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
index 1c4ceaba9e8..35842eb1c38 100644
--- a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
@@ -112,6 +112,7 @@ public class SimplePostTool {
   int delay = 0;
   String fileTypes;
   URL solrUrl;
+  String credentials;
   OutputStream out = null;
   String type;
   String format;
@@ -256,10 +257,12 @@ public class SimplePostTool {
       urlStr = SimplePostTool.appendParam(urlStr, params);
       URL url = new URL(urlStr);
       String user = null;
+      String credentials = null;
       if (url.getUserInfo() != null && url.getUserInfo().trim().length() > 0) {
         user = url.getUserInfo().split(":")[0];
       } else if (System.getProperty(BASIC_AUTH) != null) {
         user = System.getProperty(BASIC_AUTH).trim().split(":")[0];
+        credentials = System.getProperty(BASIC_AUTH).trim();
       }
       if (user != null) {
         info("Basic Authentication enabled, user=" + user);
@@ -290,7 +293,19 @@ public class SimplePostTool {
       boolean optimize = isOn(System.getProperty("optimize", 
DEFAULT_OPTIMIZE));
 
       return new SimplePostTool(
-          mode, url, auto, type, format, recursive, delay, fileTypes, out, 
commit, optimize, args);
+          mode,
+          url,
+          credentials,
+          auto,
+          type,
+          format,
+          recursive,
+          delay,
+          fileTypes,
+          out,
+          commit,
+          optimize,
+          args);
     } catch (MalformedURLException e) {
       fatal("System Property 'url' is not a valid URL: " + urlStr);
       return null;
@@ -316,6 +331,7 @@ public class SimplePostTool {
   public SimplePostTool(
       String mode,
       URL url,
+      String credentials,
       boolean auto,
       String type,
       String format,
@@ -328,6 +344,7 @@ public class SimplePostTool {
       String[] args) {
     this.mode = mode;
     this.solrUrl = url;
+    this.credentials = credentials;
     this.auto = auto;
     this.type = type;
     this.format = format;
@@ -1063,19 +1080,21 @@ public class SimplePostTool {
     return success;
   }
 
-  private static void basicAuth(HttpURLConnection urlc) throws Exception {
+  private void basicAuth(HttpURLConnection urlc) throws Exception {
     if (urlc.getURL().getUserInfo() != null) {
       String encoding =
           
Base64.getEncoder().encodeToString(urlc.getURL().getUserInfo().getBytes(US_ASCII));
       urlc.setRequestProperty("Authorization", "Basic " + encoding);
-    } else if (System.getProperty(BASIC_AUTH) != null) {
-      String basicauth = System.getProperty(BASIC_AUTH).trim();
-      if (!basicauth.contains(":")) {
-        throw new Exception("System property '" + BASIC_AUTH + "' must be of 
format user:pass");
+    } else if (credentials != null) {
+      if (!credentials.contains(":")) {
+        throw new Exception("credentials '" + credentials + "' must be of 
format user:pass");
       }
+      // urlc.setRequestProperty(
+      //    "Authorization",
+      //    "Basic " + 
Base64.getEncoder().encodeToString(credentials.getBytes(UTF_8)));
       urlc.setRequestProperty(
           "Authorization",
-          "Basic " + 
Base64.getEncoder().encodeToString(basicauth.getBytes(UTF_8)));
+          "Basic " + 
Base64.getEncoder().encodeToString(credentials.getBytes(UTF_8)));
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java 
b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index 3741672a952..14d9ab7f7c9 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -112,6 +112,16 @@ public class SolrCLI implements CLIO {
           .desc("Recurse (true|false), default is false.")
           .build();
 
+  public static final Option OPTION_CREDENTIALS =
+      Option.builder("u")
+          .longOpt("credentials")
+          .argName("credentials")
+          .hasArg()
+          .required(false)
+          .desc(
+              "Credentials in the format username:password. Example: 
--credentials solr:SolrRocks")
+          .build();
+
   public static void exit(int exitStatus) {
     try {
       System.exit(exitStatus);
@@ -394,15 +404,44 @@ public class SolrCLI implements CLIO {
         && Arrays.asList(UNAUTHORIZED.code, 
FORBIDDEN.code).contains(((SolrException) exc).code()));
   }
 
-  public static SolrClient getSolrClient(String solrUrl) {
+  public static SolrClient getSolrClient(String solrUrl, String credentials, 
boolean barePath) {
     // today we require all urls to end in /solr, however in the future we 
will need to support the
-    // /api url end point instead.
+    // /api url end point instead.   Eventually we want to have this method 
always
+    // return a bare url, and then individual calls decide if they are /solr 
or /api
     // The /solr/ check is because sometimes a full url is passed in, like
     // http://localhost:8983/solr/films_shard1_replica_n1/.
-    if (!solrUrl.endsWith("/solr") && !solrUrl.contains("/solr/")) {
+    if (!barePath && !solrUrl.endsWith("/solr") && 
!solrUrl.contains("/solr/")) {
       solrUrl = solrUrl + "/solr";
     }
-    return new 
Http2SolrClient.Builder(solrUrl).withMaxConnectionsPerHost(32).build();
+    Http2SolrClient.Builder builder =
+        new Http2SolrClient.Builder(solrUrl)
+            .withMaxConnectionsPerHost(32)
+            .withOptionalBasicAuthCredentials(credentials);
+
+    return builder.build();
+  }
+
+  /**
+   * Helper method for all the places where we assume a /solr on the url.
+   *
+   * @param solrUrl The solr url that you want the client for
+   * @param credentials The username:password for basic auth.
+   * @return The SolrClient
+   */
+  public static SolrClient getSolrClient(String solrUrl, String credentials) {
+    return getSolrClient(solrUrl, credentials, false);
+  }
+
+  public static SolrClient getSolrClient(CommandLine cli, boolean barePath) 
throws Exception {
+    String solrUrl = SolrCLI.normalizeSolrUrl(cli);
+    String credentials = 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt());
+    return getSolrClient(solrUrl, credentials, barePath);
+  }
+
+  public static SolrClient getSolrClient(CommandLine cli) throws Exception {
+    String solrUrl = SolrCLI.normalizeSolrUrl(cli);
+    String credentials = 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt());
+    return getSolrClient(solrUrl, credentials, false);
   }
 
   private static final String JSON_CONTENT_TYPE = "application/json";
@@ -474,7 +513,7 @@ public class SolrCLI implements CLIO {
    */
   public static String normalizeSolrUrl(String solrUrl) {
     if (solrUrl != null) {
-      if (solrUrl.indexOf("/solr") > -1) { //
+      if (solrUrl.contains("/solr")) { //
         String newSolrUrl = solrUrl.substring(0, solrUrl.indexOf("/solr"));
         CLIO.out(
             "WARNING: URLs provided to this tool needn't include Solr's 
context-root (e.g. \"/solr\"). Such URLs are deprecated and support for them 
will be removed in a future release. Correcting from ["
@@ -531,23 +570,13 @@ public class SolrCLI implements CLIO {
    * up from a running Solr instance based on the solrUrl option.
    */
   public static String getZkHost(CommandLine cli) throws Exception {
+
     String zkHost = cli.getOptionValue("zkHost");
     if (zkHost != null && !zkHost.isBlank()) {
       return zkHost;
     }
 
-    String solrUrl = cli.getOptionValue("solrUrl");
-    if (solrUrl == null) {
-      solrUrl = getDefaultSolrUrl();
-      CLIO.getOutStream()
-          .println(
-              "Neither -zkHost or -solrUrl parameters provided so assuming 
solrUrl is "
-                  + solrUrl
-                  + ".");
-    }
-    solrUrl = normalizeSolrUrl(solrUrl);
-
-    try (var solrClient = getSolrClient(solrUrl)) {
+    try (SolrClient solrClient = getSolrClient(cli)) {
       // hit Solr to get system info
       NamedList<Object> systemInfo =
           solrClient.request(
@@ -570,9 +599,10 @@ public class SolrCLI implements CLIO {
     return zkHost;
   }
 
-  public static boolean safeCheckCollectionExists(String solrUrl, String 
collection) {
+  public static boolean safeCheckCollectionExists(
+      String solrUrl, String collection, String credentials) {
     boolean exists = false;
-    try (var solrClient = getSolrClient(solrUrl)) {
+    try (var solrClient = getSolrClient(solrUrl, credentials)) {
       NamedList<Object> existsCheckResult = solrClient.request(new 
CollectionAdminRequest.List());
       @SuppressWarnings("unchecked")
       List<String> collections = (List<String>) 
existsCheckResult.get("collections");
@@ -584,9 +614,9 @@ public class SolrCLI implements CLIO {
   }
 
   @SuppressWarnings("unchecked")
-  public static boolean safeCheckCoreExists(String solrUrl, String coreName) {
+  public static boolean safeCheckCoreExists(String solrUrl, String coreName, 
String credentials) {
     boolean exists = false;
-    try (var solrClient = getSolrClient(solrUrl)) {
+    try (var solrClient = getSolrClient(solrUrl, credentials)) {
       boolean wait = false;
       final long startWaitAt = System.nanoTime();
       do {
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java 
b/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
index cf4fee63b35..b0d6bee2de0 100644
--- a/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
@@ -45,6 +45,6 @@ public class SolrLogPostTool {
     String baseUrl = args[0];
     String root = args[1];
     PostLogsTool postLogsTool = new PostLogsTool();
-    postLogsTool.runCommand(baseUrl, root);
+    postLogsTool.runCommand(baseUrl, root, null);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java 
b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
index 3758a60b3fe..a201fc42641 100644
--- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
@@ -97,7 +97,11 @@ public class StatusTool extends ToolBase {
       int solrPort = (new URL(solrUrl)).getPort();
       echo("Waiting up to " + maxWaitSecs + " seconds to see Solr running on 
port " + solrPort);
       try {
-        waitToSeeSolrUp(solrUrl, maxWaitSecs, TimeUnit.SECONDS);
+        waitToSeeSolrUp(
+            solrUrl,
+            cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt()),
+            maxWaitSecs,
+            TimeUnit.SECONDS);
         echo("Started Solr server on port " + solrPort + ". Happy searching!");
       } catch (TimeoutException timeout) {
         throw new Exception(
@@ -106,7 +110,8 @@ public class StatusTool extends ToolBase {
     } else {
       try {
         CharArr arr = new CharArr();
-        new JSONWriter(arr, 2).write(getStatus(solrUrl));
+        new JSONWriter(arr, 2)
+            .write(getStatus(solrUrl, 
cli.getOptionValue(SolrCLI.OPTION_CREDENTIALS.getLongOpt())));
         echo(arr.toString());
       } catch (Exception exc) {
         if (SolrCLI.exceptionIsAuthRelated(exc)) {
@@ -123,12 +128,13 @@ public class StatusTool extends ToolBase {
     }
   }
 
-  public Map<String, Object> waitToSeeSolrUp(String solrUrl, long maxWait, 
TimeUnit unit)
-      throws Exception {
+  public Map<String, Object> waitToSeeSolrUp(
+      String solrUrl, String credentials, long maxWait, TimeUnit unit) throws 
Exception {
     long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(maxWait, 
unit);
     while (System.nanoTime() < timeout) {
+
       try {
-        return getStatus(solrUrl);
+        return getStatus(solrUrl, credentials);
       } catch (Exception exc) {
         if (SolrCLI.exceptionIsAuthRelated(exc)) {
           throw exc;
@@ -148,16 +154,20 @@ public class StatusTool extends ToolBase {
             + " seconds!");
   }
 
-  public Map<String, Object> getStatus(String solrUrl) throws Exception {
+  public Map<String, Object> getStatus(String solrUrl, String credentials) 
throws Exception {
+    try (var solrClient = SolrCLI.getSolrClient(solrUrl, credentials)) {
+      return getStatus(solrClient);
+    }
+  }
+
+  public Map<String, Object> getStatus(SolrClient solrClient) throws Exception 
{
     Map<String, Object> status;
 
-    try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
-      NamedList<Object> systemInfo =
-          solrClient.request(
-              new GenericSolrRequest(SolrRequest.METHOD.GET, 
CommonParams.SYSTEM_INFO_PATH));
-      // convert raw JSON into user-friendly output
-      status = reportStatus(systemInfo, solrClient);
-    }
+    NamedList<Object> systemInfo =
+        solrClient.request(
+            new GenericSolrRequest(SolrRequest.METHOD.GET, 
CommonParams.SYSTEM_INFO_PATH));
+    // convert raw JSON into user-friendly output
+    status = reportStatus(systemInfo, solrClient);
 
     return status;
   }
diff --git a/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java 
b/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
index d817845854a..db04f60f0ef 100644
--- a/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.cli;
 
-import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Arrays;
@@ -66,9 +65,6 @@ public class ApiToolTest extends SolrCloudTestCase {
         .process(cluster.getSolrClient());
     cluster.waitForActiveCollection(COLLECTION_NAME, 2, 2);
 
-    String tmpFileLoc =
-        new File(cluster.getBaseDir().toFile().getAbsolutePath() + 
File.separator).getPath();
-
     UpdateRequest ur = new UpdateRequest();
     ur.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
 
@@ -90,7 +86,8 @@ public class ApiToolTest extends SolrCloudTestCase {
             cluster.getJettySolrRunner(0).getBaseUrl()
                 + "/"
                 + COLLECTION_NAME
-                + "/select?q=*:*&rows=1&fl=id&sort=id+asc");
+                + "/select?q=*:*&rows=1&fl=id&sort=id+asc",
+            null);
     // Fields that could be missed because of serialization
     assertFindInJson(response, "\"numFound\":1000,");
     // Correct formatting
diff --git a/solr/core/src/test/org/apache/solr/cli/CreateToolTest.java 
b/solr/core/src/test/org/apache/solr/cli/CreateToolTest.java
new file mode 100644
index 00000000000..a4801c99fb0
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cli/CreateToolTest.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.cli;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.cli.SolrCLI.findTool;
+import static org.apache.solr.cli.SolrCLI.parseCmdLine;
+import static 
org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
+
+import java.util.Map;
+import org.apache.commons.cli.CommandLine;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class CreateToolTest extends SolrCloudTestCase {
+
+  private static final String USER = "solr";
+  private static final String PASS = "SolrRocksAgain";
+  private static final String collectionName = 
"testCreateCollectionWithBasicAuth";
+
+  @BeforeClass
+  public static void setupClusterWithSecurityEnabled() throws Exception {
+    final String SECURITY_JSON =
+        Utils.toJSONString(
+            Map.of(
+                "authorization",
+                Map.of(
+                    "class",
+                    RuleBasedAuthorizationPlugin.class.getName(),
+                    "user-role",
+                    singletonMap(USER, "admin"),
+                    "permissions",
+                    singletonList(Map.of("name", "all", "role", "admin"))),
+                "authentication",
+                Map.of(
+                    "class",
+                    BasicAuthPlugin.class.getName(),
+                    "blockUnknown",
+                    true,
+                    "credentials",
+                    singletonMap(USER, getSaltedHashedValue(PASS)))));
+
+    configureCluster(2)
+        .addConfig("conf", configset("cloud-minimal"))
+        .withSecurityJson(SECURITY_JSON)
+        .configure();
+  }
+
+  @Test
+  public void testCreateCollectionWithBasicAuth() throws Exception {
+
+    String[] args = {
+      "create",
+      "-c",
+      collectionName,
+      "-n",
+      "cloud-minimal",
+      "-zkHost",
+      cluster.getZkClient().getZkServerAddress(),
+      "-credentials",
+      USER + ":" + PASS,
+      "-verbose"
+    };
+
+    assertEquals(0, runTool(args));
+  }
+
+  private int runTool(String[] args) throws Exception {
+    Tool tool = findTool(args);
+    assertTrue(tool instanceof CreateTool);
+    CommandLine cli = parseCmdLine(tool.getName(), args, tool.getOptions());
+    return tool.runTool(cli);
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/cli/DeleteToolTest.java 
b/solr/core/src/test/org/apache/solr/cli/DeleteToolTest.java
new file mode 100644
index 00000000000..808f6171971
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cli/DeleteToolTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.cli;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.cli.SolrCLI.findTool;
+import static org.apache.solr.cli.SolrCLI.parseCmdLine;
+import static 
org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
+
+import java.util.Map;
+import org.apache.commons.cli.CommandLine;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class DeleteToolTest extends SolrCloudTestCase {
+
+  private static final String USER = "solr";
+  private static final String PASS = "SolrRocksAgain";
+
+  @BeforeClass
+  public static void setupClusterWithSecurityEnabled() throws Exception {
+    final String SECURITY_JSON =
+        Utils.toJSONString(
+            Map.of(
+                "authorization",
+                Map.of(
+                    "class",
+                    RuleBasedAuthorizationPlugin.class.getName(),
+                    "user-role",
+                    singletonMap(USER, "admin"),
+                    "permissions",
+                    singletonList(Map.of("name", "all", "role", "admin"))),
+                "authentication",
+                Map.of(
+                    "class",
+                    BasicAuthPlugin.class.getName(),
+                    "blockUnknown",
+                    true,
+                    "credentials",
+                    singletonMap(USER, getSaltedHashedValue(PASS)))));
+
+    configureCluster(2)
+        .addConfig("conf", configset("cloud-minimal"))
+        .withSecurityJson(SECURITY_JSON)
+        .configure();
+  }
+
+  private <T extends SolrRequest<? extends SolrResponse>> T withBasicAuth(T 
req) {
+    req.setBasicAuthCredentials(USER, PASS);
+    return req;
+  }
+
+  @Test
+  public void testDeleteCollectionWithBasicAuth() throws Exception {
+
+    withBasicAuth(
+            CollectionAdminRequest.createCollection(
+                "testDeleteCollectionWithBasicAuth", "conf", 1, 1))
+        .processAndWait(cluster.getSolrClient(), 10);
+    waitForState(
+        "Expected collection to be created with 1 shard and 1 replicas",
+        "testDeleteCollectionWithBasicAuth",
+        clusterShape(1, 1));
+
+    String[] args = {
+      "delete",
+      "-c",
+      "testDeleteCollectionWithBasicAuth",
+      "-deleteConfig",
+      "false",
+      "-zkHost",
+      cluster.getZkClient().getZkServerAddress(),
+      "-credentials",
+      USER + ":" + PASS,
+      "-verbose"
+    };
+    assertEquals(0, runTool(args));
+  }
+
+  @Test
+  public void testFailsToDeleteProtectedCollection() throws Exception {
+
+    withBasicAuth(
+            CollectionAdminRequest.createCollection(
+                "testFailsToDeleteProtectedCollection", "conf", 1, 1))
+        .processAndWait(cluster.getSolrClient(), 10);
+    waitForState(
+        "Expected collection to be created with 1 shard and 1 replicas",
+        "testFailsToDeleteProtectedCollection",
+        clusterShape(1, 1));
+
+    String[] args = {
+      "delete",
+      "-c",
+      "testFailsToDeleteProtectedCollection",
+      "-zkHost",
+      cluster.getZkClient().getZkServerAddress(),
+      "-verbose"
+    };
+    assertEquals(1, runTool(args));
+  }
+
+  private int runTool(String[] args) throws Exception {
+    Tool tool = findTool(args);
+    assertTrue(tool instanceof DeleteTool);
+    CommandLine cli = parseCmdLine(tool.getName(), args, tool.getOptions());
+    return tool.runTool(cli);
+  }
+}
diff --git 
a/solr/core/src/test/org/apache/solr/cloud/PackageManagerCLITest.java 
b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java
similarity index 51%
rename from solr/core/src/test/org/apache/solr/cloud/PackageManagerCLITest.java
rename to solr/core/src/test/org/apache/solr/cli/PackageToolTest.java
index b1e11c45a3c..dea89655772 100644
--- a/solr/core/src/test/org/apache/solr/cloud/PackageManagerCLITest.java
+++ b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java
@@ -15,14 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.solr.cloud;
+package org.apache.solr.cli;
 
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static 
org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
+
+import java.io.StringReader;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
-import org.apache.solr.cli.PackageTool;
-import org.apache.solr.cli.SolrCLI;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.core.TestSolrConfigHandler;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.LinkedHashMapWriter;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
 import org.apache.solr.util.LogLevel;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
@@ -33,11 +46,14 @@ import org.eclipse.jetty.server.handler.ResourceHandler;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.noggit.JSONParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @LogLevel("org.apache=INFO")
-public class PackageManagerCLITest extends SolrCloudTestCase {
+public class PackageToolTest extends SolrCloudTestCase {
+  private static final String USER = "solr";
+  private static final String PASS = "SolrRocksAgain";
 
   // Note for those who want to modify the jar files used in the packages used 
in this test:
   // You need to re-sign the jars for install step, as follows:
@@ -52,14 +68,35 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
   private static LocalWebServer repositoryServer;
 
   @BeforeClass
-  public static void setupCluster() throws Exception {
+  public static void setupClusterWithSecurityEnabled() throws Exception {
     System.setProperty("enable.packages", "true");
 
-    configureCluster(1)
+    final String SECURITY_JSON =
+        Utils.toJSONString(
+            Map.of(
+                "authorization",
+                Map.of(
+                    "class",
+                    RuleBasedAuthorizationPlugin.class.getName(),
+                    "user-role",
+                    singletonMap(USER, "admin"),
+                    "permissions",
+                    singletonList(Map.of("name", "all", "role", "admin"))),
+                "authentication",
+                Map.of(
+                    "class",
+                    BasicAuthPlugin.class.getName(),
+                    "blockUnknown",
+                    true,
+                    "credentials",
+                    singletonMap(USER, getSaltedHashedValue(PASS)))));
+
+    configureCluster(2)
         .addConfig(
             "conf1", 
TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
         .addConfig(
             "conf3", 
TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .withSecurityJson(SECURITY_JSON)
         .configure();
 
     repositoryServer =
@@ -78,36 +115,61 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
     }
   }
 
+  private <T extends SolrRequest<? extends SolrResponse>> T withBasicAuth(T 
req) {
+    req.setBasicAuthCredentials(USER, PASS);
+    return req;
+  }
+
   @Test
-  public void testPackageManager() throws Exception {
+  public void testPackageTool() throws Exception {
     PackageTool tool = new PackageTool();
 
     String solrUrl = cluster.getJettySolrRunner(0).getBaseUrl().toString();
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-installed"});
+    run(
+        tool,
+        new String[] {"-solrUrl", solrUrl, "list-installed", "-credentials", 
USER + ":" + PASS});
 
     run(
         tool,
         new String[] {
           "-solrUrl",
           solrUrl,
+          "-credentials",
+          USER + ":" + PASS,
           "add-repo",
           "fullstory",
-          "http://localhost:"; + repositoryServer.getPort()
+          "http://localhost:"; + repositoryServer.getPort(),
+          "-credentials",
+          USER + ":" + PASS
         });
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-available"});
+    run(
+        tool,
+        new String[] {"-solrUrl", solrUrl, "list-available", "-credentials", 
USER + ":" + PASS});
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "install", 
"question-answer:1.0.0"});
+    run(
+        tool,
+        new String[] {
+          "-solrUrl", solrUrl, "install", "question-answer:1.0.0", 
"-credentials", USER + ":" + PASS
+        });
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-installed"});
+    run(
+        tool,
+        new String[] {"-solrUrl", solrUrl, "list-installed", "-credentials", 
USER + ":" + PASS});
 
-    CollectionAdminRequest.createCollection("abc", "conf1", 1, 
1).process(cluster.getSolrClient());
-    CollectionAdminRequest.createCollection("def", "conf3", 1, 
1).process(cluster.getSolrClient());
+    withBasicAuth(CollectionAdminRequest.createCollection("abc", "conf1", 1, 
1))
+        .processAndWait(cluster.getSolrClient(), 10);
+    withBasicAuth(CollectionAdminRequest.createCollection("def", "conf3", 1, 
1))
+        .processAndWait(cluster.getSolrClient(), 10);
 
     String rhPath = "/mypath2";
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-deployed", 
"question-answer"});
+    run(
+        tool,
+        new String[] {
+          "-solrUrl", solrUrl, "list-deployed", "question-answer", 
"-credentials", USER + ":" + PASS
+        });
 
     run(
         tool,
@@ -120,13 +182,23 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
           "-collections",
           "abc",
           "-p",
-          "RH-HANDLER-PATH=" + rhPath
+          "RH-HANDLER-PATH=" + rhPath,
+          "-credentials",
+          USER + ":" + PASS
         });
-    assertPackageVersion("abc", "question-answer", "1.0.0", rhPath, "1.0.0");
+    assertPackageVersion("abc", "question-answer", "1.0.0", rhPath, "1.0.0", 
USER + ":" + PASS);
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-deployed", 
"question-answer"});
+    run(
+        tool,
+        new String[] {
+          "-solrUrl", solrUrl, "list-deployed", "question-answer", 
"-credentials", USER + ":" + PASS
+        });
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-deployed", "-c", 
"abc"});
+    run(
+        tool,
+        new String[] {
+          "-solrUrl", solrUrl, "list-deployed", "-c", "abc", "-credentials", 
USER + ":" + PASS
+        });
 
     // Should we test the "auto-update to latest" functionality or the default 
explicit deploy
     // functionality
@@ -139,17 +211,33 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
       run(
           tool,
           new String[] {
-            "-solrUrl", solrUrl, "deploy", "question-answer:latest", "-y", 
"-collections", "abc"
+            "-solrUrl",
+            solrUrl,
+            "deploy",
+            "question-answer:latest",
+            "-y",
+            "-collections",
+            "abc",
+            "-credentials",
+            USER + ":" + PASS
           });
-      assertPackageVersion("abc", "question-answer", "$LATEST", rhPath, 
"1.0.0");
+      assertPackageVersion("abc", "question-answer", "$LATEST", rhPath, 
"1.0.0", USER + ":" + PASS);
 
-      run(tool, new String[] {"-solrUrl", solrUrl, "install", 
"question-answer"});
-      assertPackageVersion("abc", "question-answer", "$LATEST", rhPath, 
"1.1.0");
+      run(
+          tool,
+          new String[] {
+            "-solrUrl", solrUrl, "install", "question-answer", "-credentials", 
USER + ":" + PASS
+          });
+      assertPackageVersion("abc", "question-answer", "$LATEST", rhPath, 
"1.1.0", USER + ":" + PASS);
     } else {
       log.info("Testing explicit deployment to a different/newer version");
 
-      run(tool, new String[] {"-solrUrl", solrUrl, "install", 
"question-answer"});
-      assertPackageVersion("abc", "question-answer", "1.0.0", rhPath, "1.0.0");
+      run(
+          tool,
+          new String[] {
+            "-solrUrl", solrUrl, "install", "question-answer", "-credentials", 
USER + ":" + PASS
+          });
+      assertPackageVersion("abc", "question-answer", "1.0.0", rhPath, "1.0.0", 
USER + ":" + PASS);
 
       // even if parameters are not passed in, they should be picked up from 
previous deployment
       if (random().nextBoolean()) {
@@ -165,7 +253,9 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
               "-collections",
               "abc",
               "-p",
-              "RH-HANDLER-PATH=" + rhPath
+              "RH-HANDLER-PATH=" + rhPath,
+              "-credentials",
+              USER + ":" + PASS
             });
       } else {
         run(
@@ -178,40 +268,92 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
               "-y",
               "question-answer",
               "-collections",
-              "abc"
+              "abc",
+              "-credentials",
+              USER + ":" + PASS
             });
       }
-      assertPackageVersion("abc", "question-answer", "1.1.0", rhPath, "1.1.0");
+      assertPackageVersion("abc", "question-answer", "1.1.0", rhPath, "1.1.0", 
USER + ":" + PASS);
     }
 
     log.info("Running undeploy...");
     run(
         tool,
-        new String[] {"-solrUrl", solrUrl, "undeploy", "question-answer", 
"-collections", "abc"});
+        new String[] {
+          "-solrUrl",
+          solrUrl,
+          "undeploy",
+          "question-answer",
+          "-collections",
+          "abc",
+          "-credentials",
+          USER + ":" + PASS
+        });
 
-    run(tool, new String[] {"-solrUrl", solrUrl, "list-deployed", 
"question-answer"});
+    run(
+        tool,
+        new String[] {
+          "-solrUrl", solrUrl, "list-deployed", "question-answer", 
"-credentials", USER + ":" + PASS
+        });
   }
 
   void assertPackageVersion(
-      String collection, String pkg, String version, String component, String 
componentVersion)
+      String collection,
+      String pkg,
+      String version,
+      String component,
+      String componentVersion,
+      String credentials)
       throws Exception {
-    TestSolrConfigHandler.testForResponseElement(
-        null,
+
+    testForResponseElement(
         cluster.getJettySolrRunner(0).getBaseUrl().toString() + "/" + 
collection,
         "/config/params?meta=true",
-        cluster.getSolrClient(),
+        credentials,
         Arrays.asList("response", "params", "PKG_VERSIONS", pkg),
-        version,
-        10);
+        version);
 
-    TestSolrConfigHandler.testForResponseElement(
-        null,
+    testForResponseElement(
         cluster.getJettySolrRunner(0).getBaseUrl().toString() + "/" + 
collection,
         "/config/requestHandler?componentName=" + component + "&meta=true",
-        cluster.getSolrClient(),
+        credentials,
         Arrays.asList("config", "requestHandler", component, "_packageinfo_", 
"version"),
-        componentVersion,
-        10);
+        componentVersion);
+  }
+
+  @SuppressWarnings({"rawtypes"})
+  public static void testForResponseElement(
+      String testServerBaseUrl,
+      String uri,
+      String credentials,
+      List<String> jsonPath,
+      Object expected)
+      throws Exception {
+
+    // Copied method from TestSolrConfigHandler.java
+    // and then tweaked it to handle basic auth.  We need a nice pattern for 
doing
+    // http gets/puts to various end points that can be used across all tests.
+    // using the ApiTool makes me sad ;-)
+
+    boolean success = false;
+
+    ApiTool apiTool = new ApiTool();
+    String response = apiTool.callGet(testServerBaseUrl + uri, credentials);
+
+    LinkedHashMapWriter m =
+        (LinkedHashMapWriter)
+            Utils.MAPWRITEROBJBUILDER.apply(new JSONParser(new 
StringReader(response))).getVal();
+    Object actual = Utils.getObjectByPath(m, false, jsonPath);
+
+    if (Objects.equals(expected, actual)) {
+      success = true;
+    }
+
+    assertTrue(
+        StrUtils.formatString(
+            "Could not get expected value  ''{0}'' for path ''{1}'' full 
output: {2},  from server:  {3}",
+            expected, StrUtils.join(jsonPath, '/'), m.toString(), 
testServerBaseUrl),
+        success);
   }
 
   private void run(PackageTool tool, String[] args) throws Exception {
@@ -220,7 +362,7 @@ public class PackageManagerCLITest extends 
SolrCloudTestCase {
   }
 
   static class LocalWebServer {
-    private int port = 0;
+    private final int port = 0;
     private final String resourceDir;
     Server server;
     ServerConnector connector;
diff --git a/solr/core/src/test/org/apache/solr/cli/PostToolTest.java 
b/solr/core/src/test/org/apache/solr/cli/PostToolTest.java
index 681d788138c..c45bd7095f8 100644
--- a/solr/core/src/test/org/apache/solr/cli/PostToolTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/PostToolTest.java
@@ -17,32 +17,87 @@
 
 package org.apache.solr.cli;
 
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
 import static org.apache.solr.cli.SolrCLI.findTool;
 import static org.apache.solr.cli.SolrCLI.parseCmdLine;
+import static 
org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
 import org.apache.commons.cli.CommandLine;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
[email protected]
 public class PostToolTest extends SolrCloudTestCase {
 
+  private static final String USER = "solr";
+  private static final String PASS = "SolrRocksAgain";
+
   @BeforeClass
-  public static void setupCluster() throws Exception {
-    configureCluster(1)
-        .addConfig(
-            "config", 
TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+  public static void setupClusterWithSecurityEnabled() throws Exception {
+    final String SECURITY_JSON =
+        Utils.toJSONString(
+            Map.of(
+                "authorization",
+                Map.of(
+                    "class",
+                    RuleBasedAuthorizationPlugin.class.getName(),
+                    "user-role",
+                    singletonMap(USER, "admin"),
+                    "permissions",
+                    singletonList(Map.of("name", "all", "role", "admin"))),
+                "authentication",
+                Map.of(
+                    "class",
+                    BasicAuthPlugin.class.getName(),
+                    "blockUnknown",
+                    true,
+                    "credentials",
+                    singletonMap(USER, getSaltedHashedValue(PASS)))));
+
+    configureCluster(2)
+        .addConfig("conf", configset("cloud-minimal"))
+        .withSecurityJson(SECURITY_JSON)
         .configure();
   }
 
+  private <T extends SolrRequest<? extends SolrResponse>> T withBasicAuth(T 
req) {
+    req.setBasicAuthCredentials(USER, PASS);
+    return req;
+  }
+
   @Test
   public void testBasicRun() throws Exception {
     final String collection = "aliasedCollection";
-    CollectionAdminRequest.createCollection(collection, "config", 1, 1)
-        .process(cluster.getSolrClient());
 
-    String[] args = {"post", "-url", 
"http://localhost:8983/solr/aliasedCollection";, "blah.json"};
+    withBasicAuth(CollectionAdminRequest.createCollection(collection, 
"config", 1, 1, 0, 0))
+        .processAndWait(cluster.getSolrClient(), 10);
+
+    File jsonDoc = File.createTempFile("temp", "json");
+
+    FileWriter fw = new FileWriter(jsonDoc, StandardCharsets.UTF_8);
+    Utils.writeJson(Utils.toJSONString(Map.of("id", "1", "title", "mytitle")), 
fw, true);
+
+    String[] args = {
+      "post",
+      "-url",
+      cluster.getJettySolrRunner(0).getBaseUrl() + "/" + collection,
+      "-credentials",
+      USER + ":" + PASS,
+      jsonDoc.getAbsolutePath()
+    };
     assertEquals(0, runTool(args));
   }
 
diff --git a/solr/core/src/test/org/apache/solr/cli/SolrCLITest.java 
b/solr/core/src/test/org/apache/solr/cli/SolrCLITest.java
index 03f35246211..091fb4daa3a 100644
--- a/solr/core/src/test/org/apache/solr/cli/SolrCLITest.java
+++ b/solr/core/src/test/org/apache/solr/cli/SolrCLITest.java
@@ -43,4 +43,7 @@ public class SolrCLITest extends SolrTestCase {
     assertEquals(
         "106751991167 days, 7 hours, 12 minutes, 56 seconds", 
SolrCLI.uptime(Long.MAX_VALUE));
   }
+
+  @Test
+  public void testGetCredentials() {}
 }
diff --git a/solr/core/src/test/org/apache/solr/cli/TestExportTool.java 
b/solr/core/src/test/org/apache/solr/cli/TestExportTool.java
index a59dbafe7fb..0aa9bfb9b76 100644
--- a/solr/core/src/test/org/apache/solr/cli/TestExportTool.java
+++ b/solr/core/src/test/org/apache/solr/cli/TestExportTool.java
@@ -80,7 +80,7 @@ public class TestExportTool extends SolrCloudTestCase {
 
       String url = cluster.getRandomJetty(random()).getBaseUrl() + "/" + 
COLLECTION_NAME;
 
-      ExportTool.Info info = new ExportTool.MultiThreadedRunner(url);
+      ExportTool.Info info = new ExportTool.MultiThreadedRunner(url, null);
       String absolutePath = tmpFileLoc + COLLECTION_NAME + 
random().nextInt(100000) + ".jsonl";
       info.setOutFormat(absolutePath, "jsonl", false);
       info.setLimit("200");
@@ -89,7 +89,7 @@ public class TestExportTool extends SolrCloudTestCase {
 
       assertJsonDocsCount(info, 200, record -> 
"2019-09-30T05:58:03Z".equals(record.get("a_dt")));
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".jsonl";
       info.setOutFormat(absolutePath, "jsonl", false);
       info.setLimit("-1");
@@ -98,7 +98,7 @@ public class TestExportTool extends SolrCloudTestCase {
 
       assertJsonDocsCount(info, 1000, null);
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".javabin";
       info.setOutFormat(absolutePath, "javabin", false);
       info.setLimit("200");
@@ -107,7 +107,7 @@ public class TestExportTool extends SolrCloudTestCase {
 
       assertJavabinDocsCount(info, 200);
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".javabin";
       info.setOutFormat(absolutePath, "javabin", false);
       info.setLimit("-1");
@@ -115,7 +115,7 @@ public class TestExportTool extends SolrCloudTestCase {
       info.exportDocs();
       assertJavabinDocsCount(info, 1000);
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".json";
       info.setOutFormat(absolutePath, "json", false);
       info.setLimit("200");
@@ -124,7 +124,7 @@ public class TestExportTool extends SolrCloudTestCase {
 
       assertJsonDocsCount2(info, 200);
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".json";
       info.setOutFormat(absolutePath, "json", false);
       info.setLimit("-1");
@@ -190,7 +190,7 @@ public class TestExportTool extends SolrCloudTestCase {
       ExportTool.MultiThreadedRunner info;
       String absolutePath;
 
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       info.output = System.out;
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".javabin";
       info.setOutFormat(absolutePath, "javabin", false);
@@ -201,7 +201,7 @@ public class TestExportTool extends SolrCloudTestCase {
         assertEquals(
             e.getValue().longValue(), 
info.corehandlers.get(e.getKey()).receivedDocs.get());
       }
-      info = new ExportTool.MultiThreadedRunner(url);
+      info = new ExportTool.MultiThreadedRunner(url, null);
       info.output = System.out;
       absolutePath = tmpFileLoc + COLLECTION_NAME + random().nextInt(100000) + 
".jsonl";
       info.setOutFormat(absolutePath, "jsonl", false);
diff --git a/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java 
b/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
index d6f7da99eee..47577082428 100644
--- a/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
+++ b/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
@@ -503,7 +503,7 @@ public class TestSolrCLIRunExample extends SolrTestCaseJ4 {
 
     // verify Solr is running on the expected port and verify the collection 
exists
     String solrUrl = "http://localhost:"; + bindPort + "/solr";
-    if (!SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
+    if (!SolrCLI.safeCheckCollectionExists(solrUrl, collectionName, null)) {
       fail(
           "After running Solr cloud example, test collection '"
               + collectionName
diff --git a/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java 
b/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
index 03ccf37751b..5ba4f287b88 100644
--- a/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
@@ -196,6 +196,6 @@ public class SolrCloudExampleTest extends 
AbstractFullDistribZkTestBase {
     assertEquals("Delete action failed!", 0, tool.runTool(cli));
     assertFalse(
         SolrCLI.safeCheckCollectionExists(
-            solrUrl, testCollectionName)); // it should not exist anymore
+            solrUrl, testCollectionName, null)); // it should not exist anymore
   }
 }
diff --git a/solr/packaging/test/test_basic_auth.bats 
b/solr/packaging/test/test_basic_auth.bats
new file mode 100644
index 00000000000..28a9efa0335
--- /dev/null
+++ b/solr/packaging/test/test_basic_auth.bats
@@ -0,0 +1,92 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup() {
+  common_clean_setup
+  
+  echo "Starting Solr"
+  solr start -c -Denable.packages=true
+  
+  # The auth command exports some system variables that are injected as basic 
auth username and password, 
+  # however that defeats our test so fake that out via -solrIncludeFile param 
specifing a bogus path.
+  solr auth enable -type basicAuth -credentials name:password -solrIncludeFile 
/force/credentials/to/be/supplied
+  
+  solr assert -credentials name:password --cloud http://localhost:${SOLR_PORT} 
--timeout 5000
+}
+
+teardown() {
+  # save a snapshot of SOLR_HOME for failed tests
+  save_home_on_failure
+
+  run solr auth disable -z localhost:${ZK_PORT}
+  solr stop -all >/dev/null 2>&1
+}
+
+# Remaining commands that should support basic auth:
+# package, export
+
+
+@test "create, config, api, and delete with basic auth" {
+
+  # Test create
+  run solr create -u name:password -c COLL_NAME 
+  assert_output --partial "Created collection 'COLL_NAME'"
+  
+  # Test config
+  run solr config -u name:password -c COLL_NAME -action set-property -property 
updateHandler.autoCommit.maxDocs -value 100 -solrUrl 
http://localhost:${SOLR_PORT}/solr
+  assert_output --partial "Successfully set-property 
updateHandler.autoCommit.maxDocs to 100"
+  
+  # Test api
+  run solr api -u name:password -get 
"http://localhost:${SOLR_PORT}/solr/COLL_NAME/select?q=*:*"; -verbose
+  assert_output --partial '"numFound":0'
+  
+  # Test delete
+  run solr delete --credentials name:password -c COLL_NAME -zkHost 
localhost:${ZK_PORT} -verbose
+  assert_output --partial "Deleted collection 'COLL_NAME'"
+  refute collection_exists "COLL_NAME"
+  
+}
+
+@test "post, postlogs and export with basic auth" {
+run solr create -c COLL_NAME
+  run solr create -u name:password -c COLL_NAME 
+  assert_output --partial "Created collection 'COLL_NAME'"
+
+  # Test post
+  run solr post -u name:password -type application/xml -url 
http://localhost:${SOLR_PORT}/solr/monitors/update 
${SOLR_TIP}/example/exampledocs/monitor.xml
+  assert_output --partial '1 files indexed.'
+
+  # Test postlogs
+  run solr postlogs -u name:password -url 
http://localhost:${SOLR_PORT}/solr/COLL_NAME -rootdir ${SOLR_LOGS_DIR}/solr.log
+  assert_output --partial 'Committed'
+  
+  # Test export
+  #run solr export -u name:password -url 
"http://localhost:${SOLR_PORT}/solr/COLL_NAME"; -query "*:*" -out 
"${BATS_TEST_TMPDIR}/output"
+  #assert_output --partial 'Export complete'
+  
+}
+
+@test "package with basic auth" {
+  
+  run solr package deploy PACKAGE_NAME -credentials name:password -collections 
foo-1.2
+  # verify that package tool is communicating with Solr via basic auth
+  assert_output --partial "Collection(s) doesn't exist: [foo-1.2]"
+  #assert_output --partial "Deployment successful"
+  #refute_output --partial "Invalid collection"
+}
diff --git a/solr/packaging/test/test_bats.bats 
b/solr/packaging/test/test_bats.bats
index 9d269a62d3f..7fbe19c74b4 100644
--- a/solr/packaging/test/test_bats.bats
+++ b/solr/packaging/test/test_bats.bats
@@ -33,7 +33,7 @@ teardown_file() {
   sleep 3
 
   # Conversely, on shutdown, we do need this to execute strictly
-  # because using "run" will eat filing test exit codes
+  # because using "run" will eat failing test exit codes
   solr stop -all
   # DEBUG : (echo -n "# " ; solr stop -V -all) >&3
 }
diff --git 
a/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager.adoc 
b/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager.adoc
index 2456ef7f244..4c09666a2ec 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager.adoc
@@ -216,3 +216,5 @@ Also, it is critical to protect ZooKeeper from unauthorized 
write access.
 Also, keep in mind, that it is possible to install *any* package from a 
repository once it has been added.
 If you want to use some packages in production, a best practice is to setup 
your own repository and add that to Solr instead of adding a generic 
third-party repository that is beyond your administrative control.
 You might want to re-sign packages from a third-party repository using your 
own private keys and host them at your own repository.
+
+To use the package tool with Basic Auth protected Solr installation pass in 
the credentials via `-credentials username:password`.
diff --git 
a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
 
b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
index 54143627189..d292ed27e2c 100644
--- 
a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
+++ 
b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
@@ -51,7 +51,7 @@ If no nodes are already running, restart will skip the step 
to stop and proceed
 The `bin/solr` script provides many options to allow you to customize the 
server in common ways, such as changing the listening port.
 However, most of the defaults are adequate for most Solr installations, 
especially when just getting started.
 
-`-a "<jvmParams>"`::
+`-a <jvmParams>`::
 +
 [%autowidth,frame=none]
 |===
@@ -69,7 +69,7 @@ If you are passing JVM parameters that begin with `-D`, you 
can omit the `-a` op
 [source,bash]
 bin/solr start -a "-Xdebug -Xrunjdwp:transport=dt_socket, 
server=y,suspend=n,address=1044"
 
-`-j "<jettyParams>"`::
+`-j <jettyParams>`::
 +
 [%autowidth,frame=none]
 |===
@@ -484,7 +484,7 @@ Solr process 39827 running on port 8865
 
 === Assert
 
-The `assert` command sanity checks common issues with Solr installations.
+The `assert` command checks common issues with Solr installations.
 These include checking the ownership/existence of particular directories, and 
ensuring Solr is available on the expected URL.
 The command can either output a specified error message, or change its exit 
code to indicate errors.
 
@@ -530,6 +530,7 @@ usage: bin/solr assert [-m <message>] [-e] [-rR] [-s <url>] 
[-S <url>] [-c
  -x,--exists <directory>       Asserts that directory <directory> exists.
  -X,--not-exists <directory>   Asserts that directory <directory> does NOT
                                exist.
+ --credentials <user:pass>     Username and password for Basic authentication. 
                              
 ----
 
 === Healthcheck
@@ -577,6 +578,16 @@ This parameter is unnecessary if `ZK_HOST` is defined in 
`solr.in.sh` or `solr.i
 +
 *Example*: `bin/solr healthcheck -z localhost:2181`
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 Below is an example healthcheck request and response using a non-standard 
ZooKeeper connect string, with 2 nodes running:
 
 `$ bin/solr healthcheck -c gettingstarted -z localhost:9865`
@@ -734,6 +745,16 @@ Unnecessary if `ZK_HOST` is defined in `solr.in.sh` or 
`solr.in.cmd`.
 +
 Base Solr URL, which can be used in SolrCloud mode to determine the ZooKeeper 
connection string if that's not known.
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 ==== Configuration Directories and SolrCloud
 
 Before creating a collection in SolrCloud, the configuration directory used by 
the collection must be uploaded to ZooKeeper.
@@ -843,6 +864,16 @@ Unnecessary if `ZK_HOST` is defined in `solr.in.sh` or 
`solr.in.cmd`.
 +
 Base Solr URL, which can be used in SolrCloud mode to determine the ZooKeeper 
connection string if that's not known.
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 == Authentication
 
 The `bin/solr` script allows enabling or disabling Authentication, allowing 
you to configure authentication from the command line.
@@ -880,7 +911,7 @@ Here are some example usages:
 ----
 Usage: solr auth enable [-type basicAuth] -credentials user:pass 
[-blockUnknown <true|false>] [-updateIncludeFileOnly <true|false>] [-V]
        solr auth enable [-type basicAuth] -prompt <true|false> [-blockUnknown 
<true|false>] [-updateIncludeFileOnly <true|false>] [-V]
-       solr auth enable -type kerberos -config "<kerberos configs>" 
[-updateIncludeFileOnly <true|false>] [-V]
+       solr auth enable -type kerberos -config <kerberos configs> 
[-updateIncludeFileOnly <true|false>] [-V]
        solr auth disable [-updateIncludeFileOnly <true|false>] [-V]
 ----
 
@@ -888,7 +919,7 @@ Usage: solr auth enable [-type basicAuth] -credentials 
user:pass [-blockUnknown
 
 The command takes the following parameters:
 
-`-credentials <user:pass>`::
+`-u <user:pass>` or `--credentials <user:pass>`::
 +
 [%autowidth,frame=none]
 |===
@@ -1088,6 +1119,16 @@ Base Solr URL, which can be used in SolrCloud mode to 
determine the ZooKeeper co
 +
 The scheme for accessing Solr. Accepted values: http or https.  Default is 
'http'
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 
 == ZooKeeper Operations
 
@@ -1614,6 +1655,16 @@ By default all fields are fetched.
 Maximum number of docs to download.
 The value `-1` will export all documents.
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 *Examples*
 
 Export all documents from a collection `gettingstarted`:
@@ -1671,6 +1722,7 @@ $ curl -X POST --header "Content-Type: 
application/javabin" --data-binary @getti
 == Interacting with API
 
 The `api` command will allow you to send an arbitrary HTTP request to a Solr 
API endpoint.
+If you have configured basicAuth or TLS with your Solr you may find this 
easier than using a separate tool like `curl`.
 
 `bin/solr api -get 
http://localhost:8983/solr/COLL_NAME/sql?stmt=select+id+from+COLL_NAME+limit+10`
 
@@ -1689,18 +1741,27 @@ Send a GET request to a Solr API endpoint.
 +
 *Example*: `bin/solr api -get 
http://localhost:8983/solr/COLL_NAME/sql?stmt=select+id+from+COLL_NAME+limit+10`
 
-=== API
+`-u <user:pass>` or `--credentials <username:user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
+*Examples*
 
-The `api` command will allow you to send an arbitrary HTTP request to a Solr 
API endpoint.
-If you have configured basicAuth or TLS with your Solr you may find this 
easier than using a separate tool like `curl`.
+Simple search passing in Basic authentication credentials:
 
 [source,bash]
-$ bin/solr api api -get http://localhost:8983/solr/techproducts/select?q=*:*
+$ bin/solr api -get http://localhost:8983/solr/techproducts/select?q=*:* -u 
user:password
 
 
-Here is an example of sending a SQL query to the techproducts /sql end point 
(assumes you started Solr in Cloud mode with the SQL module enabled):
+Here is an example of sending a SQL query to the techproducts `/sql` end point 
(assumes you started Solr in Cloud mode with the SQL module enabled):
 
 [source,bash]
-$ bin/solr api api -get 
http://localhost:8983/solr/techproducts/sql?stmt=select+id+from+techproducts+limit+10
+$ bin/solr api -get 
http://localhost:8983/solr/techproducts/sql?stmt=select+id+from+techproducts+limit+10
 
 Results are streamed to the terminal.
diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/post-tool.adoc 
b/solr/solr-ref-guide/modules/indexing-guide/pages/post-tool.adoc
index 28a47d28bbc..f285ac6486d 100644
--- a/solr/solr-ref-guide/modules/indexing-guide/pages/post-tool.adoc
+++ b/solr/solr-ref-guide/modules/indexing-guide/pages/post-tool.adoc
@@ -53,7 +53,7 @@ OPTIONS
   Solr options:
     -url <base Solr update URL>
     -commit issue a commit
-    -u or -user <user:pass> (sets BasicAuth credentials)
+    -u or -credentials <user:pass> (sets BasicAuth credentials)
 
   Web crawl options:
     -recursive <depth> (default: 1)
diff --git 
a/solr/solr-ref-guide/modules/query-guide/pages/document-transformers.adoc 
b/solr/solr-ref-guide/modules/query-guide/pages/document-transformers.adoc
index 3821f772a94..addbefedb9d 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/document-transformers.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/document-transformers.adoc
@@ -187,7 +187,7 @@ It is likely to be removed in a future Solr release, so _if 
you find it has some
 
 When a `\_nest_path_` field is defined, the `childFilter` option supports an 
experimental syntax to combine a "path syntax" restriction with a more 
traditional filtering query.
 
-*This syntax is triggered by including a `/` seperated path structure prior to 
a query that includes a `:` character.*
+*This syntax is triggered by including a `/` separated path structure prior to 
a query that includes a `:` character.*
 
 When the "path" begins with a `/` character, it restricts matches to documents 
that have that exist "path" of nested pseudo-field documents, starting at the 
Root document of the block (even if the document being transformed is not a 
Root level document)
 
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc 
b/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
index 7ac02556335..6246008ac7a 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
@@ -36,7 +36,7 @@ The `postlogs` command reads in Solr's log format and indexes 
it in a Solr colle
 
 ==== Healthcheck Parameters
 
-`-url <ADDRESS>`::
+`-url <address>`::
 +
 [%autowidth,frame=none]
 |===
@@ -46,7 +46,7 @@ The `postlogs` command reads in Solr's log format and indexes 
it in a Solr colle
 Address of the collection, example http://localhost:8983/solr/collection1/.
 +
 
-`-rootdir <DIRECTORY>`::
+`-rootdir <directory>`::
 +
 [%autowidth,frame=none]
 |===
@@ -56,6 +56,16 @@ Address of the collection, example 
http://localhost:8983/solr/collection1/.
 File path to root of the logs directory: All files found under this directory 
(including sub-directories) will be indexed.
 If the path points to a single log file only that log file will be loaded.
 
+`-u <user:pass>` or `--credentials <user:pass>`::
++
+[%autowidth,frame=none]
+|===
+|Optional | Default: none
+|===
++
+Basic authentication username and password separated by a `:` character.
+This parameter is unnecessary if `SOLR_AUTH_TYPE` is defined in `solr.in.sh` 
or `solr.in.cmd`.
+
 +
 *Example*: `bin/solr postlogs --url http://localhost:8983/solr/logs --rootdir 
/var/logs/solrlogs`
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index f6d8a08df98..291c91df074 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -1313,6 +1313,26 @@ public class Http2SolrClient extends SolrClient {
       this.proxyIsSecure = isSecure;
       return this;
     }
+
+    /**
+     * Setup basic authentication from a string formatted as 
username:password. If the string is
+     * Null then it doesn't do anything.
+     *
+     * @param credentials The username and password formatted as 
username:password
+     * @return this Builder
+     */
+    public Builder withOptionalBasicAuthCredentials(String credentials) {
+      if (credentials != null) {
+        if (credentials.indexOf(':') == -1) {
+          throw new IllegalStateException(
+              "Invalid Authentication credential formatting. Provide username 
and password in the 'username:password' format.");
+        }
+        String username = credentials.substring(0, credentials.indexOf(':'));
+        String password = credentials.substring(credentials.indexOf(':') + 1, 
credentials.length());
+        withBasicAuthCredentials(username, password);
+      }
+      return this;
+    }
   }
 
   public Set<String> getUrlParamNames() {
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
index dd9bf0ea927..20b96a782e7 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
@@ -929,6 +929,71 @@ public class Http2SolrClientTest extends SolrJettyTestBase 
{
     }
   }
 
+  @Test
+  public void testUseOptionalCredentials() {
+    // username foo, password with embedded colon separator is "expli:cit".
+    try (Http2SolrClient client =
+        new Http2SolrClient.Builder(getBaseUrl() + "/debug/foo")
+            .withOptionalBasicAuthCredentials("foo:expli:cit")
+            .build(); ) {
+      QueryRequest r = new QueryRequest(new SolrQuery("quick brown fox"));
+      try {
+        ignoreException("Error from server");
+        client.request(r);
+      } catch (Exception e) {
+        // expected
+      }
+      unIgnoreException("Error from server");
+      assertTrue(DebugServlet.headers.size() > 0);
+      String authorizationHeader = DebugServlet.headers.get("authorization");
+      assertNotNull(
+          "No authorization information in headers found. Headers: " + 
DebugServlet.headers,
+          authorizationHeader);
+      assertEquals(
+          "Basic "
+              + Base64.getEncoder()
+                  
.encodeToString("foo:expli:cit".getBytes(StandardCharsets.UTF_8)),
+          authorizationHeader);
+    }
+  }
+
+  @Test
+  public void testUseOptionalCredentialsWithNull() {
+    // username foo, password with embedded colon separator is "expli:cit".
+    try (Http2SolrClient client =
+        new Http2SolrClient.Builder(getBaseUrl() + "/debug/foo")
+            .withOptionalBasicAuthCredentials(null)
+            .build(); ) {
+      QueryRequest r = new QueryRequest(new SolrQuery("quick brown fox"));
+      try {
+        ignoreException("Error from server");
+        client.request(r);
+      } catch (Exception e) {
+        // expected
+      }
+      unIgnoreException("Error from server");
+      assertTrue(DebugServlet.headers.size() > 0);
+      String authorizationHeader = DebugServlet.headers.get("authorization");
+      assertNull(
+          "No authorization headers expected. Headers: " + 
DebugServlet.headers,
+          authorizationHeader);
+    }
+  }
+
+  @Test
+  public void testMalformedOptionalCredentials() {
+
+    expectThrowsAndMessage(
+        IllegalStateException.class,
+        () -> new 
Http2SolrClient.Builder().withOptionalBasicAuthCredentials("usernamepassword"),
+        "Invalid Authentication credential formatting. Provide username and 
password in the 'username:password' format.");
+
+    expectThrowsAndMessage(
+        IllegalStateException.class,
+        () -> new 
Http2SolrClient.Builder().withOptionalBasicAuthCredentials("username password"),
+        "Invalid Authentication credential formatting. Provide username and 
password in the 'username:password' format.");
+  }
+
   @Test
   public void testBadHttpFactory() {
     System.setProperty(HttpClientUtil.SYS_PROP_HTTP_CLIENT_BUILDER_FACTORY, 
"FakeClassName");
diff --git 
a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java 
b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index 5edb9ed909d..6f78c06c018 100644
--- 
a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ 
b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -343,7 +343,7 @@ public class MiniSolrCloudCluster {
   }
 
   private void waitForAllNodes(int numServers, int timeoutSeconds)
-      throws IOException, InterruptedException, TimeoutException {
+      throws InterruptedException, TimeoutException {
     log.info("waitForAllNodes: numServers={}", numServers);
 
     int numRunning;
@@ -667,7 +667,7 @@ public class MiniSolrCloudCluster {
                 IOUtils.closeQuietly(c);
               });
       solrClientByCollection.clear();
-      ;
+
       List<Callable<JettySolrRunner>> shutdowns = new 
ArrayList<>(jettys.size());
       for (final JettySolrRunner jetty : jettys) {
         shutdowns.add(() -> stopJettySolrRunner(jetty));
@@ -767,13 +767,6 @@ public class MiniSolrCloudCluster {
         .withConnectionTimeout(15000);
   }
 
-  private static String getHostContextSuitableForServletContext(String ctx) {
-    if (ctx == null || ctx.isEmpty()) ctx = "/solr";
-    if (ctx.endsWith("/")) ctx = ctx.substring(0, ctx.length() - 1);
-    if (!ctx.startsWith("/")) ctx = "/" + ctx;
-    return ctx;
-  }
-
   private Exception checkForExceptions(String message, 
Collection<Future<JettySolrRunner>> futures)
       throws InterruptedException {
     Exception parsed = new Exception(message);
@@ -814,6 +807,7 @@ public class MiniSolrCloudCluster {
     }
   }
 
+  // Currently not used ;-(
   public synchronized void injectChaos(Random random) throws Exception {
 
     // sometimes we restart one of the jetty nodes
@@ -943,11 +937,7 @@ public class MiniSolrCloudCluster {
           }
         }
       }
-      if (activeReplicas == expectedReplicas) {
-        return true;
-      }
-
-      return false;
+      return activeReplicas == expectedReplicas;
     };
   }
 

Reply via email to