You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2016/12/20 10:48:34 UTC

[29/44] lucene-solr:jira/solr-9854: SOLR-9513: Generic Hadoop authentication plugins, GenericHadoopAuthPlugin and ConfigurableInternodeAuthHadoopPlugin

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/core/src/test/org/apache/solr/security/hadoop/TestImpersonationWithHadoopAuth.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestImpersonationWithHadoopAuth.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestImpersonationWithHadoopAuth.java
new file mode 100644
index 0000000..ed8397b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestImpersonationWithHadoopAuth.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.security.hadoop;
+
+import static org.apache.solr.security.HttpParamDelegationTokenPlugin.USER_PARAM;
+import static org.apache.solr.security.hadoop.ImpersonationUtil.*;
+
+import java.net.InetAddress;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.util.Constants;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.HadoopAuthPlugin;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestImpersonationWithHadoopAuth  extends SolrCloudTestCase {
+  protected static final int NUM_SERVERS = 2;
+  private static final boolean defaultAddRequestHeadersToContext =
+      SolrRequestParsers.DEFAULT.isAddRequestHeadersToContext();
+
+  @SuppressWarnings("unchecked")
+  @BeforeClass
+  public static void setupClass() throws Exception {
+    assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS);
+
+    InetAddress loopback = InetAddress.getLoopbackAddress();
+    Path securityJsonPath = TEST_PATH().resolve("security").resolve("hadoop_simple_auth_with_delegation.json");
+    String securityJson = new String(Files.readAllBytes(securityJsonPath), Charset.defaultCharset());
+
+    Map<String, Object> securityConfig = (Map<String, Object>)Utils.fromJSONString(securityJson);
+    Map<String, Object> authConfig = (Map<String, Object>)securityConfig.get("authentication");
+    Map<String,String> proxyUserConfigs = (Map<String,String>) authConfig
+        .getOrDefault(HadoopAuthPlugin.PROXY_USER_CONFIGS, new HashMap<>());
+    proxyUserConfigs.put("proxyuser.noGroups.hosts", "*");
+    proxyUserConfigs.put("proxyuser.anyHostAnyUser.hosts", "*");
+    proxyUserConfigs.put("proxyuser.anyHostAnyUser.groups", "*");
+    proxyUserConfigs.put("proxyuser.wrongHost.hosts", "1.1.1.1.1.1");
+    proxyUserConfigs.put("proxyuser.wrongHost.groups", "*");
+    proxyUserConfigs.put("proxyuser.noHosts.groups", "*");
+    proxyUserConfigs.put("proxyuser.localHostAnyGroup.hosts",
+        loopback.getCanonicalHostName() + "," + loopback.getHostName() + "," + loopback.getHostAddress());
+    proxyUserConfigs.put("proxyuser.localHostAnyGroup.groups", "*");
+    proxyUserConfigs.put("proxyuser.bogusGroup.hosts", "*");
+    proxyUserConfigs.put("proxyuser.bogusGroup.groups", "__some_bogus_group");
+    proxyUserConfigs.put("proxyuser.anyHostUsersGroup.groups", ImpersonationUtil.getUsersFirstGroup());
+    proxyUserConfigs.put("proxyuser.anyHostUsersGroup.hosts", "*");
+
+    authConfig.put(HadoopAuthPlugin.PROXY_USER_CONFIGS, proxyUserConfigs);
+
+    SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(true);
+    System.setProperty("collectionsHandler", ImpersonatorCollectionsHandler.class.getName());
+
+    configureCluster(NUM_SERVERS)// nodes
+        .withSecurityJson(Utils.toJSONString(securityConfig))
+        .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .configure();
+  }
+
+  @AfterClass
+  public static void tearDownClass() throws Exception {
+    SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(defaultAddRequestHeadersToContext);
+    System.clearProperty("collectionsHandler");
+  }
+
+  private SolrClient newSolrClient() {
+    return new HttpSolrClient.Builder(
+        cluster.getJettySolrRunner(0).getBaseUrl().toString()).build();
+  }
+
+  @Test
+  public void testProxyNoConfigGroups() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("noGroups","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getLocalizedMessage(), ex.getMessage().contains(getExpectedGroupExMsg("noGroups", "bar")));
+    }
+  }
+
+  @Test
+  public void testProxyWrongHost() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("wrongHost","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedHostExMsg("wrongHost")));
+    }
+  }
+
+  @Test
+  public void testProxyNoConfigHosts() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("noHosts","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedHostExMsg("noHosts")));
+    }
+  }
+
+  @Test
+  public void testProxyValidateAnyHostAnyUser() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("anyHostAnyUser", "bar"));
+      assertTrue(ImpersonatorCollectionsHandler.called.get());
+    }
+  }
+
+  @Test
+  public void testProxyInvalidProxyUser() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      // wrong direction, should fail
+      solrClient.request(getProxyRequest("bar","anyHostAnyUser"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("bar", "anyHostAnyUser")));
+    }
+  }
+
+  @Test
+  public void testProxyValidateHost() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("localHostAnyGroup", "bar"));
+      assertTrue(ImpersonatorCollectionsHandler.called.get());
+    }
+  }
+
+  @Test
+  public void testProxyValidateGroup() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("anyHostUsersGroup", System.getProperty("user.name")));
+      assertTrue(ImpersonatorCollectionsHandler.called.get());
+    }
+  }
+
+  @Test
+  public void testProxyInvalidGroup() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("bogusGroup","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("bogusGroup", "bar")));
+    }
+  }
+
+  @Test
+  public void testProxyNullProxyUser() throws Exception {
+    try (SolrClient solrClient = newSolrClient()) {
+      solrClient.request(getProxyRequest("","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      // this exception is specific to our implementation, don't check a specific message.
+    }
+  }
+
+  @Test
+  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/HADOOP-9893")
+  public void testForwarding() throws Exception {
+    String collectionName = "forwardingCollection";
+
+    // create collection
+    CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1",
+        1, 1);
+    try (SolrClient solrClient = newSolrClient()) {
+      create.process(solrClient);
+    }
+
+    // try a command to each node, one of them must be forwarded
+    for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
+      HttpSolrClient client =
+          new HttpSolrClient.Builder(jetty.getBaseUrl().toString() + "/" + collectionName).build();
+      try {
+        ModifiableSolrParams params = new ModifiableSolrParams();
+        params.set("q", "*:*");
+        params.set(USER_PARAM, "user");
+        client.query(params);
+      } finally {
+        client.close();
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
new file mode 100644
index 0000000..960fd9a
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.security.hadoop;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.lucene.util.Constants;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.cloud.AbstractDistribZkTestBase;
+import org.apache.solr.cloud.KerberosTestServices;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.SolrInputDocument;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
+  protected static final int NUM_SERVERS = 1;
+  protected static final int NUM_SHARDS = 1;
+  protected static final int REPLICATION_FACTOR = 1;
+  private static KerberosTestServices kerberosTestServices;
+
+  @BeforeClass
+  public static void setupClass() throws Exception {
+    assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS);
+    assumeFalse("FIXME: SOLR-8182: This test fails under Java 9", Constants.JRE_IS_MINIMUM_JAVA9);
+
+    setupMiniKdc();
+
+    configureCluster(NUM_SERVERS)// nodes
+        .withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_config.json"))
+        .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .configure();
+  }
+
+  @AfterClass
+  public static void tearDownClass() throws Exception {
+    System.clearProperty("java.security.auth.login.config");
+    System.clearProperty("solr.kerberos.principal");
+    System.clearProperty("solr.kerberos.keytab");
+    System.clearProperty("solr.kerberos.name.rules");
+    System.clearProperty("solr.jaas.debug");
+    kerberosTestServices.stop();
+    kerberosTestServices = null;
+  }
+
+  private static void setupMiniKdc() throws Exception {
+    System.setProperty("solr.jaas.debug", "true");
+    String kdcDir = createTempDir()+File.separator+"minikdc";
+    String solrClientPrincipal = "solr";
+    File keytabFile = new File(kdcDir, "keytabs");
+    kerberosTestServices = KerberosTestServices.builder()
+        .withKdc(new File(kdcDir))
+        .withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient")
+        .build();
+    String solrServerPrincipal = "HTTP/127.0.0.1";
+    kerberosTestServices.start();
+    kerberosTestServices.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal);
+
+    String jaas = "SolrClient {\n"
+        + " com.sun.security.auth.module.Krb5LoginModule required\n"
+        + " useKeyTab=true\n"
+        + " keyTab=\"" + keytabFile.getAbsolutePath() + "\"\n"
+        + " storeKey=true\n"
+        + " useTicketCache=false\n"
+        + " doNotPrompt=true\n"
+        + " debug=true\n"
+        + " principal=\"" + solrClientPrincipal + "\";\n"
+        + "};";
+
+    String jaasFilePath = kdcDir+File.separator+"jaas-client.conf";
+    FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8);
+    System.setProperty("java.security.auth.login.config", jaasFilePath);
+    System.setProperty("solr.kerberos.jaas.appname", "SolrClient"); // Get this app name from the jaas file
+
+    System.setProperty("solr.kerberos.principal", solrServerPrincipal);
+    System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath());
+    // Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM
+    System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//"
+        + "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//"
+        + "\nDEFAULT"
+        );
+  }
+
+  @Test
+  public void testBasics() throws Exception {
+    testCollectionCreateSearchDelete();
+    // sometimes run a second test e.g. to test collection create-delete-create scenario
+    if (random().nextBoolean()) testCollectionCreateSearchDelete();
+  }
+
+  protected void testCollectionCreateSearchDelete() throws Exception {
+    CloudSolrClient solrClient = cluster.getSolrClient();
+    String collectionName = "testkerberoscollection";
+
+    // create collection
+    CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1",
+        NUM_SHARDS, REPLICATION_FACTOR);
+    create.process(solrClient);
+
+    SolrInputDocument doc = new SolrInputDocument();
+    doc.setField("id", "1");
+    solrClient.add(collectionName, doc);
+    solrClient.commit(collectionName);
+
+    SolrQuery query = new SolrQuery();
+    query.setQuery("*:*");
+    QueryResponse rsp = solrClient.query(collectionName, query);
+    assertEquals(1, rsp.getResults().getNumFound());
+
+    CollectionAdminRequest.Delete deleteReq = CollectionAdminRequest.deleteCollection(collectionName);
+    deleteReq.process(solrClient);
+    AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName,
+        solrClient.getZkStateReader(), true, true, 330);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientBuilderFactory.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientBuilderFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientBuilderFactory.java
new file mode 100644
index 0000000..77c4a94
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientBuilderFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.impl;
+
+import java.io.Closeable;
+import java.util.Optional;
+
+/**
+ * Factory interface for configuring {@linkplain SolrHttpClientBuilder}. This
+ * relies on the internal HttpClient implementation and is subject to
+ * change.
+ *
+ * @lucene.experimental
+ **/
+public interface HttpClientBuilderFactory extends Closeable {
+
+  /**
+   * This method configures the {@linkplain SolrHttpClientBuilder} by overriding the
+   * configuration of passed SolrHttpClientBuilder or as a new instance.
+   *
+   * @param builder The instance of the {@linkplain SolrHttpClientBuilder} which should
+   *                by configured (optional).
+   * @return the {@linkplain SolrHttpClientBuilder}
+   */
+  public SolrHttpClientBuilder getHttpClientBuilder(Optional<SolrHttpClientBuilder> builder);
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
index 39cc2dc..7f3cf29 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
@@ -21,6 +21,7 @@ import java.security.Principal;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.security.auth.login.AppConfigurationEntry;
@@ -46,7 +47,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Kerberos-enabled SolrHttpClientBuilder
  */
-public class Krb5HttpClientBuilder  {
+public class Krb5HttpClientBuilder implements HttpClientBuilderFactory {
   
   public static final String LOGIN_CONFIG_PROP = "java.security.auth.login.config";
   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -73,7 +74,12 @@ public class Krb5HttpClientBuilder  {
   public void close() {
     HttpClientUtil.removeRequestInterceptor(bufferedEntityInterceptor);
   }
-  
+
+  @Override
+  public SolrHttpClientBuilder getHttpClientBuilder(Optional<SolrHttpClientBuilder> builder) {
+    return builder.isPresent() ? getBuilder(builder.get()) : getBuilder();
+  }
+
   public SolrHttpClientBuilder getBuilder(SolrHttpClientBuilder builder) {
     if (System.getProperty(LOGIN_CONFIG_PROP) != null) {
       String configValue = System.getProperty(LOGIN_CONFIG_PROP);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index 5ebdfb7..81e1f22 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -28,6 +28,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Random;
 import java.util.SortedMap;
 import java.util.concurrent.Callable;
@@ -68,6 +69,7 @@ public class MiniSolrCloudCluster {
       "  <str name=\"shareSchema\">${shareSchema:false}</str>\n" +
       "  <str name=\"configSetBaseDir\">${configSetBaseDir:configsets}</str>\n" +
       "  <str name=\"coreRootDirectory\">${coreRootDirectory:.}</str>\n" +
+      "  <str name=\"collectionsHandler\">${collectionsHandler:solr.CollectionsHandler}</str>\n" +
       "\n" +
       "  <shardHandlerFactory name=\"shardHandlerFactory\" class=\"HttpShardHandlerFactory\">\n" +
       "    <str name=\"urlScheme\">${urlScheme:}</str>\n" +
@@ -180,8 +182,30 @@ public class MiniSolrCloudCluster {
    *
    * @throws Exception if there was an error starting the cluster
    */
-  public MiniSolrCloudCluster(int numServers, Path baseDir, String solrXml, JettyConfig jettyConfig, ZkTestServer zkTestServer) throws Exception {
+  public MiniSolrCloudCluster(int numServers, Path baseDir, String solrXml, JettyConfig jettyConfig,
+      ZkTestServer zkTestServer) throws Exception {
+    this(numServers, baseDir, solrXml, jettyConfig, zkTestServer, Optional.empty());
+  }
 
+  /**
+   * Create a MiniSolrCloudCluster.
+   * Note - this constructor visibility is changed to package protected so as to
+   * discourage its usage. Ideally *new* functionality should use {@linkplain SolrCloudTestCase}
+   * to configure any additional parameters.
+   *
+   * @param numServers number of Solr servers to start
+   * @param baseDir base directory that the mini cluster should be run from
+   * @param solrXml solr.xml file to be uploaded to ZooKeeper
+   * @param jettyConfig Jetty configuration
+   * @param zkTestServer ZkTestServer to use.  If null, one will be created
+   * @param securityJson A string representation of security.json file (optional).
+   *
+   * @throws Exception if there was an error starting the cluster
+   */
+   MiniSolrCloudCluster(int numServers, Path baseDir, String solrXml, JettyConfig jettyConfig,
+      ZkTestServer zkTestServer, Optional<String> securityJson) throws Exception {
+
+    Objects.requireNonNull(securityJson);
     this.baseDir = Objects.requireNonNull(baseDir);
     this.jettyConfig = Objects.requireNonNull(jettyConfig);
 
@@ -202,6 +226,9 @@ public class MiniSolrCloudCluster {
       if (jettyConfig.sslConfig != null && jettyConfig.sslConfig.isSSLMode()) {
         zkClient.makePath("/solr" + ZkStateReader.CLUSTER_PROPS, "{'urlScheme':'https'}".getBytes(StandardCharsets.UTF_8), true);
       }
+      if (securityJson.isPresent()) { // configure Solr security
+        zkClient.makePath("/solr/security.json", securityJson.get().getBytes(Charset.defaultCharset()), true);
+      }
     }
 
     // tell solr to look in zookeeper for solr.xml

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a1a8b286/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
index 9cc4a22..34dc8ac 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
@@ -26,6 +26,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
@@ -90,6 +91,7 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
     private final Path baseDir;
     private String solrxml = MiniSolrCloudCluster.DEFAULT_CLOUD_SOLR_XML;
     private JettyConfig jettyConfig = buildJettyConfig("/solr");
+    private Optional<String> securityJson = Optional.empty();
 
     private List<Config> configs = new ArrayList<>();
     private Map<String, String> clusterProperties = new HashMap<>();
@@ -133,6 +135,32 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
     }
 
     /**
+     * Configure the specified security.json for the {@linkplain MiniSolrCloudCluster}
+     *
+     * @param securityJson The path specifying the security.json file
+     * @return the instance of {@linkplain Builder}
+     */
+    public Builder withSecurityJson(Path securityJson) {
+      try {
+        this.securityJson = Optional.of(new String(Files.readAllBytes(securityJson), Charset.defaultCharset()));
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      return this;
+    }
+
+    /**
+     * Configure the specified security.json for the {@linkplain MiniSolrCloudCluster}
+     *
+     * @param securityJson The string specifying the security.json configuration
+     * @return the instance of {@linkplain Builder}
+     */
+    public Builder withSecurityJson(String securityJson) {
+      this.securityJson = Optional.of(securityJson);
+      return this;
+    }
+
+    /**
      * Upload a collection config before tests start
      * @param configName the config name
      * @param configPath the path to the config files
@@ -157,7 +185,7 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
      * @throws Exception if an error occurs on startup
      */
     public void configure() throws Exception {
-      cluster = new MiniSolrCloudCluster(nodeCount, baseDir, solrxml, jettyConfig);
+      cluster = new MiniSolrCloudCluster(nodeCount, baseDir, solrxml, jettyConfig, null, securityJson);
       CloudSolrClient client = cluster.getSolrClient();
       for (Config config : configs) {
         ((ZkClientClusterStateProvider)client.getClusterStateProvider()).uploadConfig(config.path, config.name);