diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 2146539395a..bd7f19ca569 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -57,7 +57,9 @@ Detailed Change List New Features ---------------------- -* SOLR-9187: Support dates and booleans in /export handler, support boolean DocValues fields +* SOLR-9187: Support dates and booleans in /export handler, support boolean DocValues fields (Erick Erickson) + +* SOLR-8048: bin/solr script should support basic auth credentials provided in solr.in.sh (noble) Bug Fixes diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java b/solr/core/src/java/org/apache/solr/util/SolrCLI.java index 9bc986b1c01..02f8d5a3d76 100644 --- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java +++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java @@ -16,8 +16,6 @@ */ package org.apache.solr.util; -import static org.apache.solr.common.params.CommonParams.NAME; - import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -56,6 +54,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.exec.DefaultExecuteResultHandler; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.Executor; @@ -74,6 +73,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicHeader; import org.apache.http.util.EntityUtils; import org.apache.lucene.util.Version; import org.apache.solr.client.solrj.SolrClient; @@ -88,7 +88,6 @@ import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.ClusterState; -import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Slice; import org.apache.solr.common.cloud.ZkCoreNodeProps; @@ -97,6 +96,7 @@ import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.StrUtils; import org.noggit.CharArr; import org.noggit.JSONParser; import org.noggit.JSONWriter; @@ -104,11 +104,12 @@ import org.noggit.ObjectBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.solr.common.params.CommonParams.NAME; /** * Command-line utility for working with Solr. */ public class SolrCLI { - + /** * Defines the interface to a Solr tool that can be run from this command-line app. */ @@ -139,6 +140,7 @@ public class SolrCLI { int toolExitStatus = 0; try { + setBasicAuth(cli); runImpl(cli); } catch (Exception exc) { // since this is a CLI, spare the user the stacktrace @@ -153,6 +155,21 @@ public class SolrCLI { return toolExitStatus; } + private void setBasicAuth(CommandLine cli) throws Exception { + String basicauth = System.getProperty("basicauth", null); + if (basicauth != null) { + List ss = StrUtils.splitSmart(basicauth, ':'); + if (ss.size() != 2) + throw new Exception("Please provide 'basicauth' in the 'user:password' format"); + + HttpClientUtil.addRequestInterceptor((httpRequest, httpContext) -> { + String pair = ss.get(0) + ":" + ss.get(1); + byte[] encodedBytes = Base64.encodeBase64(pair.getBytes()); + httpRequest.addHeader(new BasicHeader("Authorization", "Basic "+ new String(encodedBytes))); + }); + } + } + protected abstract void runImpl(CommandLine cli) throws Exception; // It's a little awkward putting this in ToolBase, but to re-use it in upconfig and create, _and_ have access diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java index 33565dd3ba6..00a43d7a6e8 100644 --- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java +++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java @@ -16,7 +16,12 @@ */ package org.apache.solr.security; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,6 +60,7 @@ import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.StrUtils; import org.apache.solr.common.util.Utils; +import org.apache.solr.util.SolrCLI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -129,39 +135,20 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudClusterBase { "'set-user-role': {'harry':'admin'}\n" + "}"; - httpPost = new HttpPost(baseUrl + authzPrefix); - setBasicAuthHeader(httpPost, "solr", "SolrRocks"); - httpPost.setEntity(new ByteArrayEntity(command.getBytes(UTF_8))); - httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); - r = cl.execute(httpPost); - assertEquals(200, r.getStatusLine().getStatusCode()); - Utils.consumeFully(r.getEntity()); + executeCommand(baseUrl + authzPrefix, cl,command, "solr", "SolrRocks"); baseUrl = getRandomReplica(zkStateReader.getClusterState().getCollection(defaultCollName), random()).getStr(BASE_URL_PROP); verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/user-role/harry", NOT_NULL_PREDICATE, 20); - - httpPost = new HttpPost(baseUrl + authzPrefix); - setBasicAuthHeader(httpPost, "harry", "HarryIsUberCool"); - httpPost.setEntity(new ByteArrayEntity(Utils.toJSON(singletonMap("set-permission", Utils.makeMap + executeCommand(baseUrl + authzPrefix, cl, Utils.toJSONString(singletonMap("set-permission", Utils.makeMap ("collection", "x", "path", "/update/*", - "role", "dev"))))); - - httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); - verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/user-role/harry", NOT_NULL_PREDICATE, 20); - r = cl.execute(httpPost); - assertEquals(200, r.getStatusLine().getStatusCode()); - Utils.consumeFully(r.getEntity()); + "role", "dev"))), "harry", "HarryIsUberCool" ); verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[1]/collection", "x", 20); - httpPost = new HttpPost(baseUrl + authzPrefix); - setBasicAuthHeader(httpPost, "harry", "HarryIsUberCool"); - httpPost.setEntity(new ByteArrayEntity(Utils.toJSON(singletonMap("set-permission", Utils.makeMap - ("name","collection-admin-edit", "role", "admin" ))))); - r = cl.execute(httpPost); - Utils.consumeFully(r.getEntity()); + executeCommand(baseUrl + authzPrefix, cl,Utils.toJSONString(singletonMap("set-permission", Utils.makeMap + ("name", "collection-admin-edit", "role", "admin"))), "harry", "HarryIsUberCool" ); verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[2]/name", "collection-admin-edit", 20); CollectionAdminRequest.Reload reload = new CollectionAdminRequest.Reload(); @@ -196,14 +183,7 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudClusterBase { } cloudSolrClient.setDefaultCollection(old); - - httpPost = new HttpPost(baseUrl + authzPrefix); - setBasicAuthHeader(httpPost, "harry", "HarryIsUberCool"); - httpPost.setEntity(new ByteArrayEntity("{set-permission : { name : update , role : admin}}".getBytes(UTF_8))); - httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); - r = cl.execute(httpPost); - assertEquals(200,r.getStatusLine().getStatusCode()); - Utils.consumeFully(r.getEntity()); + executeCommand(baseUrl + authzPrefix, cl,"{set-permission : { name : update , role : admin}}", "harry", "HarryIsUberCool"); SolrInputDocument doc = new SolrInputDocument(); doc.setField("id","4"); @@ -212,10 +192,42 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudClusterBase { update.add(doc); update.setCommitWithin(100); cloudSolrClient.request(update); - + + + executeCommand(baseUrl + authzPrefix, cl, "{set-property : { blockUnknown: true}}", "harry", "HarryIsUberCool"); + String[] toolArgs = new String[]{ + "status", "-solr", baseUrl}; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream stdoutSim = new PrintStream(baos, true, StandardCharsets.UTF_8.name()); + SolrCLI.StatusTool tool = new SolrCLI.StatusTool(stdoutSim); + try { + System.setProperty("basicauth", "harry:HarryIsUberCool"); + tool.runTool(SolrCLI.processCommandLineArgs(SolrCLI.joinCommonAndToolOptions(tool.getOptions()), toolArgs)); + Map obj = (Map) Utils.fromJSON(new ByteArrayInputStream(baos.toByteArray())); + assertTrue(obj.containsKey("version")); + assertTrue(obj.containsKey("startTime")); + assertTrue(obj.containsKey("uptime")); + assertTrue(obj.containsKey("memory")); + } catch (Exception e) { + log.error("RunExampleTool failed due to: " + e + + "; stdout from tool prior to failure: " + baos.toString(StandardCharsets.UTF_8.name())); + } + executeCommand(baseUrl + authzPrefix, cl, "{set-property : { blockUnknown: false}}", "harry", "HarryIsUberCool"); HttpClientUtil.close(cl); } + public static void executeCommand(String url, HttpClient cl, String payload, String user, String pwd) throws IOException { + HttpPost httpPost; + HttpResponse r; + httpPost = new HttpPost(url); + setBasicAuthHeader(httpPost, user, pwd); + httpPost.setEntity(new ByteArrayEntity(payload.getBytes(UTF_8))); + httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); + r = cl.execute(httpPost); + assertEquals(200, r.getStatusLine().getStatusCode()); + Utils.consumeFully(r.getEntity()); + } + public static void verifySecurityStatus(HttpClient cl, String url, String objPath, Object expected, int count) throws Exception { boolean success = false; String s = null; @@ -262,12 +274,7 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudClusterBase { return l.isEmpty() ? null : l.get(0); } - static final Predicate NOT_NULL_PREDICATE = new Predicate() { - @Override - public boolean test(Object o) { - return o != null; - } - }; + static final Predicate NOT_NULL_PREDICATE = o -> o != null; //the password is 'SolrRocks' //this could be generated everytime. But , then we will not know if there is any regression