Skip to content

Commit

Permalink
TLS related tests. (#2156)
Browse files Browse the repository at this point in the history
This PR introduced negative test cases related to TLS. Almost all integration tests use TLS so this feature is already pretty well tested.

Signed-off-by: Lukasz Soszynski <[email protected]>
Signed-off-by: Lukasz Soszynski <[email protected]>
  • Loading branch information
lukasz-soszynski-eliatra authored Oct 21, 2022
1 parent 49e7300 commit 5072d05
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 52 deletions.
69 changes: 69 additions & 0 deletions src/integrationTest/java/org/opensearch/security/SslOnlyTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/
package org.opensearch.security;

import java.util.Map;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.opensearch.security.support.ConfigConstants;
import org.opensearch.test.framework.cluster.ClusterManager;
import org.opensearch.test.framework.cluster.LocalCluster;
import org.opensearch.test.framework.cluster.TestRestClient;
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse;

import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;

/**
* Test related to SSL-only mode of security plugin. In this mode, the security plugin is responsible only for TLS/SSL encryption.
* Therefore, the plugin does not perform authentication and authorization. Moreover, the REST resources (e.g. /_plugins/_security/whoami,
* /_plugins/_security/authinfo, etc.) provided by the plugin are not available.
*/
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class SslOnlyTests {


@ClassRule
public static LocalCluster cluster = new LocalCluster.Builder()
.clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false)
.loadConfigurationIntoIndex(false)
.nodeSettings(Map.of(ConfigConstants.SECURITY_SSL_ONLY, true))
.sslOnly(true)
.authc(AUTHC_HTTPBASIC_INTERNAL).build();

@Test
public void shouldNotLoadSecurityPluginResources() {
try(TestRestClient client = cluster.getRestClient()) {

HttpResponse response = client.getAuthInfo();

// in SSL only mode the security plugin does not register a handler for resource /_plugins/_security/whoami. Therefore error
// response is returned.
response.assertStatusCode(400);
}
}

@Test
public void shouldGetIndicesWithoutAuthentication() {
try(TestRestClient client = cluster.getRestClient()) {

// request does not contains credential
HttpResponse response = client.get("/_cat/indices");

// successful response is returned because the security plugin in SSL only mode
// does not perform authentication and authorization
response.assertStatusCode(200);
}
}
}
90 changes: 90 additions & 0 deletions src/integrationTest/java/org/opensearch/security/TlsTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/
package org.opensearch.security;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLHandshakeException;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.NoHttpResponseException;
import org.apache.hc.core5.http.message.BasicHeader;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.opensearch.test.framework.TestSecurityConfig.User;
import org.opensearch.test.framework.cluster.ClusterManager;
import org.opensearch.test.framework.cluster.LocalCluster;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS;
import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy;

@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class TlsTests {

private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS);

public static final String SUPPORTED_CIPHER_SUIT = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
public static final String NOT_SUPPORTED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA";
public static final String AUTH_INFO_ENDPOINT = "/_opendistro/_security/authinfo?pretty";

@ClassRule
public static LocalCluster cluster = new LocalCluster.Builder()
.clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false)
.nodeSettings(Map.of(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of(SUPPORTED_CIPHER_SUIT)))
.authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).build();

@Test
public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException {
try(CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("http://localhost:" + cluster.getHttpPort());

assertThatThrownBy(() -> httpClient.execute(request), instanceOf(NoHttpResponseException.class));
}
//TODO check if audit is created, audit_category = SSL_EXCEPTION
}

@Test
public void shouldSupportClientCipherSuite_positive() throws IOException {
try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { SUPPORTED_CIPHER_SUIT })) {
HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT);
BasicHeader header = cluster.getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword());
httpGet.addHeader(header);

try(CloseableHttpResponse response = client.execute(httpGet)) {

int responseStatusCode = response.getCode();
assertThat(responseStatusCode, equalTo(200));
}
}
}

@Test
public void shouldSupportClientCipherSuite_negative() throws IOException {
try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[]{ NOT_SUPPORTED_CIPHER_SUITE })) {
HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT);

assertThatThrownBy(() -> client.execute(httpGet), instanceOf(SSLHandshakeException.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/
package org.opensearch.test.framework.cluster;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.io.SocketConfig;

class CloseableHttpClientFactory {

private final SSLContext sslContext;

private final RequestConfig requestConfig;

private final String[] supportedCipherSuit;

public CloseableHttpClientFactory(SSLContext sslContext, RequestConfig requestConfig, String[] supportedCipherSuit) {
this.sslContext = Objects.requireNonNull(sslContext, "SSL context is required.");
this.requestConfig = requestConfig;
this.supportedCipherSuit = supportedCipherSuit;
}

public CloseableHttpClient getHTTPClient() {

final HttpClientBuilder hcb = HttpClients.custom();

final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, null, supportedCipherSuit,
NoopHostnameVerifier.INSTANCE);

final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslsf)
.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build())
.build();
hcb.setConnectionManager(cm);

if (requestConfig != null) {
hcb.setDefaultRequestConfig(requestConfig);
}

return hcb.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope

protected static final AtomicLong num = new AtomicLong();

private boolean sslOnly = false;

private final List<Class<? extends Plugin>> plugins;
private final ClusterManager clusterManager;
private final TestSecurityConfig testSecurityConfig;
Expand All @@ -83,19 +85,24 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope
private volatile LocalOpenSearchCluster localOpenSearchCluster;
private final List<TestIndex> testIndices;

private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, Settings nodeOverride,
private boolean loadConfigurationIntoIndex;

private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, boolean sslOnly, Settings nodeOverride,
ClusterManager clusterManager, List<Class<? extends Plugin>> plugins, TestCertificates testCertificates,
List<LocalCluster> clusterDependencies, Map<String, LocalCluster> remotes, List<TestIndex> testIndices) {
List<LocalCluster> clusterDependencies, Map<String, LocalCluster> remotes, List<TestIndex> testIndices,
boolean loadConfigurationIntoIndex) {
this.plugins = plugins;
this.testCertificates = testCertificates;
this.clusterManager = clusterManager;
this.testSecurityConfig = testSgConfig;
this.sslOnly = sslOnly;
this.nodeOverride = nodeOverride;
this.clusterName = clusterName;
this.minimumOpenSearchSettingsSupplierFactory = new MinimumSecuritySettingsSupplierFactory(testCertificates);
this.remotes = remotes;
this.clusterDependencies = clusterDependencies;
this.testIndices = testIndices;
this.loadConfigurationIntoIndex = loadConfigurationIntoIndex;
}

public String getSnapshotDirPath() {
Expand Down Expand Up @@ -151,6 +158,10 @@ public InetSocketAddress getHttpAddress() {
return localOpenSearchCluster.clientNode().getHttpAddress();
}

public int getHttpPort() {
return getHttpAddress().getPort();
}

@Override
public InetSocketAddress getTransportAddress() {
return localOpenSearchCluster.clientNode().getTransportAddress();
Expand Down Expand Up @@ -193,13 +204,13 @@ public Random getRandom() {

private void start() {
try {
localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager,
minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(nodeOverride), plugins, testCertificates);
NodeSettingsSupplier nodeSettingsSupplier = minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(sslOnly, nodeOverride);
localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager, nodeSettingsSupplier, plugins, testCertificates);

localOpenSearchCluster.start();


if (testSecurityConfig != null) {
if (loadConfigurationIntoIndex) {
initSecurityIndex(testSecurityConfig);
}

Expand All @@ -225,6 +236,8 @@ private void initSecurityIndex(TestSecurityConfig testSecurityConfig) {
public static class Builder {

private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder();

private boolean sslOnly = false;
private final List<Class<? extends Plugin>> plugins = new ArrayList<>();
private Map<String, LocalCluster> remoteClusters = new HashMap<>();
private List<LocalCluster> clusterDependencies = new ArrayList<>();
Expand All @@ -234,6 +247,8 @@ public static class Builder {
private String clusterName = "local_cluster";
private TestCertificates testCertificates;

private boolean loadConfigurationIntoIndex = true;

public Builder() {
this.testCertificates = new TestCertificates();
}
Expand Down Expand Up @@ -269,6 +284,11 @@ public Builder config(TestSecurityConfig testSecurityConfig) {
return this;
}

public Builder sslOnly(boolean sslOnly) {
this.sslOnly = sslOnly;
return this;
}

public Builder nodeSettings(Map<String, Object> settings) {
settings.forEach((key, value) -> {
if (value instanceof List) {
Expand Down Expand Up @@ -343,15 +363,18 @@ public Builder anonymousAuth(boolean anonAuthEnabled) {
return this;
}

public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) {
this.loadConfigurationIntoIndex = loadConfigurationIntoIndex;
return this;
}

public LocalCluster build() {
try {

clusterName += "_" + num.incrementAndGet();
Settings settings = nodeOverrideSettingsBuilder
.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false)
.build();
return new LocalCluster(clusterName, testSecurityConfig, settings, clusterManager, plugins,
testCertificates, clusterDependencies, remoteClusters, testIndices);
Settings settings = nodeOverrideSettingsBuilder.build();
return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins,
testCertificates, clusterDependencies, remoteClusters, testIndices, loadConfigurationIntoIndex);
} catch (Exception e) {
log.error("Failed to build LocalCluster", e);
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,6 @@ private Settings getMinimalOpenSearchSettings() {
.put("discovery.initial_state_timeout", "8s").putList("discovery.seed_hosts", seedHosts).put("transport.tcp.port", transportPort)
.put("http.port", httpPort).put("cluster.routing.allocation.disk.threshold_enabled", false)
.put("discovery.probe.connect_timeout", "10s").put("discovery.probe.handshake_timeout", "10s").put("http.cors.enabled", true)
.put("plugins.security.compliance.salt", "1234567890123456")
.put("plugins.security.audit.type", "noop")
.put("gateway.auto_import_dangling_indices", "true")
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
package org.opensearch.test.framework.cluster;

import org.opensearch.common.settings.Settings;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.test.framework.certificate.TestCertificates;

public class MinimumSecuritySettingsSupplierFactory {
Expand All @@ -46,12 +47,8 @@ public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates)

}

public NodeSettingsSupplier minimumOpenSearchSettings(Settings other) {
return i -> minimumOpenSearchSettingsBuilder(i, false).put(other).build();
}

public NodeSettingsSupplier minimumOpenSearchSettingsSslOnly(Settings other) {
return i -> minimumOpenSearchSettingsBuilder(i, true).put(other).build();
public NodeSettingsSupplier minimumOpenSearchSettings(boolean sslOnly, Settings other) {
return i -> minimumOpenSearchSettingsBuilder(i, sslOnly).put(other).build();
}

private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslOnly) {
Expand All @@ -68,9 +65,12 @@ private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslO
builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath());
builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_HTTP_PASSWORD).getAbsolutePath());
builder.put("plugins.security.ssl.http.pemkey_password", PRIVATE_KEY_HTTP_PASSWORD);

builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs());

if(sslOnly == false) {
builder.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false);
builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs());
builder.put("plugins.security.compliance.salt", "1234567890123456");
builder.put("plugins.security.audit.type", "noop");
}
return builder;

}
Expand Down
Loading

0 comments on commit 5072d05

Please sign in to comment.