CVE-2020-13957
Description
Apache Solr versions 6.6.0 to 6.6.6, 7.0.0 to 7.7.3 and 8.0.0 to 8.6.2 prevents some features considered dangerous (which could be used for remote code execution) to be configured in a ConfigSet that's uploaded via API without authentication/authorization. The checks in place to prevent such features can be circumvented by using a combination of UPLOAD/CREATE actions.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Solr ConfigSet API authentication bypass allows attackers to upload dangerous configurations, enabling remote code execution.
Vulnerability
Overview CVE-2020-13957 is an authentication bypass vulnerability in Apache Solr's ConfigSet API. Apache Solr versions 6.6.0 to 6.6.6, 7.0.0 to 7.7.3, and 8.0.0 to 8.6.2 attempt to block dangerous features (which could lead to remote code execution) from being configured in a ConfigSet uploaded via the API. However, the validation checks can be circumvented by chaining UPLOAD and CREATE actions, allowing an attacker to bypass restrictions.
Exploitation
An attacker can exploit this vulnerability by sending crafted API requests to the ConfigSet endpoint without any authentication or authorization. By using a combination of UPLOAD and CREATE actions, the attacker can upload a malicious ConfigSet that includes dangerous features such as custom Java classes or external parameters, which are otherwise blocked.
Impact
Successful exploitation allows an attacker to achieve remote code execution on the Solr server. This compromises the confidentiality, integrity, and availability of the Solr instance and potentially the underlying host.
Mitigation
The vulnerability is fixed in Apache Solr 8.6.3, 7.7.3, and 6.6.6. Users should upgrade to these versions or later. The fix includes proper authorization checks in the ConfigSet API handler. Refer to the commit for detailed changes [1][2].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.solr:solr-parentMaven | >= 6.6.0, < 8.6.3 | 8.6.3 |
org.apache.solr:solr-solrjMaven | >= 6.6.0, < 8.6.3 | 8.6.3 |
org.apache.solr:solr-coreMaven | >= 6.6.0, < 8.6.3 | 8.6.3 |
Affected products
5- Apache/Solrdescription
- osv-coords4 versionspkg:bitnami/solrpkg:maven/org.apache.solr/solr-corepkg:maven/org.apache.solr/solr-parentpkg:maven/org.apache.solr/solr-solrj
>= 6.6.0, < 6.6.7+ 3 more
- (no CPE)range: >= 6.6.0, < 6.6.7
- (no CPE)range: >= 6.6.0, < 8.6.3
- (no CPE)range: >= 6.6.0, < 8.6.3
- (no CPE)range: >= 6.6.0, < 8.6.3
Patches
1e001c2221812SOLR-14663: Copy ConfigSet root data from base ConfigSet when using CREATE command
8 files changed · +396 −248
solr/CHANGES.txt+3 −0 modified@@ -35,6 +35,9 @@ Bug Fixes --------------------- * SOLR-14751: Zookeeper Admin screen not working for old ZK versions (janhoy) +* SOLR-14663: Copy ConfigSet root data from base ConfigSet when using CREATE command (Andras Salamon, Tomás Fernández Löbbe) + + ================== 8.6.1 ================== Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java+63 −25 modified@@ -29,7 +29,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.hadoop.fs.Path; import org.apache.solr.api.Api; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.cloud.OverseerSolrResponse; @@ -73,6 +72,7 @@ * A {@link org.apache.solr.request.SolrRequestHandler} for ConfigSets API requests. */ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionNameProvider { + final public static Boolean DISABLE_CREATE_AUTH_CHECKS = Boolean.getBoolean("solr.disableConfigSetsCreateAuthChecks"); // this is for back compat only private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); protected final CoreContainer coreContainer; public static long DEFAULT_ZK_TIMEOUT = 300 * 1000; @@ -87,19 +87,9 @@ public ConfigSetsHandler(final CoreContainer coreContainer) { this.coreContainer = coreContainer; } - @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - if (coreContainer == null) { - throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, - "Core container instance missing"); - } - - // Make sure that the core is ZKAware - if (!coreContainer.isZooKeeperAware()) { - throw new SolrException(ErrorCode.BAD_REQUEST, - "Solr instance is not running in SolrCloud mode."); - } + checkErrors(); // Pick the action SolrParams params = req.getParams(); @@ -120,6 +110,19 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw rsp.setHttpCaching(false); } + protected void checkErrors() { + if (coreContainer == null) { + throw new SolrException(ErrorCode.BAD_REQUEST, + "Core container instance missing"); + } + + // Make sure that the core is ZKAware + if (!coreContainer.isZooKeeperAware()) { + throw new SolrException(ErrorCode.BAD_REQUEST, + "Solr instance is not running in SolrCloud mode."); + } + } + void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetAction action) throws Exception { ConfigSetOperation operation = ConfigSetOperation.get(action); if (log.isInfoEnabled()) { @@ -153,7 +156,7 @@ private void handleConfigUploadRequest(SolrQueryRequest req, SolrQueryResponse r } SolrZkClient zkClient = coreContainer.getZkController().getZkClient(); - String configPathInZk = ZkConfigManager.CONFIGS_ZKNODE + Path.SEPARATOR + configSetName; + String configPathInZk = ZkConfigManager.CONFIGS_ZKNODE + "/" + configSetName; if (zkClient.exists(configPathInZk, true)) { throw new SolrException(ErrorCode.BAD_REQUEST, @@ -170,7 +173,7 @@ private void handleConfigUploadRequest(SolrQueryRequest req, SolrQueryResponse r InputStream inputStream = contentStreamsIterator.next().getStream(); // Create a node for the configuration in zookeeper - boolean trusted = getTrusted(req); + boolean trusted = isTrusted(req, coreContainer.getAuthenticationPlugin()); zkClient.makePath(configPathInZk, ("{\"trusted\": " + Boolean.toString(trusted) + "}"). getBytes(StandardCharsets.UTF_8), true); @@ -187,16 +190,30 @@ private void handleConfigUploadRequest(SolrQueryRequest req, SolrQueryResponse r } zis.close(); } - - boolean getTrusted(SolrQueryRequest req) { - AuthenticationPlugin authcPlugin = coreContainer.getAuthenticationPlugin(); - if (log.isInfoEnabled()) { - log.info("Trying to upload a configset. authcPlugin: {}, user principal: {}", - authcPlugin, req.getUserPrincipal()); + private static boolean isCurrentlyTrusted(SolrZkClient zkClient, String configSetZkPath) { + byte[] configSetNodeContent; + try { + configSetNodeContent = zkClient.getData(configSetZkPath, null, null, true); + if (configSetNodeContent == null || configSetNodeContent.length == 0) { + return true; + } + } catch (KeeperException e) { + throw new SolrException(ErrorCode.SERVER_ERROR, "Exception while fetching current configSet at " + configSetZkPath, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new SolrException(ErrorCode.SERVER_ERROR, "Interrupted while fetching current configSet at " + configSetZkPath, e); } - if (authcPlugin != null && req.getUserPrincipal() != null) { + @SuppressWarnings("unchecked") + Map<Object, Object> contentMap = (Map<Object, Object>) Utils.fromJSON(configSetNodeContent); + return (boolean) contentMap.getOrDefault("trusted", true); + } + + static boolean isTrusted(SolrQueryRequest req, AuthenticationPlugin authPlugin) { + if (authPlugin != null && req.getUserPrincipal() != null) { + log.debug("Trusted configset request"); return true; } + log.debug("Untrusted configset request"); return false; } @@ -271,23 +288,44 @@ public Category getCategory() { enum ConfigSetOperation { CREATE_OP(CREATE) { @Override - Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { + public Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { String baseConfigSetName = req.getParams().get(BASE_CONFIGSET, DEFAULT_CONFIGSET_NAME); + String newConfigSetName = req.getParams().get(NAME); + if (newConfigSetName == null || newConfigSetName.length() == 0) { + throw new SolrException(ErrorCode.BAD_REQUEST, "ConfigSet name not specified"); + } + + ZkConfigManager zkConfigManager = new ZkConfigManager(h.coreContainer.getZkController().getZkStateReader().getZkClient()); + if (zkConfigManager.configExists(newConfigSetName)) { + throw new SolrException(ErrorCode.BAD_REQUEST, "ConfigSet already exists: " + newConfigSetName); + } + + // is there a base config that already exists + if (!zkConfigManager.configExists(baseConfigSetName)) { + throw new SolrException(ErrorCode.BAD_REQUEST, + "Base ConfigSet does not exist: " + baseConfigSetName); + } + Map<String, Object> props = CollectionsHandler.copy(req.getParams().required(), null, NAME); props.put(BASE_CONFIGSET, baseConfigSetName); + if (!DISABLE_CREATE_AUTH_CHECKS && + !isTrusted(req, h.coreContainer.getAuthenticationPlugin()) && + isCurrentlyTrusted(h.coreContainer.getZkController().getZkClient(), ZkConfigManager.CONFIGS_ZKNODE + "/" + baseConfigSetName)) { + throw new SolrException(ErrorCode.UNAUTHORIZED, "Can't create a configset with an unauthenticated request from a trusted " + BASE_CONFIGSET); + } return copyPropertiesWithPrefix(req.getParams(), props, PROPERTY_PREFIX + "."); } }, DELETE_OP(DELETE) { @Override - Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { + public Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { return CollectionsHandler.copy(req.getParams().required(), null, NAME); } }, @SuppressWarnings({"unchecked"}) LIST_OP(LIST) { @Override - Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { + public Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception { NamedList<Object> results = new NamedList<>(); SolrZkClient zk = h.coreContainer.getZkController().getZkStateReader().getZkClient(); ZkConfigManager zkConfigManager = new ZkConfigManager(zk); @@ -305,7 +343,7 @@ Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSets this.action = action; } - abstract Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception; + public abstract Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception; public static ConfigSetOperation get(ConfigSetAction action) { for (ConfigSetOperation op : values()) {
solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIExclusivity.java+3 −0 modified@@ -17,6 +17,7 @@ package org.apache.solr.cloud; import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -92,6 +93,8 @@ public void testAPIExclusivity() throws Exception { private void setupBaseConfigSet(String baseConfigSetName) throws Exception { solrCluster.uploadConfigSet(configset("configset-2"), baseConfigSetName); + //Make configset untrusted + solrCluster.getZkClient().setData("/configs/" + baseConfigSetName, "{\"trusted\": false}".getBytes(StandardCharsets.UTF_8), true); } private Exception getFirstExceptionOrNull(List<Exception> list) {
solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java+301 −212 modified@@ -16,6 +16,13 @@ */ package org.apache.solr.cloud; +import javax.script.ScriptEngineManager; +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -27,66 +34,67 @@ import java.lang.invoke.MethodHandles; import java.net.URI; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import java.security.Principal; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import javax.script.ScriptEngineManager; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import org.apache.http.HttpEntity; -import org.apache.http.client.HttpClient; +import org.apache.http.auth.BasicUserPrincipal; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.message.BasicHeader; import org.apache.http.util.EntityUtils; import org.apache.lucene.util.TestUtil; -import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.embedded.JettySolrRunner; +import org.apache.solr.client.solrj.impl.BaseHttpSolrClient; import org.apache.solr.client.solrj.impl.CloudSolrClient; -import org.apache.solr.client.solrj.impl.HttpClientUtil; -import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.request.ConfigSetAdminRequest; import org.apache.solr.client.solrj.request.ConfigSetAdminRequest.Create; import org.apache.solr.client.solrj.request.ConfigSetAdminRequest.Delete; import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.client.solrj.response.CollectionAdminResponse; import org.apache.solr.client.solrj.response.ConfigSetAdminResponse; +import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.cloud.ZkConfigManager; -import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.params.CollectionParams.CollectionAction; import org.apache.solr.common.params.ConfigSetParams; import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; -import org.apache.solr.common.util.Base64; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.Utils; import org.apache.solr.core.ConfigSetProperties; import org.apache.solr.core.TestDynamicLoading; -import org.apache.solr.security.BasicAuthIntegrationTest; +import org.apache.solr.security.AuthenticationPlugin; +import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationPlugin; +import org.apache.solr.security.AuthorizationResponse; import org.apache.solr.servlet.SolrDispatchFilter; import org.apache.solr.util.ExternalPaths; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.junit.After; +import org.junit.AfterClass; +import org.apache.zookeeper.data.Stat; import org.junit.Assume; -import org.junit.Before; -import org.junit.Ignore; +import org.junit.BeforeClass; import org.junit.Test; import org.noggit.JSONParser; import org.slf4j.Logger; @@ -100,76 +108,114 @@ /** * Simple ConfigSets API tests on user errors and simple success cases. */ -public class TestConfigSetsAPI extends SolrTestCaseJ4 { +public class TestConfigSetsAPI extends SolrCloudTestCase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private MiniSolrCloudCluster solrCluster; - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - solrCluster = new MiniSolrCloudCluster(1, createTempDir(), buildJettyConfig("/solr")); + private static ZkConfigManager zkConfigManager; + + @BeforeClass + public static void setUpClass() throws Exception { + configureCluster(1) + .withSecurityJson(getSecurityJson()) + .configure(); + zkConfigManager = new ZkConfigManager(cluster.getZkClient()); + } + + @AfterClass + public static void tearDownClass() throws Exception { + zkConfigManager = null; } @Override @After public void tearDown() throws Exception { - if (null != solrCluster) { - solrCluster.shutdown(); - solrCluster = null; - } + cluster.deleteAllCollections(); + cluster.deleteAllConfigSets(); super.tearDown(); } @Test public void testCreateErrors() throws Exception { - final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString(); - final SolrClient solrClient = getHttpSolrClient(baseUrl); - solrCluster.uploadConfigSet(configset("configset-2"), "configSet"); - - // no action - CreateNoErrorChecking createNoAction = new CreateNoErrorChecking(); - createNoAction.setAction(null); - verifyException(solrClient, createNoAction, "action"); - - // no ConfigSet name - CreateNoErrorChecking create = new CreateNoErrorChecking(); - verifyException(solrClient, create, NAME); - - // set ConfigSet - create.setConfigSetName("configSetName"); - - // ConfigSet already exists - Create alreadyExists = new Create(); - alreadyExists.setConfigSetName("configSet").setBaseConfigSetName("baseConfigSet"); - verifyException(solrClient, alreadyExists, "ConfigSet already exists"); - - // Base ConfigSet does not exist - Create baseConfigNoExists = new Create(); - baseConfigNoExists.setConfigSetName("newConfigSet").setBaseConfigSetName("baseConfigSet"); - verifyException(solrClient, baseConfigNoExists, "Base ConfigSet does not exist"); - - solrClient.close(); + final String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString(); + try (final SolrClient solrClient = getHttpSolrClient(baseUrl)) { + zkConfigManager.uploadConfigDir(configset("configset-2"), "configSet"); + + // no action + CreateNoErrorChecking createNoAction = new CreateNoErrorChecking(); + createNoAction.setAction(null); + verifyException(solrClient, createNoAction, "action"); + + // no ConfigSet name + CreateNoErrorChecking create = new CreateNoErrorChecking(); + verifyException(solrClient, create, NAME); + + // set ConfigSet + create.setConfigSetName("configSetName"); + + // ConfigSet already exists + Create alreadyExists = new Create(); + alreadyExists.setConfigSetName("configSet").setBaseConfigSetName("baseConfigSet"); + verifyException(solrClient, alreadyExists, "ConfigSet already exists"); + + // Base ConfigSet does not exist + Create baseConfigNoExists = new Create(); + baseConfigNoExists.setConfigSetName("newConfigSet").setBaseConfigSetName("baseConfigSet"); + verifyException(solrClient, baseConfigNoExists, "Base ConfigSet does not exist"); + } } @Test public void testCreate() throws Exception { // no old, no new - verifyCreate(null, "configSet1", null, null); + verifyCreate(null, "configSet1", null, null, "solr"); // no old, new verifyCreate("baseConfigSet2", "configSet2", - null, ImmutableMap.<String, String>of("immutable", "true", "key1", "value1")); + null, ImmutableMap.<String, String>of("immutable", "true", "key1", "value1"), "solr"); // old, no new verifyCreate("baseConfigSet3", "configSet3", - ImmutableMap.<String, String>of("immutable", "false", "key2", "value2"), null); + ImmutableMap.<String, String>of("immutable", "false", "key2", "value2"), null, "solr"); // old, new verifyCreate("baseConfigSet4", "configSet4", ImmutableMap.<String, String>of("immutable", "true", "onlyOld", "onlyOldValue"), - ImmutableMap.<String, String>of("immutable", "false", "onlyNew", "onlyNewValue")); + ImmutableMap.<String, String>of("immutable", "false", "onlyNew", "onlyNewValue"), "solr"); + } + + @Test + public void testCreateWithTrust() throws Exception { + String configsetName = "regular"; + String configsetSuffix = "testCreateWithTrust"; + String configsetSuffix2 = "testCreateWithTrust2"; + uploadConfigSetWithAssertions(configsetName, configsetSuffix, "solr"); + uploadConfigSetWithAssertions(configsetName, configsetSuffix2, null); + try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), + AbstractZkTestCase.TIMEOUT, 45000, null)) { + assertTrue(isTrusted(zkClient, configsetName, configsetSuffix)); + assertFalse(isTrusted(zkClient, configsetName, configsetSuffix2)); + try { + ignoreException("unauthenticated request"); + // trusted -> unstrusted + createConfigSet(configsetName + configsetSuffix, "foo", Collections.emptyMap(), cluster.getSolrClient(), null); + fail("Expecting exception"); + } catch (SolrException e) { + assertEquals(SolrException.ErrorCode.UNAUTHORIZED.code, e.code()); + unIgnoreException("unauthenticated request"); + } + // trusted -> trusted + verifyCreate(configsetName + configsetSuffix, "foo2", Collections.emptyMap(), Collections.emptyMap(), "solr"); + assertTrue(isTrusted(zkClient, "foo2", "")); + + // unstrusted -> unstrusted + verifyCreate(configsetName + configsetSuffix2, "bar", Collections.emptyMap(), Collections.emptyMap(), null); + assertFalse(isTrusted(zkClient, "bar", "")); + + // unstrusted -> trusted + verifyCreate(configsetName + configsetSuffix2, "bar2", Collections.emptyMap(), Collections.emptyMap(), "solr"); + assertFalse(isTrusted(zkClient, "bar2", "")); + } } private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> oldProps) throws Exception { @@ -179,39 +225,46 @@ private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> ol FileUtils.copyDirectory(configDir, tmpConfigDir); if (oldProps != null) { FileUtils.write(new File(tmpConfigDir, ConfigSetProperties.DEFAULT_FILENAME), - getConfigSetProps(oldProps), StandardCharsets.UTF_8); + getConfigSetProps(oldProps), UTF_8); } - solrCluster.uploadConfigSet(tmpConfigDir.toPath(), baseConfigSetName); + zkConfigManager.uploadConfigDir(tmpConfigDir.toPath(), baseConfigSetName); } private void verifyCreate(String baseConfigSetName, String configSetName, - Map<String, String> oldProps, Map<String, String> newProps) throws Exception { - final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString(); - final SolrClient solrClient = getHttpSolrClient(baseUrl); - setupBaseConfigSet(baseConfigSetName, oldProps); + Map<String, String> oldProps, Map<String, String> newProps, String username) throws Exception { + final String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString(); + try (final SolrClient solrClient = getHttpSolrClient(baseUrl)) { + setupBaseConfigSet(baseConfigSetName, oldProps); - SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), - AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null); - try { - ZkConfigManager configManager = new ZkConfigManager(zkClient); - assertFalse(configManager.configExists(configSetName)); - - Create create = new Create(); - create.setBaseConfigSetName(baseConfigSetName).setConfigSetName(configSetName); - if (newProps != null) { - Properties p = new Properties(); - p.putAll(newProps); - create.setNewConfigSetProperties(p); + SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), + AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null); + try { + ZkConfigManager configManager = new ZkConfigManager(zkClient); + assertFalse(configManager.configExists(configSetName)); + + ConfigSetAdminResponse response = createConfigSet(baseConfigSetName, configSetName, newProps, solrClient, username); + assertNotNull(response.getResponse()); + assertTrue(configManager.configExists(configSetName)); + + verifyProperties(configSetName, oldProps, newProps, zkClient); + } finally { + zkClient.close(); } - ConfigSetAdminResponse response = create.process(solrClient); - assertNotNull(response.getResponse()); - assertTrue(configManager.configExists(configSetName)); + } + } - verifyProperties(configSetName, oldProps, newProps, zkClient); - } finally { - zkClient.close(); + private ConfigSetAdminResponse createConfigSet(String baseConfigSetName, String configSetName, Map<String, String> newProps, SolrClient solrClient, String username) throws SolrServerException, IOException { + Create create = new Create(); + create.setBaseConfigSetName(baseConfigSetName).setConfigSetName(configSetName); + if (newProps != null) { + Properties p = new Properties(); + p.putAll(newProps); + create.setNewConfigSetProperties(p); } - solrClient.close(); + if (username != null) { + create.addHeader("user", username); + } + return create.process(solrClient); } @SuppressWarnings({"rawtypes"}) @@ -225,7 +278,7 @@ private NamedList getConfigSetPropertiesFromZk( } if (oldPropsData != null) { - InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(oldPropsData), StandardCharsets.UTF_8); + InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(oldPropsData), UTF_8); try { return ConfigSetProperties.readFromInputStream(reader); } finally { @@ -281,35 +334,39 @@ private void verifyProperties(String configSetName, Map<String, String> oldProps @Test public void testUploadErrors() throws Exception { - final SolrClient solrClient = getHttpSolrClient(solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString()); + final SolrClient solrClient = getHttpSolrClient(cluster.getJettySolrRunners().get(0).getBaseUrl().toString()); ByteBuffer emptyData = ByteBuffer.allocate(0); + ignoreException("The configuration name should be provided"); // Checking error when no configuration name is specified in request @SuppressWarnings({"rawtypes"}) - Map map = postDataAndGetResponse(solrCluster.getSolrClient(), - solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString() - + "/admin/configs?action=UPLOAD", emptyData, null, null); + Map map = postDataAndGetResponse(cluster.getSolrClient(), + cluster.getJettySolrRunners().get(0).getBaseUrl().toString() + + "/admin/configs?action=UPLOAD", emptyData, null); assertNotNull(map); + unIgnoreException("The configuration name should be provided"); long statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status")); assertEquals(400l, statusCode); - SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), + SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null); // Create dummy config files in zookeeper zkClient.makePath("/configs/myconf", true); zkClient.create("/configs/myconf/firstDummyFile", - "first dummy content".getBytes(StandardCharsets.UTF_8), CreateMode.PERSISTENT, true); + "first dummy content".getBytes(UTF_8), CreateMode.PERSISTENT, true); zkClient.create("/configs/myconf/anotherDummyFile", - "second dummy content".getBytes(StandardCharsets.UTF_8), CreateMode.PERSISTENT, true); + "second dummy content".getBytes(UTF_8), CreateMode.PERSISTENT, true); // Checking error when configuration name specified already exists - map = postDataAndGetResponse(solrCluster.getSolrClient(), - solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString() - + "/admin/configs?action=UPLOAD&name=myconf", emptyData, null, null); + ignoreException("already exists"); + map = postDataAndGetResponse(cluster.getSolrClient(), + cluster.getJettySolrRunners().get(0).getBaseUrl().toString() + + "/admin/configs?action=UPLOAD&name=myconf", emptyData, null); assertNotNull(map); + unIgnoreException("already exists`"); statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status")); assertEquals(400l, statusCode); @@ -324,177 +381,158 @@ public void testUploadErrors() throws Exception { @Test public void testUploadDisabled() throws Exception { - try (SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), + try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null)) { + ignoreException("Configset upload feature is disabled"); for (boolean enabled: new boolean[] {true, false}) { System.setProperty("configset.upload.enabled", String.valueOf(enabled)); try { - long statusCode = uploadConfigSet("regular", "test-enabled-is-" + enabled, null, null, zkClient); + long statusCode = uploadConfigSet("regular", "test-enabled-is-" + enabled, null, zkClient); assertEquals("ConfigSet upload enabling/disabling not working as expected for enabled=" + enabled + ".", enabled? 0l: 400l, statusCode); } finally { System.clearProperty("configset.upload.enabled"); } } + unIgnoreException("Configset upload feature is disabled"); + } + } + + private boolean isTrusted(SolrZkClient zkClient, String configsetName, String configsetSuffix) throws KeeperException, InterruptedException { + String configSetZkPath = String.format(Locale.ROOT,"/configs/%s%s", configsetName, configsetSuffix); + byte[] configSetNodeContent = zkClient.getData(configSetZkPath, null, null, true); + if (configSetNodeContent == null || configSetNodeContent.length == 0) { + return true; } + + @SuppressWarnings("unchecked") + Map<Object, Object> contentMap = (Map<Object, Object>) Utils.fromJSON(configSetNodeContent); + return (boolean) contentMap.getOrDefault("trusted", true); + } + + private int getConfigZNodeVersion(SolrZkClient zkClient, String configsetName, String configsetSuffix, String configFile) throws KeeperException, InterruptedException { + Stat stat = new Stat(); + zkClient.getData(String.format(Locale.ROOT, "/configs/%s%s/%s", configsetName, configsetSuffix, configFile), null, stat, true); + return stat.getVersion(); } @Test public void testUpload() throws Exception { String suffix = "-untrusted"; - uploadConfigSetWithAssertions("regular", suffix, null, null); + uploadConfigSetWithAssertions("regular", suffix, null); // try to create a collection with the uploaded configset - createCollection("newcollection", "regular" + suffix, 1, 1, solrCluster.getSolrClient()); + createCollection("newcollection", "regular" + suffix, 1, 1, cluster.getSolrClient()); } - + @Test public void testUploadWithScriptUpdateProcessor() throws Exception { Assume.assumeNotNull((new ScriptEngineManager()).getEngineByExtension("js")); Assume.assumeNotNull((new ScriptEngineManager()).getEngineByName("JavaScript")); - - // Authorization off - // unprotectConfigsHandler(); // TODO Enable this back when testUploadWithLibDirective() is re-enabled - final String untrustedSuffix = "-untrusted"; - uploadConfigSetWithAssertions("with-script-processor", untrustedSuffix, null, null); - // try to create a collection with the uploaded configset - Throwable thrown = expectThrows(HttpSolrClient.RemoteSolrException.class, () -> { - createCollection("newcollection2", "with-script-processor" + untrustedSuffix, - 1, 1, solrCluster.getSolrClient()); - }); - + // Authorization off + final String untrustedSuffix = "-untrusted"; + uploadConfigSetWithAssertions("with-script-processor", untrustedSuffix, null); + // try to create a collection with the uploaded configset + ignoreException("uploaded without any authentication in place"); + Throwable thrown = expectThrows(BaseHttpSolrClient.RemoteSolrException.class, () -> { + createCollection("newcollection2", "with-script-processor" + untrustedSuffix, + 1, 1, cluster.getSolrClient()); + }); + unIgnoreException("uploaded without any authentication in place"); assertThat(thrown.getMessage(), containsString("Underlying core creation failed")); // Authorization on final String trustedSuffix = "-trusted"; - protectConfigsHandler(); - uploadConfigSetWithAssertions("with-script-processor", trustedSuffix, "solr", "SolrRocks"); + uploadConfigSetWithAssertions("with-script-processor", trustedSuffix, "solr"); // try to create a collection with the uploaded configset CollectionAdminResponse resp = createCollection("newcollection2", "with-script-processor" + trustedSuffix, - 1, 1, solrCluster.getSolrClient()); + 1, 1, cluster.getSolrClient()); scriptRequest("newcollection2"); } @Test - @Ignore // enable this back when the sleep is removed from protectConfigsHandler() call public void testUploadWithLibDirective() throws Exception { - // Authorization off - unprotectConfigsHandler(); final String untrustedSuffix = "-untrusted"; - uploadConfigSetWithAssertions("with-lib-directive", untrustedSuffix, null, null); + uploadConfigSetWithAssertions("with-lib-directive", untrustedSuffix, null); // try to create a collection with the uploaded configset - Throwable thrown = expectThrows(HttpSolrClient.RemoteSolrException.class, () -> { + ignoreException("without any authentication in place"); + Throwable thrown = expectThrows(BaseHttpSolrClient.RemoteSolrException.class, () -> { createCollection("newcollection3", "with-lib-directive" + untrustedSuffix, - 1, 1, solrCluster.getSolrClient()); + 1, 1, cluster.getSolrClient()); }); + unIgnoreException("without any authentication in place"); assertThat(thrown.getMessage(), containsString("Underlying core creation failed")); // Authorization on final String trustedSuffix = "-trusted"; - protectConfigsHandler(); - uploadConfigSetWithAssertions("with-lib-directive", trustedSuffix, "solr", "SolrRocks"); + uploadConfigSetWithAssertions("with-lib-directive", trustedSuffix, "solr"); // try to create a collection with the uploaded configset CollectionAdminResponse resp = createCollection("newcollection3", "with-lib-directive" + trustedSuffix, - 1, 1, solrCluster.getSolrClient()); + 1, 1, cluster.getSolrClient()); SolrInputDocument doc = sdoc("id", "4055", "subject", "Solr"); - solrCluster.getSolrClient().add("newcollection3", doc); - solrCluster.getSolrClient().commit("newcollection3"); - assertEquals("4055", solrCluster.getSolrClient().query("newcollection3", + cluster.getSolrClient().add("newcollection3", doc); + cluster.getSolrClient().commit("newcollection3"); + assertEquals("4055", cluster.getSolrClient().query("newcollection3", params("q", "*:*")).getResults().get(0).get("id")); } - protected SolrZkClient zkClient() { - ZkStateReader reader = solrCluster.getSolrClient().getZkStateReader(); - if (reader == null) - solrCluster.getSolrClient().connect(); - return solrCluster.getSolrClient().getZkStateReader().getZkClient(); - } - - private void unprotectConfigsHandler() throws Exception { - HttpClient cl = null; - try { - cl = HttpClientUtil.createClient(null); - zkClient().setData("/security.json", "{}".getBytes(UTF_8), true); - } finally { - if (cl != null) { - HttpClientUtil.close(cl); - } - } - Thread.sleep(1000); // TODO: Without a delay, the test fails. Some problem with Authc/Authz framework? - } - - private void protectConfigsHandler() throws Exception { - String authcPrefix = "/admin/authentication"; - String authzPrefix = "/admin/authorization"; - + private static String getSecurityJson() throws KeeperException, InterruptedException { String securityJson = "{\n" + - " 'authentication':{\n" + - " 'class':'solr.BasicAuthPlugin',\n" + - " 'credentials':{'solr':'orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw='}},\n" + - " 'authorization':{\n" + - " 'class':'solr.RuleBasedAuthorizationPlugin',\n" + - " 'user-role':{'solr':'admin'},\n" + - " 'permissions':[{'name':'security-edit','role':'admin'}, {'name':'config-edit','role':'admin'}]}}"; - - HttpClient cl = null; - try { - cl = HttpClientUtil.createClient(null); - JettySolrRunner randomJetty = solrCluster.getRandomJetty(random()); - String baseUrl = randomJetty.getBaseUrl().toString(); - - zkClient().setData("/security.json", securityJson.replaceAll("'", "\"").getBytes(UTF_8), true); - BasicAuthIntegrationTest.verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/class", "solr.BasicAuthPlugin", 50); - BasicAuthIntegrationTest.verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/class", "solr.RuleBasedAuthorizationPlugin", 50); - } finally { - if (cl != null) { - HttpClientUtil.close(cl); - } - } - Thread.sleep(1000); // TODO: Without a delay, the test fails. Some problem with Authc/Authz framework? + " 'authentication':{\n" + + " 'class':'" + MockAuthenticationPlugin.class.getName() + "'},\n" + + " 'authorization':{\n" + + " 'class':'" + MockAuthorizationPlugin.class.getName() + "'}}"; + return securityJson; } - private void uploadConfigSetWithAssertions(String configSetName, String suffix, String username, String password) throws Exception { - SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), + private void uploadConfigSetWithAssertions(String configSetName, String suffix, String username) throws Exception { + SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null); try { - long statusCode = uploadConfigSet(configSetName, suffix, username, password, zkClient); + long statusCode = uploadConfigSet(configSetName, suffix, username, zkClient); assertEquals(0l, statusCode); - - assertTrue("managed-schema file should have been uploaded", - zkClient.exists("/configs/"+configSetName+suffix+"/managed-schema", true)); - assertTrue("managed-schema file contents on zookeeper are not exactly same as that of the file uploaded in config", - Arrays.equals(zkClient.getData("/configs/"+configSetName+suffix+"/managed-schema", null, null, true), - readFile("solr/configsets/upload/"+configSetName+"/managed-schema"))); - - assertTrue("solrconfig.xml file should have been uploaded", - zkClient.exists("/configs/"+configSetName+suffix+"/solrconfig.xml", true)); - byte data[] = zkClient.getData("/configs/"+configSetName+suffix, null, null, true); - //assertEquals("{\"trusted\": false}", new String(data, StandardCharsets.UTF_8)); - assertTrue("solrconfig.xml file contents on zookeeper are not exactly same as that of the file uploaded in config", - Arrays.equals(zkClient.getData("/configs/"+configSetName+suffix+"/solrconfig.xml", null, null, true), - readFile("solr/configsets/upload/"+configSetName+"/solrconfig.xml"))); + assertConfigsetFiles(configSetName, suffix, zkClient); } finally { zkClient.close(); } } + private void assertConfigsetFiles(String configSetName, String suffix, SolrZkClient zkClient) throws KeeperException, InterruptedException, IOException { + assertTrue("managed-schema file should have been uploaded", + zkClient.exists("/configs/"+configSetName+suffix+"/managed-schema", true)); + assertTrue("managed-schema file contents on zookeeper are not exactly same as that of the file uploaded in config", + Arrays.equals(zkClient.getData("/configs/"+configSetName+suffix+"/managed-schema", null, null, true), + readFile("solr/configsets/upload/"+configSetName+"/managed-schema"))); - private long uploadConfigSet(String configSetName, String suffix, String username, String password, - SolrZkClient zkClient) throws IOException { + assertTrue("solrconfig.xml file should have been uploaded", + zkClient.exists("/configs/"+configSetName+suffix+"/solrconfig.xml", true)); + byte data[] = zkClient.getData("/configs/"+configSetName+suffix, null, null, true); + //assertEquals("{\"trusted\": false}", new String(data, StandardCharsets.UTF_8)); + assertTrue("solrconfig.xml file contents on zookeeper are not exactly same as that of the file uploaded in config", + Arrays.equals(zkClient.getData("/configs/"+configSetName+suffix+"/solrconfig.xml", null, null, true), + readFile("solr/configsets/upload/"+configSetName+"/solrconfig.xml"))); + } + + private long uploadConfigSet(String configSetName, String suffix, String username, + SolrZkClient zkClient) throws IOException { + ZkConfigManager configManager = new ZkConfigManager(zkClient); + assertFalse(configManager.configExists(configSetName + suffix)); + return uploadConfigSet(configSetName, suffix, username, zkClient, false, false); + } + + private long uploadConfigSet(String configSetName, String suffix, String username, + SolrZkClient zkClient, boolean overwrite, boolean cleanup) throws IOException { // Read zipped sample config ByteBuffer sampleZippedConfig = TestDynamicLoading .getFileContent( createTempZipFile("solr/configsets/upload/"+configSetName), false); - ZkConfigManager configManager = new ZkConfigManager(zkClient); - assertFalse(configManager.configExists(configSetName+suffix)); - @SuppressWarnings({"rawtypes"}) - Map map = postDataAndGetResponse(solrCluster.getSolrClient(), - solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString() + "/admin/configs?action=UPLOAD&name="+configSetName+suffix, - sampleZippedConfig, username, password); + Map map = postDataAndGetResponse(cluster.getSolrClient(), + cluster.getJettySolrRunners().get(0).getBaseUrl().toString() + "/admin/configs?action=UPLOAD&name="+configSetName+suffix + (overwrite? "&overwrite=true" : "") + (cleanup? "&cleanup=true" : ""), + sampleZippedConfig, username); assertNotNull(map); long statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status")); return statusCode; @@ -505,7 +543,7 @@ private long uploadConfigSet(String configSetName, String suffix, String usernam * and return the path for the zip file. */ private String createTempZipFile(String directoryPath) { - File zipFile = new File(solrCluster.getBaseDir().toFile().getAbsolutePath() + + File zipFile = new File(cluster.getBaseDir().toFile().getAbsolutePath() + File.separator + TestUtil.randomSimpleString(random(), 6, 8) + ".zip"); File directory = TestDynamicLoading.getFile(directoryPath); @@ -565,7 +603,7 @@ private static void zip(File directory, File zipfile) throws IOException { } public void scriptRequest(String collection) throws SolrServerException, IOException { - SolrClient client = solrCluster.getSolrClient(); + SolrClient client = cluster.getSolrClient(); SolrInputDocument doc = sdoc("id", "4055", "subject", "Solr"); client.add(collection, doc); client.commit(collection); @@ -592,7 +630,7 @@ protected CollectionAdminResponse createCollection(String collectionName, String @SuppressWarnings({"rawtypes"}) public static Map postDataAndGetResponse(CloudSolrClient cloudClient, - String uri, ByteBuffer bytarr, String username, String password) throws IOException { + String uri, ByteBuffer bytarr, String username) throws IOException { HttpPost httpPost = null; HttpEntity entity; String response = null; @@ -602,19 +640,17 @@ public static Map postDataAndGetResponse(CloudSolrClient cloudClient, httpPost = new HttpPost(uri); if (username != null) { - String userPass = username + ":" + password; - String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8)); - BasicHeader header = new BasicHeader("Authorization", "Basic " + encoded); - httpPost.setHeader(header); + httpPost.addHeader(new BasicHeader("user", username)); } httpPost.setHeader("Content-Type", "application/octet-stream"); httpPost.setEntity(new ByteArrayEntity(bytarr.array(), bytarr .arrayOffset(), bytarr.limit())); + log.info("Uploading configset with user {}", username); entity = cloudClient.getLbClient().getHttpClient().execute(httpPost) .getEntity(); try { - response = EntityUtils.toString(entity, StandardCharsets.UTF_8); + response = EntityUtils.toString(entity, UTF_8); m = (Map) Utils.fromJSONString(response); } catch (JSONParser.ParseException e) { System.err.println("err response: " + response); @@ -659,16 +695,16 @@ private byte[] readFile(String fname) throws IOException { @Test public void testDeleteErrors() throws Exception { - final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString(); + final String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString(); final SolrClient solrClient = getHttpSolrClient(baseUrl); final File configDir = getFile("solr").toPath().resolve("configsets/configset-2/conf").toFile(); final File tmpConfigDir = createTempDir().toFile(); tmpConfigDir.deleteOnExit(); // Ensure ConfigSet is immutable FileUtils.copyDirectory(configDir, tmpConfigDir); FileUtils.write(new File(tmpConfigDir, "configsetprops.json"), - getConfigSetProps(ImmutableMap.<String, String>of("immutable", "true")), StandardCharsets.UTF_8); - solrCluster.uploadConfigSet(tmpConfigDir.toPath(), "configSet"); + getConfigSetProps(ImmutableMap.<String, String>of("immutable", "true")), UTF_8); + zkConfigManager.uploadConfigDir(tmpConfigDir.toPath(), "configSet"); // no ConfigSet name DeleteNoErrorChecking delete = new DeleteNoErrorChecking(); @@ -688,19 +724,21 @@ public void testDeleteErrors() throws Exception { private void verifyException(SolrClient solrClient, @SuppressWarnings({"rawtypes"})ConfigSetAdminRequest request, String errorContains) throws Exception { + ignoreException(errorContains); Exception e = expectThrows(Exception.class, () -> solrClient.request(request)); assertTrue("Expected exception message to contain: " + errorContains + " got: " + e.getMessage(), e.getMessage().contains(errorContains)); + unIgnoreException(errorContains); } @Test public void testDelete() throws Exception { - final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString(); + final String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString(); final SolrClient solrClient = getHttpSolrClient(baseUrl); - final String configSet = "configSet"; - solrCluster.uploadConfigSet(configset("configset-2"), configSet); + final String configSet = "testDelete"; + zkConfigManager.uploadConfigDir(configset("configset-2"), configSet); - SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), + SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null); try { ZkConfigManager configManager = new ZkConfigManager(zkClient); @@ -720,10 +758,10 @@ public void testDelete() throws Exception { @Test public void testList() throws Exception { - final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString(); + final String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString(); final SolrClient solrClient = getHttpSolrClient(baseUrl); - SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(), + SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null); try { // test empty @@ -736,7 +774,7 @@ public void testList() throws Exception { Set<String> configSets = new HashSet<String>(); for (int i = 0; i < 5; ++i) { String configSet = "configSet" + i; - solrCluster.uploadConfigSet(configset("configset-2"), configSet); + zkConfigManager.uploadConfigDir(configset("configset-2"), configSet); configSets.add(configSet); } response = list.process(solrClient); @@ -778,7 +816,7 @@ public void testUserAndTestDefaultConfigsetsAreSame() throws IOException { } private StringBuilder getConfigSetProps(Map<String, String> map) { - return new StringBuilder(new String(Utils.toJSON(map), StandardCharsets.UTF_8)); + return new StringBuilder(new String(Utils.toJSON(map), UTF_8)); } public static class CreateNoErrorChecking extends ConfigSetAdminRequest.Create { @@ -811,4 +849,55 @@ public SolrParams getParams() { return params; } } + + public static class MockAuthenticationPlugin extends AuthenticationPlugin { + + @Override + public void init(Map<String, Object> pluginConfig) { + + } + + @Override + public boolean doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception { + if (((HttpServletRequest)request).getHeader("user") != null) { + final Principal p = new BasicUserPrincipal("solr"); + filterChain.doFilter(wrap((HttpServletRequest)request, p, "solr"), response); + } else { + filterChain.doFilter(request, response); + } + return true; + } + + HttpServletRequest wrap(HttpServletRequest request, Principal principal, String username) { + return new HttpServletRequestWrapper(request) { + @Override + public Principal getUserPrincipal() { + return principal; + } + + @Override + public String getRemoteUser() { + return username; + } + }; + } + } + + public static class MockAuthorizationPlugin implements AuthorizationPlugin { + + @Override + public AuthorizationResponse authorize(AuthorizationContext context) { + return AuthorizationResponse.OK; + } + + @Override + public void init(Map<String, Object> initInfo) { + + } + + @Override + public void close() throws IOException { + + } + } }
solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIZkFailure.java+2 −0 modified@@ -136,6 +136,8 @@ private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> ol getConfigSetProps(oldProps), StandardCharsets.UTF_8); } solrCluster.uploadConfigSet(tmpConfigDir.toPath(), baseConfigSetName); + //Make configset untrusted + solrCluster.getZkClient().setData("/configs/" + baseConfigSetName, "{\"trusted\": false}".getBytes(StandardCharsets.UTF_8), true); } private StringBuilder getConfigSetProps(Map<String, String> map) {
solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java+0 −3 modified@@ -29,7 +29,6 @@ import static java.util.Collections.EMPTY_MAP; import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; import static org.apache.solr.handler.admin.TestCollectionAPIs.compareOutput; @@ -54,8 +53,6 @@ protected void sendToZk(SolrQueryResponse rsp, compareOutput(apiBag, "/cluster/configs/sample", DELETE, null, null, "{name :sample, operation:delete}"); - compareOutput(apiBag, "/cluster/configs", POST, "{create:{name : newconf, baseConfigSet: sample }}", null, - "{operation:create, name :newconf, baseConfigSet: sample, immutable: false }"); } } }
solr/solrj/src/java/org/apache/solr/common/cloud/ZkConfigManager.java+17 −6 modified@@ -138,11 +138,7 @@ private void copyConfigDirFromZk(String fromZkPath, String toZkPath, Set<String> for (String file : files) { List<String> children = zkClient.getChildren(fromZkPath + "/" + file, null, true); if (children.size() == 0) { - final String toZkFilePath = toZkPath + "/" + file; - log.info("Copying zk node {}/{} to {}", fromZkPath, file, toZkFilePath); - byte[] data = zkClient.getData(fromZkPath + "/" + file, null, null, true); - zkClient.makePath(toZkFilePath, data, true); - if (copiedToZkPaths != null) copiedToZkPaths.add(toZkFilePath); + copyData(copiedToZkPaths, fromZkPath + "/" + file, toZkPath + "/" + file); } else { copyConfigDirFromZk(fromZkPath + "/" + file, toZkPath + "/" + file, copiedToZkPaths); } @@ -153,6 +149,13 @@ private void copyConfigDirFromZk(String fromZkPath, String toZkPath, Set<String> } } + private void copyData(Set<String> copiedToZkPaths, String fromZkFilePath, String toZkFilePath) throws KeeperException, InterruptedException { + log.info("Copying zk node {} to {}", fromZkFilePath, toZkFilePath); + byte[] data = zkClient.getData(fromZkFilePath, null, null, true); + zkClient.makePath(toZkFilePath, data, true); + if (copiedToZkPaths != null) copiedToZkPaths.add(toZkFilePath); + } + /** * Copy a config in ZooKeeper * @@ -174,7 +177,15 @@ public void copyConfigDir(String fromConfig, String toConfig) throws IOException * @throws IOException if an I/O error occurs */ public void copyConfigDir(String fromConfig, String toConfig, Set<String> copiedToZkPaths) throws IOException { - copyConfigDirFromZk(CONFIGS_ZKNODE + "/" + fromConfig, CONFIGS_ZKNODE + "/" + toConfig, copiedToZkPaths); + String fromConfigPath = CONFIGS_ZKNODE + "/" + fromConfig; + String toConfigPath = CONFIGS_ZKNODE + "/" + toConfig; + try { + copyData(copiedToZkPaths, fromConfigPath, toConfigPath); + } catch (KeeperException | InterruptedException e) { + throw new IOException("Error config " + fromConfig + " to " + toConfig, + SolrZkClient.checkInterrupted(e)); + } + copyConfigDirFromZk(fromConfigPath, toConfigPath, copiedToZkPaths); } // This method is used by configSetUploadTool and CreateTool to resolve the configset directory.
solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java+7 −2 modified@@ -17,6 +17,7 @@ package org.apache.solr.cloud; import java.io.IOException; + import java.lang.invoke.MethodHandles; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -46,7 +47,6 @@ import javax.servlet.Filter; import org.apache.lucene.util.LuceneTestCase; -import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.embedded.JettyConfig; import org.apache.solr.client.solrj.embedded.JettySolrRunner; import org.apache.solr.client.solrj.embedded.SSLConfig; @@ -78,6 +78,7 @@ import org.slf4j.LoggerFactory; import com.codahale.metrics.MetricRegistry; +import static org.apache.solr.core.ConfigSetProperties.DEFAULT_FILENAME; /** * "Mini" SolrCloud cluster to be used for testing @@ -585,14 +586,18 @@ public void onChange(Set<String> oldCollections, Set<String> newCollections) { } - public void deleteAllConfigSets() throws SolrServerException, IOException { + public void deleteAllConfigSets() throws Exception { List<String> configSetNames = new ConfigSetAdminRequest.List().process(solrClient).getConfigSets(); for (String configSet : configSetNames) { if (configSet.equals("_default")) { continue; } + try { + // cleanup any property before removing the configset + getZkClient().delete(ZkConfigManager.CONFIGS_ZKNODE + "/" + configSet + "/" + DEFAULT_FILENAME, -1, true); + } catch (KeeperException.NoNodeException nne) { } new ConfigSetAdminRequest.Delete() .setConfigSetName(configSet) .process(solrClient);
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
46- github.com/advisories/GHSA-3c7p-vv5r-cmr5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-13957ghsaADVISORY
- github.com/apache/solr/commit/e001c2221812a0ba9e9378855040ce72f93eced4ghsaWEB
- lists.apache.org/thread.html/r13a728994c60be5b5a7049282b5c926dac1fc6a9a0b2362f6adfa573%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r13a728994c60be5b5a7049282b5c926dac1fc6a9a0b2362f6adfa573@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r1c783d3d81ba62f3381a17a4d6c826f7dead3a132ba42349c90df075%40%3Ccommits.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r1c783d3d81ba62f3381a17a4d6c826f7dead3a132ba42349c90df075@%3Ccommits.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r1d4a247329a8478073163567bbc8c8cb6b49c6bfc2bf58153a857af1%40%3Ccommits.druid.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r1d4a247329a8478073163567bbc8c8cb6b49c6bfc2bf58153a857af1@%3Ccommits.druid.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r2236fdf99ac3efbfc36c2df96d3a88f822baa6f45e13fec7ff558e34%40%3Cdev.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r2236fdf99ac3efbfc36c2df96d3a88f822baa6f45e13fec7ff558e34@%3Cdev.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r226c1112bb41e7cd427862d875eff9877a20a40242c2542f4dd39e4a%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r226c1112bb41e7cd427862d875eff9877a20a40242c2542f4dd39e4a@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r2a6600fe9afd502c04d26fd112823ec3f3c3ad1b4a289d10567a78a0%40%3Cdev.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r2a6600fe9afd502c04d26fd112823ec3f3c3ad1b4a289d10567a78a0@%3Cdev.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r2f8d33a4de07db9459fb2a98a1cd39747066137636b53f84a13e5628%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r2f8d33a4de07db9459fb2a98a1cd39747066137636b53f84a13e5628@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r3d1e24a73e6bffa1d6534e1f34c8f5cbd9999495e7d933640f4fa0ed%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r3d1e24a73e6bffa1d6534e1f34c8f5cbd9999495e7d933640f4fa0ed@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r3da9895cea476bcee2557531bebd4e8f6f367dc3ea900a65e2f51cd8%40%3Cissues.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r3da9895cea476bcee2557531bebd4e8f6f367dc3ea900a65e2f51cd8@%3Cissues.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r4ca8ba5980d9049cf3707798aa3116ee76c1582f171ff452ad2ca75e%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r4ca8ba5980d9049cf3707798aa3116ee76c1582f171ff452ad2ca75e@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r5557641fcf5cfd99260a7037cfbc8788fb546b72c98a900570edaa2e%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r5557641fcf5cfd99260a7037cfbc8788fb546b72c98a900570edaa2e@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r622a043c2890327f8a4aea16b131e8a7137a282a004614369fceb224%40%3Cdev.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r622a043c2890327f8a4aea16b131e8a7137a282a004614369fceb224@%3Cdev.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r7512ae552cd9d14ab8b1bc0a7e95f2ec52ae85364f068d4034398ede%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r7512ae552cd9d14ab8b1bc0a7e95f2ec52ae85364f068d4034398ede@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r853fdc6d0b91d5e01a26c7bd5becb044ad775a231703d634ca5d55c9%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r853fdc6d0b91d5e01a26c7bd5becb044ad775a231703d634ca5d55c9@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r8b1782d42d0a4ce573495d5d9345ad328d652c68c411ccdb245c57e3%40%3Cissues.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r8b1782d42d0a4ce573495d5d9345ad328d652c68c411ccdb245c57e3@%3Cissues.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r999f828e6e37d9e825e207471cbfd2681c3befcd7f3abd59ed87c0d5%40%3Cissues.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r999f828e6e37d9e825e207471cbfd2681c3befcd7f3abd59ed87c0d5@%3Cissues.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r9d7356f209ee30d702b6a921c866564eb2e291b126640c7ab70feea7%40%3Ccommits.lucene.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r9d7356f209ee30d702b6a921c866564eb2e291b126640c7ab70feea7@%3Ccommits.lucene.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rb1de6ba50a468e9baff32a249edaa08f6bcec7dd7cc208e25e6b48c8%40%3Cissues.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/rb1de6ba50a468e9baff32a249edaa08f6bcec7dd7cc208e25e6b48c8@%3Cissues.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rb2f1c7fd3d3ea719dfac4706a80e6affddecae8663dda04e1335347f%40%3Ccommits.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/rb2f1c7fd3d3ea719dfac4706a80e6affddecae8663dda04e1335347f@%3Ccommits.bigtop.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rf1a32f00017e83ff29a74be2de02e28e4302dddb5f14c624e297a8c0%40%3Cdev.bigtop.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/rf1a32f00017e83ff29a74be2de02e28e4302dddb5f14c624e297a8c0@%3Cdev.bigtop.apache.org%3EghsaWEB
- mail-archives.us.apache.org/mod_mbox/www-announce/202010.mbox/%3CCAECwjAWCVLoVaZy%3DTNRQ6Wk9KWVxdPRiGS8NT%2BPHMJCxbbsEVg%40mail.gmail.com%3Eghsax_refsource_MISCWEB
- security.netapp.com/advisory/ntap-20201023-0002ghsaWEB
- security.netapp.com/advisory/ntap-20201023-0002/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.