org.apache.solr.cloud.TestSolrCloudWithSecureImpersonation.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.cloud.TestSolrCloudWithSecureImpersonation.java

Source

/*
 * 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();
            }
        }
    }
}