Skip to content

Commit

Permalink
REST API tests refactoring (Part 2) (opensearch-project#4252)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Pleskach <ples@aiven.io>
  • Loading branch information
willyborankin authored and dlin2028 committed May 1, 2024
1 parent 4fdcf8b commit ec46c75
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.security.api;

import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test;

import org.opensearch.security.dlic.rest.api.Endpoint;
import org.opensearch.test.framework.cluster.TestRestClient;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED;

public class SslCertsRestApiIntegrationTest extends AbstractApiIntegrationTest {

final static String REST_API_ADMIN_SSL_INFO = "rest-api-admin-ssl-info";

static {
clusterSettings.put(SECURITY_RESTAPI_ADMIN_ENABLED, true);
testSecurityConfig.withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions())
.withRestAdminUser(REST_API_ADMIN_SSL_INFO, restAdminPermission(Endpoint.SSL, CERTS_INFO_ACTION));
}

protected String sslCertsPath() {
return super.apiPath("ssl", "certs");
}

@Test
public void certsInfoForbiddenForRegularUser() throws Exception {
withUser(NEW_USER, client -> forbidden(() -> client.get(sslCertsPath())));
}

@Test
public void certsInfoForbiddenForAdminUser() throws Exception {
withUser(NEW_USER, client -> forbidden(() -> client.get(sslCertsPath())));
}

@Test
public void certsInfoAvailableForTlsAdmin() throws Exception {
withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifySSLCertsInfo);
}

@Test
public void certsInfoAvailableForRestAdmin() throws Exception {
withUser(REST_ADMIN_USER, this::verifySSLCertsInfo);
withUser(REST_API_ADMIN_SSL_INFO, this::verifySSLCertsInfo);
}

private void verifySSLCertsInfo(final TestRestClient client) throws Exception {
final var response = ok(() -> client.get(sslCertsPath()));

final var body = response.bodyAsJsonNode();
assertThat(response.getBody(), body.has("http_certificates_list"));
assertThat(response.getBody(), body.get("http_certificates_list").isArray());
verifyCertsJson(body.get("http_certificates_list").get(0));
assertThat(response.getBody(), body.has("transport_certificates_list"));
assertThat(response.getBody(), body.get("transport_certificates_list").isArray());
verifyCertsJson(body.get("transport_certificates_list").get(0));
}

private void verifyCertsJson(final JsonNode jsonNode) {
assertThat(jsonNode.toPrettyString(), jsonNode.has("issuer_dn"));
assertThat(jsonNode.toPrettyString(), jsonNode.has("subject_dn"));
assertThat(jsonNode.toPrettyString(), jsonNode.get("subject_dn").asText().matches(".*node-\\d.example.com+"));
assertThat(jsonNode.toPrettyString(), jsonNode.get("san").asText().matches(".*node-\\d.example.com.*"));
assertThat(jsonNode.toPrettyString(), jsonNode.has("not_before"));
assertThat(jsonNode.toPrettyString(), jsonNode.has("not_after"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ public class TestCertificates {

private static final Logger log = LogManager.getLogger(TestCertificates.class);

public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3;
public static final Integer DEFAULT_NUMBER_OF_NODE_CERTIFICATES = 3;

public static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA";

public static final String LDAP_SUBJECT = "DC=de,L=test,O=node,OU=node,CN=ldap.example.com";
public static final String NODE_SUBJECT_PATTERN = "DC=de,L=test,O=node,OU=node,CN=node-%d.example.com";

private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA";
private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de";
private static final int CERTIFICATE_VALIDITY_DAYS = 365;
private static final String CERTIFICATE_FILE_EXT = ".cert";
Expand All @@ -66,13 +70,18 @@ public class TestCertificates {
private final CertificateData adminCertificate;
private final List<CertificateData> nodeCertificates;

private final int numberOfNodes;

private final CertificateData ldapCertificate;

public TestCertificates() {
this(DEFAULT_NUMBER_OF_NODE_CERTIFICATES);
}

public TestCertificates(final int numberOfNodes) {
this.caCertificate = createCaCertificate();
this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES)
.mapToObj(this::createNodeCertificate)
.collect(Collectors.toList());
this.numberOfNodes = numberOfNodes;
this.nodeCertificates = IntStream.range(0, this.numberOfNodes).mapToObj(this::createNodeCertificate).collect(Collectors.toList());
this.ldapCertificate = createLdapCertificate();
this.adminCertificate = createAdminCertificate(ADMIN_DN);
log.info("Test certificates successfully generated");
Expand Down Expand Up @@ -109,7 +118,7 @@ public CertificateData getRootCertificateData() {

/**
* Certificate for Open Search node. The certificate is derived from root certificate, returned by method {@link #getRootCertificate()}
* @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES}
* @param node is a node index. It has to be less than {@link #DEFAULT_NUMBER_OF_NODE_CERTIFICATES}
* @return file which contains certificate in PEM format, defined by <a href="https://www.rfc-editor.org/rfc/rfc1421.txt">RFC 1421</a>
*/
public File getNodeCertificate(int node) {
Expand All @@ -123,18 +132,18 @@ public CertificateData getNodeCertificateData(int node) {
}

private void isCorrectNodeNumber(int node) {
if (node >= MAX_NUMBER_OF_NODE_CERTIFICATES) {
if (node >= numberOfNodes) {
String message = String.format(
"Cannot get certificate for node %d, number of created certificates for nodes is %d",
node,
MAX_NUMBER_OF_NODE_CERTIFICATES
numberOfNodes
);
throw new RuntimeException(message);
}
}

private CertificateData createNodeCertificate(Integer node) {
String subject = String.format("DC=de,L=test,O=node,OU=node,CN=node-%d.example.com", node);
final var subject = String.format(NODE_SUBJECT_PATTERN, node);
String domain = String.format("node-%d.example.com", node);
CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS)
.withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH)
Expand All @@ -150,8 +159,7 @@ public CertificateData issueUserCertificate(String organizationUnit, String user
}

private CertificateData createLdapCertificate() {
String subject = "DC=de,L=test,O=node,OU=node,CN=ldap.example.com";
CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS)
CertificateMetadata metadata = CertificateMetadata.basicMetadata(LDAP_SUBJECT, CERTIFICATE_VALIDITY_DAYS)
.withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH)
.withSubjectAlternativeName(null, List.of("localhost"), "127.0.0.1");
return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate);
Expand All @@ -164,7 +172,7 @@ public CertificateData getLdapCertificateData() {
/**
* It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)}
*
* @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES}
* @param node is a node index. It has to be less than {@link #DEFAULT_NUMBER_OF_NODE_CERTIFICATES}
* @param privateKeyPassword is a password used to encode private key, can be <code>null</code> to retrieve unencrypted key.
* @return file which contains private key encoded in PEM format, defined
* by <a href="https://www.rfc-editor.org/rfc/rfc1421.txt">RFC 1421</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ public void before() {
}

for (Map.Entry<String, LocalCluster> entry : remotes.entrySet()) {
@SuppressWarnings("resource")
InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress();
String key = "cluster.remote." + entry.getKey() + ".seeds";
String value = transportAddress.getHostString() + ":" + transportAddress.getPort();
Expand Down Expand Up @@ -509,7 +508,7 @@ public Builder defaultConfigurationInitDirectory(String defaultConfigurationInit
public LocalCluster build() {
try {
if (testCertificates == null) {
testCertificates = new TestCertificates();
testCertificates = new TestCertificates(clusterManager.getNodes());
}
clusterName += "_" + num.incrementAndGet();
Settings settings = nodeOverrideSettingsBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public class LocalOpenSearchCluster {

private File snapshotDir;

private int nodeCounter = 0;

public LocalOpenSearchCluster(
String clusterName,
ClusterManager clusterManager,
Expand Down Expand Up @@ -163,7 +165,6 @@ public void start() throws Exception {
this.initialClusterManagerHosts = toHostList(clusterManagerPorts);

started = true;

CompletableFuture<Void> clusterManagerNodeFuture = startNodes(
clusterManager.getClusterManagerNodeSettings(),
clusterManagerNodeTransportPorts,
Expand Down Expand Up @@ -195,7 +196,6 @@ public void start() throws Exception {
log.info("Startup finished. Waiting for GREEN");

waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), nodes.size());

log.info("Started: {}", this);

}
Expand Down Expand Up @@ -303,10 +303,10 @@ private CompletableFuture<Void> startNodes(
List<CompletableFuture<StartStage>> futures = new ArrayList<>();

for (NodeSettings nodeSettings : nodeSettingList) {
Node node = new Node(nodeSettings, transportPortIterator.next(), httpPortIterator.next());
Node node = new Node(nodeCounter, nodeSettings, transportPortIterator.next(), httpPortIterator.next());
futures.add(node.start());
nodeCounter += 1;
}

return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}

Expand Down Expand Up @@ -386,8 +386,10 @@ public class Node implements OpenSearchClientProvider {
private PluginAwareNode node;
private boolean running = false;
private boolean portCollision = false;
private final int nodeNumber;

Node(NodeSettings nodeSettings, int transportPort, int httpPort) {
Node(int nodeNumber, NodeSettings nodeSettings, int transportPort, int httpPort) {
this.nodeNumber = nodeNumber;
this.nodeName = createNextNodeName(requireNonNull(nodeSettings, "Node settings are required."));
this.nodeSettings = nodeSettings;
this.nodeHomeDir = new File(clusterHomeDir, nodeName);
Expand Down Expand Up @@ -517,7 +519,7 @@ private Settings getOpenSearchSettings() {

if (nodeSettingsSupplier != null) {
// TODO node number
return Settings.builder().put(settings).put(nodeSettingsSupplier.get(0)).build();
return Settings.builder().put(settings).put(nodeSettingsSupplier.get(nodeNumber)).build();
}
return settings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,6 @@ public class OpenSearchSecuritySSLPlugin extends Plugin implements SystemIndexPl
protected final SSLConfig SSLConfig;
protected volatile ThreadPool threadPool;

// public OpenSearchSecuritySSLPlugin(final Settings settings, final Path configPath) {
// this(settings, configPath, false);
// }

@SuppressWarnings("removal")
protected OpenSearchSecuritySSLPlugin(final Settings settings, final Path configPath, boolean disabled) {

Expand Down
22 changes: 11 additions & 11 deletions src/main/java/org/opensearch/security/ssl/SecurityKeyStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,25 @@

public interface SecurityKeyStore {

public SSLEngine createHTTPSSLEngine() throws SSLException;
SSLEngine createHTTPSSLEngine() throws SSLException;

public SSLEngine createServerTransportSSLEngine() throws SSLException;
SSLEngine createServerTransportSSLEngine() throws SSLException;

public SSLEngine createClientTransportSSLEngine(String peerHost, int peerPort) throws SSLException;
SSLEngine createClientTransportSSLEngine(String peerHost, int peerPort) throws SSLException;

public String getHTTPProviderName();
String getHTTPProviderName();

public String getTransportServerProviderName();
String getTransportServerProviderName();

public String getTransportClientProviderName();
String getTransportClientProviderName();

public String getSubjectAlternativeNames(X509Certificate cert);
String getSubjectAlternativeNames(X509Certificate cert);

public void initHttpSSLConfig();
void initHttpSSLConfig();

public void initTransportSSLConfig();
void initTransportSSLConfig();

public X509Certificate[] getTransportCerts();
X509Certificate[] getTransportCerts();

public X509Certificate[] getHttpCerts();
X509Certificate[] getHttpCerts();
}
Loading

0 comments on commit ec46c75

Please sign in to comment.