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}