/* * 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.lucene.util.Constants; 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.common.cloud.ZkStateReader; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; 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 if (!Constants.WINDOWS) { // does not work on Windows! 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<String, String>(); 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", "1.1.1.1.1.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 { assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS); 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() throws Exception { ImpersonatorCollectionsHandler.called.set(false); } @AfterClass public static void shutdown() throws Exception { if (miniCluster != null) { miniCluster.shutdown(); } miniCluster = null; if (solrClient != null) { solrClient.close(); } solrClient = 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() { @Override public SolrParams getParams() { ModifiableSolrParams msp = new ModifiableSolrParams(super.getParams()); msp.set(USER_PARAM, "user"); return msp; } }; create.setConfigName(config); create.setCollectionName(name); create.setNumShards(1); create.setReplicationFactor(1); create.setMaxShardsPerNode(1); response = create.process(solrCluster.getSolrClient()); if (response.getStatus() != 0 || response.getErrorMessages() != null) { fail("Could not create collection. Response" + response.toString()); } ZkStateReader zkStateReader = solrCluster.getSolrClient().getZkStateReader(); AbstractDistribZkTestBase.waitForRecoveriesToFinish(name, zkStateReader, false, true, 100); } private SolrRequest getProxyRequest(String user, String doAs) { return getProxyRequest(user, doAs, null); } private SolrRequest getProxyRequest(String user, String doAs, String remoteHost) { return getProxyRequest(user, doAs, remoteHost, null); } 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 { try { solrClient.request(getProxyRequest("noGroups","bar")); fail("Expected RemoteSolrException"); } catch (HttpSolrClient.RemoteSolrException ex) { assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("noGroups", "bar"))); } } @Test public void testProxyWrongHost() throws Exception { try { 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.request(getProxyRequest("noHosts","bar")); fail("Expected RemoteSolrException"); } catch (HttpSolrClient.RemoteSolrException ex) { // 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 { try { // 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 { 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 { try { // Use a reserved ip address String nonProxyUserConfiguredIpAddress = "255.255.255.255"; solrClient.request(getProxyRequest("localHostAnyGroup", "bar", "unknownhost.bar.foo", nonProxyUserConfiguredIpAddress)); fail("Expected RemoteSolrException"); } catch (HttpSolrClient.RemoteSolrException ex) { assertTrue(ex.getMessage().contains(getExpectedHostExMsg("localHostAnyGroup"))); } } @Test public void testProxyInvalidRemote() throws Exception { try { String invalidIpAddress = "-127.-128"; solrClient.request(getProxyRequest("localHostAnyGroup","bar", "[ff01::114]", invalidIpAddress)); fail("Expected RemoteSolrException"); } catch (HttpSolrClient.RemoteSolrException ex) { assertTrue(ex.getMessage().contains(getExpectedHostExMsg("localHostAnyGroup"))); } } @Test public void testProxyInvalidGroup() throws Exception { try { solrClient.request(getProxyRequest("bogusGroup","bar", null)); fail("Expected RemoteSolrException"); } catch (HttpSolrClient.RemoteSolrException ex) { assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("bogusGroup", "bar"))); } } @Test public void testProxyNullProxyUser() throws Exception { try { 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 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()) { 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(); } } } }