blob: 9b6fd0061d88e76a94046708c0487ee31abab7a8 [file] [log] [blame]
/*
* 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.cloud;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hadoop.conf.Configuration;
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.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.cloud.hdfs.HdfsTestUtil;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.HttpParamDelegationTokenPlugin;
import org.apache.solr.security.KerberosPlugin;
import org.apache.solr.servlet.SolrRequestParsers;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.apache.solr.security.HttpParamDelegationTokenPlugin.REMOTE_ADDRESS_PARAM;
import static org.apache.solr.security.HttpParamDelegationTokenPlugin.REMOTE_HOST_PARAM;
import static org.apache.solr.security.HttpParamDelegationTokenPlugin.USER_PARAM;
public class TestSolrCloudWithSecureImpersonation extends SolrTestCaseJ4 {
private static final int NUM_SERVERS = 2;
private static MiniSolrCloudCluster miniCluster;
private static SolrClient solrClient;
private static String getUsersFirstGroup() throws Exception {
String group = "*"; // accept any group if a group can't be found
org.apache.hadoop.security.Groups hGroups =
new org.apache.hadoop.security.Groups(new Configuration());
try {
List<String> g = hGroups.getGroups(System.getProperty("user.name"));
if (g != null && g.size() > 0) {
group = g.get(0);
}
} catch (NullPointerException npe) {
// if user/group doesn't exist on test box
}
return group;
}
private static Map<String, String> getImpersonatorSettings() throws Exception {
Map<String, String> filterProps = new TreeMap<>();
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "noGroups.hosts", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostAnyUser.groups", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostAnyUser.hosts", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "wrongHost.hosts", DEAD_HOST_1);
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "wrongHost.groups", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "noHosts.groups", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "localHostAnyGroup.groups", "*");
InetAddress loopback = InetAddress.getLoopbackAddress();
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "localHostAnyGroup.hosts",
loopback.getCanonicalHostName() + "," + loopback.getHostName() + "," + loopback.getHostAddress());
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostUsersGroup.groups", getUsersFirstGroup());
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostUsersGroup.hosts", "*");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "bogusGroup.groups", "__some_bogus_group");
filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "bogusGroup.hosts", "*");
return filterProps;
}
@BeforeClass
public static void startup() throws Exception {
HdfsTestUtil.checkAssumptions();
System.setProperty("authenticationPlugin", HttpParamDelegationTokenPlugin.class.getName());
System.setProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED, "true");
System.setProperty("solr.kerberos.cookie.domain", "127.0.0.1");
Map<String, String> impSettings = getImpersonatorSettings();
for (Map.Entry<String, String> entry : impSettings.entrySet()) {
System.setProperty(entry.getKey(), entry.getValue());
}
System.setProperty("solr.test.sys.prop1", "propone");
System.setProperty("solr.test.sys.prop2", "proptwo");
SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(true);
System.setProperty("collectionsHandler", ImpersonatorCollectionsHandler.class.getName());
miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), buildJettyConfig("/solr"));
JettySolrRunner runner = miniCluster.getJettySolrRunners().get(0);
solrClient = new HttpSolrClient.Builder(runner.getBaseUrl().toString()).build();
}
/**
* Verify that impersonator info is preserved in the request
*/
public static class ImpersonatorCollectionsHandler extends CollectionsHandler {
public static AtomicBoolean called = new AtomicBoolean(false);
public ImpersonatorCollectionsHandler() { super(); }
public ImpersonatorCollectionsHandler(final CoreContainer coreContainer) {
super(coreContainer);
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
called.set(true);
super.handleRequestBody(req, rsp);
String doAs = req.getParams().get(KerberosPlugin.IMPERSONATOR_DO_AS_HTTP_PARAM);
if (doAs != null) {
HttpServletRequest httpRequest = (HttpServletRequest)req.getContext().get("httpRequest");
assertNotNull(httpRequest);
String user = (String)httpRequest.getAttribute(USER_PARAM);
assertNotNull(user);
assertEquals(user, httpRequest.getAttribute(KerberosPlugin.IMPERSONATOR_USER_NAME));
}
}
}
@Before
public void clearCalledIndicator() {
ImpersonatorCollectionsHandler.called.set(false);
}
@AfterClass
public static void shutdown() throws Exception {
if (solrClient != null) {
IOUtils.closeQuietly(solrClient);
solrClient = null;
}
try {
if (miniCluster != null) {
miniCluster.shutdown();
}
} finally {
miniCluster = null;
System.clearProperty("authenticationPlugin");
System.clearProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED);
System.clearProperty("solr.kerberos.cookie.domain");
Map<String, String> impSettings = getImpersonatorSettings();
for (Map.Entry<String, String> entry : impSettings.entrySet()) {
System.clearProperty(entry.getKey());
}
System.clearProperty("solr.test.sys.prop1");
System.clearProperty("solr.test.sys.prop2");
System.clearProperty("collectionsHandler");
SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(false);
}
}
private void create1ShardCollection(String name, String config, MiniSolrCloudCluster solrCluster) throws Exception {
CollectionAdminResponse response;
CollectionAdminRequest.Create create = new CollectionAdminRequest.Create(name,config,1,1,0,0) {
@Override
public SolrParams getParams() {
ModifiableSolrParams msp = new ModifiableSolrParams(super.getParams());
msp.set(USER_PARAM, "user");
return msp;
}
};
create.setMaxShardsPerNode(1);
response = create.process(solrCluster.getSolrClient());
miniCluster.waitForActiveCollection(name, 1, 1);
if (response.getStatus() != 0 || response.getErrorMessages() != null) {
fail("Could not create collection. Response" + response.toString());
}
}
@SuppressWarnings({"rawtypes"})
private SolrRequest getProxyRequest(String user, String doAs) {
return getProxyRequest(user, doAs, null);
}
@SuppressWarnings({"rawtypes"})
private SolrRequest getProxyRequest(String user, String doAs, String remoteHost) {
return getProxyRequest(user, doAs, remoteHost, null);
}
@SuppressWarnings({"rawtypes"})
private SolrRequest getProxyRequest(String user, String doAs, String remoteHost, String remoteAddress) {
return new CollectionAdminRequest.List() {
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams(super.getParams());
params.set(USER_PARAM, user);
params.set(KerberosPlugin.IMPERSONATOR_DO_AS_HTTP_PARAM, doAs);
if (remoteHost != null) params.set(REMOTE_HOST_PARAM, remoteHost);
if (remoteAddress != null) params.set(REMOTE_ADDRESS_PARAM, remoteAddress);
return params;
}
};
}
private String getExpectedGroupExMsg(String user, String doAs) {
return "User: " + user + " is not allowed to impersonate " + doAs;
}
private String getExpectedHostExMsg(String user) {
return "Unauthorized connection for super-user: " + user;
}
@Test
public void testProxyNoConfigGroups() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("noGroups","bar"))
);
assertTrue(e.getMessage().contains(getExpectedGroupExMsg("noGroups", "bar")));
}
@Test
public void testProxyWrongHost() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("wrongHost","bar"))
);
assertTrue(e.getMessage().contains(getExpectedHostExMsg("wrongHost")));
}
@Test
public void testProxyNoConfigHosts() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("noHosts","bar"))
);
// FixMe: this should return an exception about the host being invalid,
// but a bug (HADOOP-11077) causes an NPE instead.
// assertTrue(ex.getMessage().contains(getExpectedHostExMsg("noHosts")));
}
@Test
public void testProxyValidateAnyHostAnyUser() throws Exception {
solrClient.request(getProxyRequest("anyHostAnyUser", "bar", null));
assertTrue(ImpersonatorCollectionsHandler.called.get());
}
@Test
public void testProxyInvalidProxyUser() throws Exception {
// wrong direction, should fail
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("bar","anyHostAnyUser"))
);
assertTrue(e.getMessage().contains(getExpectedGroupExMsg("bar", "anyHostAnyUser")));
}
@Test
public void testProxyValidateHost() throws Exception {
solrClient.request(getProxyRequest("localHostAnyGroup", "bar"));
assertTrue(ImpersonatorCollectionsHandler.called.get());
}
@Test
public void testProxyValidateGroup() throws Exception {
solrClient.request(getProxyRequest("anyHostUsersGroup", System.getProperty("user.name"), null));
assertTrue(ImpersonatorCollectionsHandler.called.get());
}
@Test
public void testProxyUnknownRemote() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> {
// Use a reserved ip address
String nonProxyUserConfiguredIpAddress = "255.255.255.255";
solrClient.request(getProxyRequest("localHostAnyGroup", "bar", "unknownhost.bar.foo", nonProxyUserConfiguredIpAddress));
});
assertTrue(e.getMessage().contains(getExpectedHostExMsg("localHostAnyGroup")));
}
@Test
public void testProxyInvalidRemote() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> {
solrClient.request(getProxyRequest("localHostAnyGroup","bar", "[ff01::114]", DEAD_HOST_2));
});
}
@Test
public void testProxyInvalidGroup() throws Exception {
HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("bogusGroup","bar", null))
);
assertTrue(e.getMessage().contains(getExpectedGroupExMsg("bogusGroup", "bar")));
}
@Test
public void testProxyNullProxyUser() throws Exception {
expectThrows(HttpSolrClient.RemoteSolrException.class,
() -> solrClient.request(getProxyRequest("","bar"))
);
}
@Test
public void testForwarding() throws Exception {
String collectionName = "forwardingCollection";
miniCluster.uploadConfigSet(TEST_PATH().resolve("collection1/conf"), "conf1");
create1ShardCollection(collectionName, "conf1", miniCluster);
// try a command to each node, one of them must be forwarded
for (JettySolrRunner jetty : miniCluster.getJettySolrRunners()) {
try (HttpSolrClient client = new HttpSolrClient.Builder(
jetty.getBaseUrl().toString() + "/" + collectionName).build()) {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", "*:*");
params.set(USER_PARAM, "user");
client.query(params);
}
}
}
}