Skip to content

Commit

Permalink
SOLR-14496: Add Basic Auth support to SolrCLI (apache#1954)
Browse files Browse the repository at this point in the history
Introduce parameters to the various Solr CLI commands (bin/solr) that allow them to interact with a Solr that is protected with Basic Authentication.
  • Loading branch information
epugh authored Oct 17, 2023
1 parent cfeb4db commit ddfe2f1
Show file tree
Hide file tree
Showing 36 changed files with 1,025 additions and 217 deletions.
2 changes: 1 addition & 1 deletion solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------
Expand Down
9 changes: 5 additions & 4 deletions solr/core/src/java/org/apache/solr/cli/ApiTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,28 @@ public List<Option> getOptions() {
.hasArg()
.required(true)
.desc("Send a GET request to a Solr API endpoint.")
.build());
.build(),
SolrCLI.OPTION_CREDENTIALS);
}

@Override
public void runImpl(CommandLine cli) throws Exception {
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
echo(response);
}
}

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
Expand Down
63 changes: 42 additions & 21 deletions solr/core/src/java/org/apache/solr/cli/AssertTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
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;
Expand Down Expand Up @@ -123,6 +125,14 @@ public List<Option> getOptions() {
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());
}

Expand Down Expand Up @@ -190,24 +200,34 @@ protected int runAssert(CommandLine cli) throws Exception {
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;
Expand All @@ -222,11 +242,11 @@ public static int assertSolrRunning(String url) throws Exception {
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);
Expand All @@ -236,7 +256,7 @@ public static int assertSolrNotRunning(String url) throws Exception {
}
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);
Expand All @@ -258,8 +278,8 @@ public static int assertSolrNotRunning(String url) throws Exception {
+ " 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
Expand All @@ -268,14 +288,15 @@ public static int assertSolrRunningInCloudMode(String url) throws Exception {
+ " 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
Expand All @@ -284,7 +305,7 @@ public static int assertSolrNotRunningInCloudMode(String url) throws Exception {
+ " seconds");
}

if (runningSolrIsCloud(url)) {
if (runningSolrIsCloud(url, credentials)) {
return exitOrException("Solr is not running in standalone mode on " + url);
}
return 0;
Expand Down Expand Up @@ -352,10 +373,10 @@ private static int exitOrException(String msg) throws AssertionFailureException
}
}

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)) {
Expand All @@ -365,8 +386,8 @@ private static boolean isSolrRunningOn(String url) throws Exception {
}
}

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);
}
}
Expand Down
15 changes: 5 additions & 10 deletions solr/core/src/java/org/apache/solr/cli/AuthTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,7 @@ public List<Option> getOptions() {
.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()
Expand Down Expand Up @@ -292,8 +287,8 @@ private int handleBasicAuth(CommandLine cli) throws Exception {
.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));
Expand Down Expand Up @@ -337,8 +332,8 @@ private int handleBasicAuth(CommandLine cli) throws Exception {
}

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 {
Expand Down
7 changes: 5 additions & 2 deletions solr/core/src/java/org/apache/solr/cli/ConfigTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public List<Option> getOptions() {
.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
Expand Down Expand Up @@ -108,7 +109,9 @@ public void runImpl(CommandLine cli) throws Exception {
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) {
Expand Down
21 changes: 13 additions & 8 deletions solr/core/src/java/org/apache/solr/cli/CreateTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ public List<Option> getOptions() {
.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 {
Expand Down Expand Up @@ -155,7 +155,8 @@ protected void createCore(CommandLine cli, SolrClient solrClient) throws Excepti
// 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
Expand Down Expand Up @@ -197,13 +198,16 @@ protected void createCore(CommandLine cli, SolrClient solrClient) throws Excepti
}

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();
Expand Down Expand Up @@ -275,7 +279,8 @@ protected void createCollection(CloudSolrClient cloudSolrClient, CommandLine cli
}

// 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
Expand Down
14 changes: 9 additions & 5 deletions solr/core/src/java/org/apache/solr/cli/DeleteTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public List<Option> getOptions() {
"Skip safety checks when deleting the configuration directory used by a collection.")
.build(),
SolrCLI.OPTION_ZKHOST,
SolrCLI.OPTION_CREDENTIALS,
SolrCLI.OPTION_VERBOSE);
}

Expand All @@ -92,7 +93,7 @@ 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)) {
deleteCollection(cli);
} else {
Expand All @@ -102,13 +103,16 @@ public void runImpl(CommandLine cli) throws Exception {
}

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();
Expand Down
Loading

0 comments on commit ddfe2f1

Please sign in to comment.