org.elasticsearch.test.SecuritySingleNodeTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.test.SecuritySingleNodeTestCase.java

Source

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.test;

import io.netty.util.ThreadDeathWatcher;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.license.LicenseService;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.PluginInfo;
import org.elasticsearch.xpack.security.LocalStateSecurity;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.ExternalResource;

import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.elasticsearch.test.SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.core.IsCollectionContaining.hasItem;

/**
 * A test that starts a single node with security enabled. This test case allows for customization
 * of users and roles that the cluster starts with. If an integration test is needed to test but
 * multiple nodes are not needed, then this class should be favored over
 * {@link SecurityIntegTestCase} due to simplicity and improved speed from not needing to start
 * multiple nodes and wait for the cluster to form.
 */
public abstract class SecuritySingleNodeTestCase extends ESSingleNodeTestCase {

    private static SecuritySettingsSource SECURITY_DEFAULT_SETTINGS = null;
    private static CustomSecuritySettingsSource customSecuritySettingsSource = null;
    private static RestClient restClient = null;
    private static SecureString BOOTSTRAP_PASSWORD = null;

    @BeforeClass
    public static void generateBootstrapPassword() {
        BOOTSTRAP_PASSWORD = TEST_PASSWORD_SECURE_STRING.clone();
    }

    @BeforeClass
    public static void initDefaultSettings() {
        if (SECURITY_DEFAULT_SETTINGS == null) {
            SECURITY_DEFAULT_SETTINGS = new SecuritySettingsSource(1, randomBoolean(), createTempDir(),
                    ESIntegTestCase.Scope.SUITE);
        }
    }

    /**
     * Set the static default settings to null to prevent a memory leak. The test framework also checks for memory leaks
     * and computes the size, this can cause issues when running with the security manager as it tries to do reflection
     * into protected sun packages.
     */
    @AfterClass
    public static void destroyDefaultSettings() {
        SECURITY_DEFAULT_SETTINGS = null;
        customSecuritySettingsSource = null;
        if (BOOTSTRAP_PASSWORD != null) {
            BOOTSTRAP_PASSWORD.close();
            BOOTSTRAP_PASSWORD = null;
        }
        tearDownRestClient();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        if (resetNodeAfterTest()) {
            tearDownRestClient();
        }
    }

    private static void tearDownRestClient() {
        if (restClient != null) {
            IOUtils.closeWhileHandlingException(restClient);
            restClient = null;
        }
    }

    @Rule
    //Rules are the only way to have something run before the before (final) method inherited from ESSingleNodeTestCase
    public ExternalResource externalResource = new ExternalResource() {
        @Override
        protected void before() {
            if (customSecuritySettingsSource == null) {
                customSecuritySettingsSource = new CustomSecuritySettingsSource(transportSSLEnabled(),
                        createTempDir(), ESIntegTestCase.Scope.SUITE);
            }
        }
    };

    /**
     * A JUnit class level rule that runs after the AfterClass method in {@link ESIntegTestCase},
     * which stops the cluster. After the cluster is stopped, there are a few netty threads that
     * can linger, so we wait for them to finish otherwise these lingering threads can intermittently
     * trigger the thread leak detector
     */
    @ClassRule
    public static final ExternalResource STOP_NETTY_RESOURCE = new ExternalResource() {
        @Override
        protected void after() {
            try {
                GlobalEventExecutor.INSTANCE.awaitInactivity(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (IllegalStateException e) {
                if (e.getMessage().equals("thread was not started") == false) {
                    throw e;
                }
                // ignore since the thread was never started
            }

            try {
                ThreadDeathWatcher.awaitInactivity(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    };

    @Before
    //before methods from the superclass are run before this, which means that the current cluster is ready to go
    public void assertXPackIsInstalled() {
        doAssertXPackIsInstalled();
    }

    private void doAssertXPackIsInstalled() {
        NodesInfoResponse nodeInfos = client().admin().cluster().prepareNodesInfo().clear().setPlugins(true).get();
        for (NodeInfo nodeInfo : nodeInfos.getNodes()) {
            // TODO: disable this assertion for now, due to random runs with mock plugins. perhaps run without mock plugins?
            // assertThat(nodeInfo.getPlugins().getInfos(), hasSize(2));
            Collection<String> pluginNames = nodeInfo.getPlugins().getPluginInfos().stream()
                    .map(PluginInfo::getClassname).collect(Collectors.toList());
            assertThat("plugin [" + LocalStateSecurity.class.getName() + "] not found in [" + pluginNames + "]",
                    pluginNames, hasItem(LocalStateSecurity.class.getName()));
        }
    }

    @Override
    protected Settings nodeSettings() {
        Settings.Builder builder = Settings.builder().put(super.nodeSettings());
        Settings customSettings = customSecuritySettingsSource.nodeSettings(0);
        builder.put(customSettings, false); // handle secure settings separately
        builder.put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial");
        builder.put("transport.type", "security4");
        builder.put("path.home", customSecuritySettingsSource.nodePath(0));
        Settings.Builder customBuilder = Settings.builder().put(customSettings);
        if (customBuilder.getSecureSettings() != null) {
            SecuritySettingsSource.addSecureSettings(builder,
                    secureSettings -> secureSettings.merge((MockSecureSettings) customBuilder.getSecureSettings()));
        }
        if (builder.getSecureSettings() == null) {
            builder.setSecureSettings(new MockSecureSettings());
        }
        ((MockSecureSettings) builder.getSecureSettings()).setString("bootstrap.password",
                BOOTSTRAP_PASSWORD.toString());
        return builder.build();
    }

    protected Settings transportClientSettings() {
        return Settings.builder().put(customSecuritySettingsSource.transportClientSettings()).build();
    }

    @Override
    protected Collection<Class<? extends Plugin>> getPlugins() {
        return customSecuritySettingsSource.nodePlugins();
    }

    /**
     * Allows to override the users config file
     */
    protected String configUsers() {
        return SECURITY_DEFAULT_SETTINGS.configUsers();
    }

    /**
     * Allows to override the users_roles config file
     */
    protected String configUsersRoles() {
        return SECURITY_DEFAULT_SETTINGS.configUsersRoles();
    }

    /**
     * Allows to override the roles config file
     */
    protected String configRoles() {
        return SECURITY_DEFAULT_SETTINGS.configRoles();
    }

    /**
     * Allows to override the node client username
     */
    protected String nodeClientUsername() {
        return SECURITY_DEFAULT_SETTINGS.nodeClientUsername();
    }

    /**
     * Allows to override the node client password (used while sending requests to the test node)
     */
    protected SecureString nodeClientPassword() {
        return SECURITY_DEFAULT_SETTINGS.nodeClientPassword();
    }

    /**
     * Allows to control whether ssl key information is auto generated or not on the transport layer
     */
    protected boolean transportSSLEnabled() {
        return randomBoolean();
    }

    private class CustomSecuritySettingsSource extends SecuritySettingsSource {

        private CustomSecuritySettingsSource(boolean sslEnabled, Path configDir, ESIntegTestCase.Scope scope) {
            super(1, sslEnabled, configDir, scope);
        }

        @Override
        protected String configUsers() {
            return SecuritySingleNodeTestCase.this.configUsers();
        }

        @Override
        protected String configUsersRoles() {
            return SecuritySingleNodeTestCase.this.configUsersRoles();
        }

        @Override
        protected String configRoles() {
            return SecuritySingleNodeTestCase.this.configRoles();
        }

        @Override
        protected String nodeClientUsername() {
            return SecuritySingleNodeTestCase.this.nodeClientUsername();
        }

        @Override
        protected SecureString nodeClientPassword() {
            return SecuritySingleNodeTestCase.this.nodeClientPassword();
        }
    }

    @Override
    public Client client() {
        Map<String, String> headers = Collections.singletonMap("Authorization",
                basicAuthHeaderValue(nodeClientUsername(), nodeClientPassword()));
        // we need to wrap node clients because we do not specify a user for nodes and all requests will use the system
        // user. This is ok for internal n2n stuff but the test framework does other things like wiping indices, repositories, etc
        // that the system user cannot do. so we wrap the node client with a user that can do these things since the client() calls
        // are all using a node client
        return super.client().filterWithHeader(headers);
    }

    protected boolean isTransportSSLEnabled() {
        return customSecuritySettingsSource.isSslEnabled();
    }

    /**
     * Returns an instance of {@link RestClient} pointing to the current node.
     * Creates a new client if the method is invoked for the first time in the context of the current test scope.
     * The returned client gets automatically closed when needed, it shouldn't be closed as part of tests otherwise
     * it cannot be reused by other tests anymore.
     */
    protected RestClient getRestClient() {
        return getRestClient(client());
    }

    protected RestClient createRestClient(RestClientBuilder.HttpClientConfigCallback httpClientConfigCallback,
            String protocol) {
        return createRestClient(client(), httpClientConfigCallback, protocol);
    }

    private static synchronized RestClient getRestClient(Client client) {
        if (restClient == null) {
            restClient = createRestClient(client, null, "http");
        }
        return restClient;
    }

    private static RestClient createRestClient(Client client,
            RestClientBuilder.HttpClientConfigCallback httpClientConfigCallback, String protocol) {
        NodesInfoResponse nodesInfoResponse = client.admin().cluster().prepareNodesInfo().get();
        assertFalse(nodesInfoResponse.hasFailures());
        assertEquals(nodesInfoResponse.getNodes().size(), 1);
        NodeInfo node = nodesInfoResponse.getNodes().get(0);
        assertNotNull(node.getHttp());
        TransportAddress publishAddress = node.getHttp().address().publishAddress();
        InetSocketAddress address = publishAddress.address();
        final HttpHost host = new HttpHost(NetworkAddress.format(address.getAddress()), address.getPort(),
                protocol);
        RestClientBuilder builder = RestClient.builder(host);
        if (httpClientConfigCallback != null) {
            builder.setHttpClientConfigCallback(httpClientConfigCallback);
        }
        return builder.build();
    }
}