You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bigtop.apache.org by iw...@apache.org on 2021/03/01 15:04:35 UTC
[bigtop] branch master updated: BIGTOP-3507: CVE-2020-13957
mitigation backport (#743)
This is an automated email from the ASF dual-hosted git repository.
iwasakims pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bigtop.git
The following commit(s) were added to refs/heads/master by this push:
new 0ab8b09 BIGTOP-3507: CVE-2020-13957 mitigation backport (#743)
0ab8b09 is described below
commit 0ab8b09f20cc74bf35b2341ee70720d2177bfaf0
Author: Jun He <ju...@linaro.org>
AuthorDate: Mon Mar 1 23:04:30 2021 +0800
BIGTOP-3507: CVE-2020-13957 mitigation backport (#743)
Fix unauthenticated configset uploads security risk. Patch in SOLR-14663
is backported to v6.6.6
Change-Id: I40c6f7d0edae057acd2182174dbf5fea84c0b825
Signed-off-by: Jun He <ju...@arm.com>
---
.../common/solr/patch0-SOLR-14663-backport.diff | 604 +++++++++++++++++++++
bigtop-packages/src/rpm/solr/SPECS/solr.spec | 3 +
2 files changed, 607 insertions(+)
diff --git a/bigtop-packages/src/common/solr/patch0-SOLR-14663-backport.diff b/bigtop-packages/src/common/solr/patch0-SOLR-14663-backport.diff
new file mode 100644
index 0000000..3733636
--- /dev/null
+++ b/bigtop-packages/src/common/solr/patch0-SOLR-14663-backport.diff
@@ -0,0 +1,604 @@
+diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
+index 387f260dae5..f16fae10efc 100644
+--- a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
++++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
+@@ -20,9 +20,11 @@ import java.io.InputStream;
+ import java.lang.invoke.MethodHandles;
+ import java.nio.charset.StandardCharsets;
+ import java.util.Collection;
++import java.util.Collections;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
++import java.util.Set;
+ import java.util.concurrent.TimeUnit;
+ import java.util.zip.ZipEntry;
+ import java.util.zip.ZipInputStream;
+@@ -71,6 +73,8 @@ import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+ * 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
++ final public static String DEFAULT_CONFIGSET_NAME = "_default";
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ protected final CoreContainer coreContainer;
+ public static long DEFAULT_ZK_TIMEOUT = 300*1000;
+@@ -151,7 +155,8 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
+ SolrZkClient zkClient = coreContainer.getZkController().getZkClient();
+ String configPathInZk = ZkConfigManager.CONFIGS_ZKNODE + Path.SEPARATOR + configSetName;
+
+- if (zkClient.exists(configPathInZk, true)) {
++ boolean overwritesExisting = zkClient.exists(configPathInZk, true);
++ if (overwritesExisting) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,
+ "The configuration " + configSetName + " already exists in zookeeper");
+ }
+@@ -166,31 +171,96 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
+ InputStream inputStream = contentStreamsIterator.next().getStream();
+
+ // Create a node for the configuration in zookeeper
+- boolean trusted = getTrusted(req);
+- zkClient.makePath(configPathInZk, ("{\"trusted\": " + Boolean.toString(trusted) + "}").
+- getBytes(StandardCharsets.UTF_8), true);
++ Set<String> filesToDelete = Collections.emptySet();
++ createBaseZnode(zkClient, overwritesExisting, isTrusted(req, coreContainer.getAuthenticationPlugin()), false, configPathInZk);
+
+ ZipInputStream zis = new ZipInputStream(inputStream, StandardCharsets.UTF_8);
+ ZipEntry zipEntry = null;
+ while ((zipEntry = zis.getNextEntry()) != null) {
+ String filePathInZk = configPathInZk + "/" + zipEntry.getName();
++ if (filePathInZk.endsWith("/")) {
++ filesToDelete.remove(filePathInZk.substring(0, filePathInZk.length() -1));
++ } else {
++ filesToDelete.remove(filePathInZk);
++ }
+ if (zipEntry.isDirectory()) {
+- zkClient.makePath(filePathInZk, true);
++ zkClient.makePath(filePathInZk, false, true);
+ } else {
+ createZkNodeIfNotExistsAndSetData(zkClient, filePathInZk,
+ IOUtils.toByteArray(zis));
+ }
+ }
+ zis.close();
++ deleteUnusedFiles(zkClient, filesToDelete);
++ }
++
++ private void createBaseZnode(SolrZkClient zkClient, boolean overwritesExisting, boolean requestIsTrusted, boolean cleanup, String configPathInZk) throws KeeperException, InterruptedException {
++ byte[] baseZnodeData = ("{\"trusted\": " + Boolean.toString(requestIsTrusted) + "}").getBytes(StandardCharsets.UTF_8);
++
++ if (overwritesExisting) {
++ if (cleanup && requestIsTrusted) {
++ zkClient.setData(configPathInZk, baseZnodeData, true);
++ } else if (!requestIsTrusted) {
++ ensureOverwritingUntrustedConfigSet(zkClient, configPathInZk);
++ }
++ } else {
++ zkClient.makePath(configPathInZk, baseZnodeData, true);
++ }
+ }
+
+- boolean getTrusted(SolrQueryRequest req) {
+- AuthenticationPlugin authcPlugin = coreContainer.getAuthenticationPlugin();
++ private void deleteUnusedFiles(SolrZkClient zkClient, Set<String> filesToDelete) throws InterruptedException, KeeperException {
++ if (!filesToDelete.isEmpty()) {
++ if (log.isInfoEnabled()) {
++ log.info("Cleaning up {} unused files", filesToDelete.size());
++ }
++ if (log.isDebugEnabled()) {
++ log.debug("Cleaning up unused files: {}", filesToDelete);
++ }
++ for (String f:filesToDelete) {
++ try {
++ zkClient.delete(f, -1, true);
++ } catch (KeeperException.NoNodeException nne) {
++ }
++ }
++ }
++ }
++
++ /*
++ * Fail if an untrusted request tries to update a trusted ConfigSet
++ */
++ private void ensureOverwritingUntrustedConfigSet(SolrZkClient zkClient, String configSetZkPath) {
++ boolean isCurrentlyTrusted = isCurrentlyTrusted(zkClient, configSetZkPath);
++ if (isCurrentlyTrusted) {
++ throw new SolrException(ErrorCode.BAD_REQUEST, "Trying to make an unstrusted ConfigSet update on a trusted configSet");
++ }
++ }
++
++ 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);
++ }
++ @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) {
+ log.info("Trying to upload a configset. authcPlugin: {}, user principal: {}",
+- authcPlugin, req.getUserPrincipal());
+- if (authcPlugin != null && req.getUserPrincipal() != null) {
++ authPlugin, req.getUserPrincipal());
++ if (authPlugin != null && req.getUserPrincipal() != null) {
++ log.debug("Trusted configset request");
+ return true;
+ }
++ log.debug("Untrusted configset request");
+ return false;
+ }
+
+@@ -259,7 +329,31 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
+ CREATE_OP(CREATE) {
+ @Override
+ Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception {
+- Map<String, Object> props = req.getParams().required().getAll(null, NAME, BASE_CONFIGSET);
++ 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 = req.getParams().required().getAll(null, NAME);//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 + ".");
+ }
+ },
+diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+index 5a01c6cc054..f75b5ac508c 100644
+--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
++++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+@@ -36,6 +36,7 @@ import java.nio.ByteBuffer;
+ import java.nio.charset.StandardCharsets;
+ import java.util.Arrays;
+ import java.util.Collection;
++import java.util.Collections;
+ import java.util.Deque;
+ import java.util.HashSet;
+ import java.util.Iterator;
+@@ -45,6 +46,7 @@ import java.util.Properties;
+ import java.util.Set;
+ import java.util.zip.ZipEntry;
+ import java.util.zip.ZipOutputStream;
++import java.util.Locale;
+
+ import org.apache.commons.io.FileUtils;
+ import org.apache.http.HttpEntity;
+@@ -85,6 +87,7 @@ 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.TestSolrConfigHandler;
+ import org.apache.solr.core.TestDynamicLoading;
+ import org.apache.solr.security.BasicAuthIntegrationTest;
+ import org.apache.zookeeper.CreateMode;
+@@ -125,7 +128,7 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ @Test
+ public void testCreateErrors() throws Exception {
+ final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+- final SolrClient solrClient = getHttpSolrClient(baseUrl);
++ try (final SolrClient solrClient = getHttpSolrClient(baseUrl)) {
+ solrCluster.uploadConfigSet(configset("configset-2"), "configSet");
+
+ // no action
+@@ -137,9 +140,8 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ CreateNoErrorChecking create = new CreateNoErrorChecking();
+ verifyException(solrClient, create, NAME);
+
+- // no base ConfigSet name
++ // set ConfigSet
+ create.setConfigSetName("configSetName");
+- verifyException(solrClient, create, BASE_CONFIGSET);
+
+ // ConfigSet already exists
+ Create alreadyExists = new Create();
+@@ -151,29 +153,65 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ baseConfigNoExists.setConfigSetName("newConfigSet").setBaseConfigSetName("baseConfigSet");
+ verifyException(solrClient, baseConfigNoExists, "Base ConfigSet does not exist");
+
+- solrClient.close();
++ //solrClient.close();
++ }
+ }
+
+ @Test
+ public void testCreate() throws Exception {
+ // no old, no new
+- verifyCreate("baseConfigSet1", "configSet1", null, null);
++ verifyCreate("baseConfigSet1", "configSet1", null, null, null, null);
+
+ // no old, new
+ verifyCreate("baseConfigSet2", "configSet2",
+- null, ImmutableMap.<String, String>of("immutable", "true", "key1", "value1"));
++ null, ImmutableMap.<String, String>of("immutable", "true", "key1", "value1"), null, null);
+
+ // old, no new
+ verifyCreate("baseConfigSet3", "configSet3",
+- ImmutableMap.<String, String>of("immutable", "false", "key2", "value2"), null);
++ ImmutableMap.<String, String>of("immutable", "false", "key2", "value2"), null, null, null);
+
+ // 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"), null, null);
+ }
+
+- private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> oldProps) throws Exception {
++ @Test
++ public void testCreateWithTrust() throws Exception {
++ String configsetName = "regular";
++ String configsetSuffix = "testCreateWithTrust";
++ String configsetSuffix2 = "testCreateWithTrust2";
++ protectConfigsHandler();
++ uploadConfigSetWithAssertions(configsetName, configsetSuffix, "solr", "SolrRocks");
++ uploadConfigSetWithAssertions(configsetName, configsetSuffix2, null, null);
++ try (SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(),
++ AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null)) {
++ assertTrue(isTrusted(zkClient, configsetName, configsetSuffix));
++ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix2));
++ try {
++ ignoreException("unauthenticated request");
++ // trusted -> unstrusted
++ createConfigSet(configsetName + configsetSuffix, "foo", Collections.emptyMap(), solrCluster.getSolrClient(), null, 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", "SolrRocks");
++ assertTrue(isTrusted(zkClient, "foo2", ""));
++
++ // unstrusted -> unstrusted
++ verifyCreate(configsetName + configsetSuffix2, "bar", Collections.emptyMap(), Collections.emptyMap(), null, null);
++ assertFalse(isTrusted(zkClient, "bar", ""));
++
++ // unstrusted -> trusted
++ verifyCreate(configsetName + configsetSuffix2, "bar2", Collections.emptyMap(), Collections.emptyMap(), "solr", "SolrRocks");
++ assertTrue(isTrusted(zkClient, "bar2", ""));
++ }
++ }
++
++ private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> oldProps, boolean trusted) throws Exception {
+ final File configDir = getFile("solr").toPath().resolve("configsets/configset-2/conf").toFile();
+ final File tmpConfigDir = createTempDir().toFile();
+ tmpConfigDir.deleteOnExit();
+@@ -183,36 +221,53 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ getConfigSetProps(oldProps), StandardCharsets.UTF_8);
+ }
+ solrCluster.uploadConfigSet(tmpConfigDir.toPath(), baseConfigSetName);
++ // Mark configset untrusted
++ String trustedFlag = "{\"trusted\": false}"; // default is untrusted
++ if (trusted) trustedFlag = "{\"trusted\": true}";
++ SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(),
++ AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null);
++ zkClient().setData("/configs/" + baseConfigSetName, trustedFlag.getBytes(StandardCharsets.UTF_8), true);
++ zkClient.close();
+ }
+
+ private void verifyCreate(String baseConfigSetName, String configSetName,
+- Map<String, String> oldProps, Map<String, String> newProps) throws Exception {
++ Map<String, String> oldProps, Map<String, String> newProps, String username, String password) throws Exception {
+ final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+- final SolrClient solrClient = getHttpSolrClient(baseUrl);
+- setupBaseConfigSet(baseConfigSetName, oldProps);
++ try (final SolrClient solrClient = getHttpSolrClient(baseUrl)) {
++ if (username != null && password != null)
++ setupBaseConfigSet(baseConfigSetName, oldProps, true);
++ else
++ setupBaseConfigSet(baseConfigSetName, oldProps, false);
+
+- SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(),
++ 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);
++ try {
++ ZkConfigManager configManager = new ZkConfigManager(zkClient);
++ assertFalse(configManager.configExists(configSetName));
++
++ ConfigSetAdminResponse response = createConfigSet(baseConfigSetName, configSetName, newProps, solrClient, username, password);
++ 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, String password) 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 && password != null) {
++ create.setBasicAuthCredentials(username, password);
++ }
++ return create.process(solrClient);
+ }
+
+ private NamedList getConfigSetPropertiesFromZk(
+@@ -277,6 +332,15 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ }
+ }
+
++ 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);;
++
++ @SuppressWarnings("unchecked")
++ Map<Object, Object> contentMap = (Map<Object, Object>) Utils.fromJSON(configSetNodeContent);
++ return (boolean) contentMap.getOrDefault("trusted", true);
++ }
++
+ @Test
+ public void testUploadErrors() throws Exception {
+ final SolrClient solrClient = new HttpSolrClient(
+@@ -404,7 +468,7 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
+ " 'authorization':{\n" +
+ " 'class':'solr.RuleBasedAuthorizationPlugin',\n" +
+ " 'user-role':{'solr':'admin'},\n" +
+- " 'permissions':[{'name':'security-edit','role':'admin'}, {'name':'config-edit','role':'admin'}]}}";
++ " 'permissions':[{'name':'security-edit','role':'admin'}]}}";
+
+ HttpClient cl = null;
+ try {
+diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIExclusivity.java b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIExclusivity.java
+index a11072754f1..efdc0fd97ae 100644
+--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIExclusivity.java
++++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIExclusivity.java
+@@ -17,11 +17,14 @@
+ 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;
+
+ import org.apache.solr.SolrTestCaseJ4;
++import org.apache.solr.common.cloud.SolrZkClient;
++import org.apache.solr.common.cloud.ZkStateReader;
+ import org.apache.solr.client.solrj.SolrClient;
+ import org.apache.solr.client.solrj.request.ConfigSetAdminRequest;
+ import org.apache.solr.client.solrj.request.ConfigSetAdminRequest.Create;
+@@ -56,13 +59,19 @@ public class TestConfigSetsAPIExclusivity extends SolrTestCaseJ4 {
+ @Override
+ @After
+ public void tearDown() throws Exception {
+- solrCluster.shutdown();
++ if (null != solrCluster) {
++ solrCluster.shutdown();
++ solrCluster = null;
++ }
+ super.tearDown();
+ }
+
+ @Test
+ public void testAPIExclusivity() throws Exception {
+ int trials = 20;
++ ZkStateReader reader = solrCluster.getSolrClient().getZkStateReader();
++ if (reader == null)
++ solrCluster.getSolrClient().connect();
+ setupBaseConfigSet(GRANDBASE_CONFIGSET_NAME);
+ CreateThread createBaseThread =
+ new CreateThread(solrCluster, BASE_CONFIGSET_NAME, GRANDBASE_CONFIGSET_NAME, trials);
+@@ -89,6 +98,8 @@ public class TestConfigSetsAPIExclusivity extends SolrTestCaseJ4 {
+
+ 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) {
+diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIZkFailure.java b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIZkFailure.java
+index 34acb2509e0..5d510403bd4 100644
+--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIZkFailure.java
++++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPIZkFailure.java
+@@ -91,8 +91,14 @@ public class TestConfigSetsAPIZkFailure extends SolrTestCaseJ4 {
+ @Override
+ @After
+ public void tearDown() throws Exception {
+- solrCluster.shutdown();
+- zkTestServer.shutdown();
++ if (null != solrCluster) {
++ solrCluster.shutdown();
++ solrCluster = null;
++ }
++ if (null != zkTestServer) {
++ zkTestServer.shutdown();
++ zkTestServer = null;
++ }
+ super.tearDown();
+ }
+
+@@ -102,10 +108,10 @@ public class TestConfigSetsAPIZkFailure extends SolrTestCaseJ4 {
+ final SolrClient solrClient = getHttpSolrClient(baseUrl);
+
+ final Map<String, String> oldProps = ImmutableMap.of("immutable", "true");
+- setupBaseConfigSet(BASE_CONFIGSET_NAME, oldProps);
+
+ SolrZkClient zkClient = new SolrZkClient(solrCluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null);
++ setupBaseConfigSet(BASE_CONFIGSET_NAME, oldProps, zkClient);
+ try {
+ ZkConfigManager configManager = new ZkConfigManager(zkClient);
+ assertFalse(configManager.configExists(CONFIGSET_NAME));
+@@ -127,7 +133,7 @@ public class TestConfigSetsAPIZkFailure extends SolrTestCaseJ4 {
+ solrClient.close();
+ }
+
+- private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> oldProps) throws Exception {
++ private void setupBaseConfigSet(String baseConfigSetName, Map<String, String> oldProps, SolrZkClient zkClient) throws Exception {
+ final File configDir = getFile("solr").toPath().resolve("configsets/configset-2/conf").toFile();
+ final File tmpConfigDir = createTempDir().toFile();
+ tmpConfigDir.deleteOnExit();
+@@ -137,6 +143,7 @@ public class TestConfigSetsAPIZkFailure extends SolrTestCaseJ4 {
+ getConfigSetProps(oldProps), StandardCharsets.UTF_8);
+ }
+ solrCluster.uploadConfigSet(tmpConfigDir.toPath(), baseConfigSetName);
++ zkClient.setData("/configs/" + baseConfigSetName, "{\"trusted\": false}".getBytes(StandardCharsets.UTF_8), true);
+ }
+
+ private StringBuilder getConfigSetProps(Map<String, String> map) {
+diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+index 13d4d0161f7..eae84548965 100644
+--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
++++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+@@ -16,9 +16,8 @@
+ */
+ package org.apache.solr.core;
+
+-import java.io.File;
+-import java.io.IOException;
+-import java.io.StringReader;
++import java.io.*;
++import java.nio.ByteBuffer;
+ import java.lang.invoke.MethodHandles;
+ import java.util.Arrays;
+ import java.util.Collections;
+@@ -66,7 +65,24 @@ public class TestSolrConfigHandler extends RestTestBase {
+
+ private static final String collection = "collection1";
+ private static final String confDir = collection + "/conf";
++ public static ByteBuffer getFileContent(String f) throws IOException {
++ return getFileContent(f, true);
++ }
+
++ /**
++ * @param loadFromClassPath if true, it will look in the classpath to find the file,
++ * otherwise load from absolute filesystem path.
++ */
++ public static ByteBuffer getFileContent(String f, boolean loadFromClassPath) throws IOException {
++ ByteBuffer jar;
++ File file = loadFromClassPath ? getFile(f): new File(f);
++ try (FileInputStream fis = new FileInputStream(file)) {
++ byte[] buf = new byte[fis.available()];
++ fis.read(buf);
++ jar = ByteBuffer.wrap(buf);
++ }
++ return jar;
++ }
+
+ @Before
+ public void before() throws Exception {
+diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
+index d2c96a66be2..5721db48799 100644
+--- a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
++++ b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
+@@ -29,7 +29,6 @@ import org.apache.zookeeper.KeeperException;
+
+ 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;
+
+@@ -52,8 +51,5 @@ public class TestConfigsApi extends SolrTestCaseJ4 {
+ for (Api api : handler.getApis()) apiBag.register(api, EMPTY_MAP);
+ 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 }");
+ }
+ }
+diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkConfigManager.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkConfigManager.java
+index 0b3608ba2a7..f53d082ae9e 100644
+--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkConfigManager.java
++++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkConfigManager.java
+@@ -138,12 +138,7 @@ public class ZkConfigManager {
+ for (String file : files) {
+ List<String> children = zkClient.getChildren(fromZkPath + "/" + file, null, true);
+ if (children.size() == 0) {
+- final String toZkFilePath = toZkPath + "/" + file;
+- logger.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);
+ }
+@@ -154,6 +149,13 @@ public class ZkConfigManager {
+ }
+ }
+
++ private void copyData(Set<String> copiedToZkPaths, String fromZkFilePath, String toZkFilePath) throws KeeperException, InterruptedException {
++ logger.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
+ *
+@@ -175,6 +177,14 @@ public class ZkConfigManager {
+ * @throws IOException if an I/O error occurs
+ */
+ public void copyConfigDir(String fromConfig, String toConfig, Set<String> copiedToZkPaths) throws IOException {
++ 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(CONFIGS_ZKNODE + "/" + fromConfig, CONFIGS_ZKNODE + "/" + toConfig, copiedToZkPaths);
+ }
+
+--
+2.17.1
+
diff --git a/bigtop-packages/src/rpm/solr/SPECS/solr.spec b/bigtop-packages/src/rpm/solr/SPECS/solr.spec
index d173d99..9ba246a 100644
--- a/bigtop-packages/src/rpm/solr/SPECS/solr.spec
+++ b/bigtop-packages/src/rpm/solr/SPECS/solr.spec
@@ -56,6 +56,7 @@ Source2: install_%{name}.sh
Source3: solr.default
Source4: solr-server.init
Source5: solrctl.sh
+#BIGTOP_PATCH_FILES
Requires: bigtop-utils >= 0.7
# CentOS 5 does not have any dist macro
@@ -105,6 +106,8 @@ Documentation for Apache Solr
%prep
%setup -n solr-%{solr_base_version}
+#BIGTOP_PATCH_COMMANDS
+
%build
env FULL_VERSION=%{solr_base_version} bash %{SOURCE1}