diff --git a/build.gradle b/build.gradle index e9a4bba83f..3a0ddecacd 100644 --- a/build.gradle +++ b/build.gradle @@ -494,6 +494,9 @@ configurations { force "net.bytebuddy:byte-buddy:1.17.6" force "org.ow2.asm:asm:9.8" force "com.google.j2objc:j2objc-annotations:3.0.0" + force "com.google.protobuf:protobuf-java:3.25.5" + force "io.perfmark:perfmark-api:0.27.0" + force "com.google.guava:failureaccess:1.0.3" } } @@ -533,6 +536,11 @@ allprojects { integrationTestImplementation("org.opensearch.plugin:reindex-client:${opensearch_version}"){ exclude(group: 'org.slf4j', module: 'slf4j-api') } + integrationTestImplementation "org.opensearch:protobufs:0.3.0" + integrationTestImplementation "io.grpc:grpc-protobuf:${versions.grpc}" + integrationTestImplementation "io.grpc:grpc-stub:${versions.grpc}" + integrationTestImplementation "io.grpc:grpc-netty-shaded:${versions.grpc}" + integrationTestImplementation "org.opensearch.plugin:transport-grpc:${opensearch_version}" integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" integrationTestImplementation 'commons-io:commons-io:2.19.0' integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index b644acc00b..e9da98a85a 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -67,6 +67,8 @@ configurations.all { force 'org.hamcrest:hamcrest:2.2' force 'org.mockito:mockito-core:5.18.0' force 'org.slf4j:slf4j-api:1.7.36' + force 'com.google.errorprone:error_prone_annotations:2.36.0' + force 'com.google.guava:guava:33.4.8-jre' } } diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index 175eb109e8..748b036d16 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -14,9 +14,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -24,6 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.junit.Test; +import org.opensearch.common.CheckedConsumer; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.security.ssl.config.CertType; import org.opensearch.test.framework.TestSecurityConfig; @@ -37,12 +36,12 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; +import static junit.framework.TestCase.fail; public class CertificatesRestApiIntegrationTest extends AbstractApiIntegrationTest { - final static String REST_API_ADMIN_SSL_INFO = "rest-api-admin-ssl-info"; - final static String REGULAR_USER = "regular_user"; + final static String ROOT_CA = "Root CA"; static { testSecurityConfig.roles( @@ -89,13 +88,17 @@ public void forbiddenForAdminUser() throws Exception { @Test public void availableForTlsAdmin() throws Exception { - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifySSLCertsInfo); + withUser( + ADMIN_USER_NAME, + localCluster.getAdminCertificate(), + verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)) + ); } @Test public void availableForRestAdmin() throws Exception { - withUser(REST_ADMIN_USER, this::verifySSLCertsInfo); - withUser(REST_API_ADMIN_SSL_INFO, this::verifySSLCertsInfo); + withUser(REST_ADMIN_USER, verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT))); + withUser(REST_API_ADMIN_SSL_INFO, verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT))); } @Test @@ -107,72 +110,78 @@ private void verifyTimeoutRequest(final TestRestClient client) throws Exception ok(() -> client.get(sslCertsPath() + "?timeout=0")); } - private void verifySSLCertsInfo(final TestRestClient client) throws Exception { - assertSSLCertsInfo(localCluster.nodes(), CertType.TYPES, ok(() -> client.get(sslCertsPath()))); - if (localCluster.nodes().size() > 1) { - final var randomNodes = randomNodes(); - final var nodeIds = randomNodes.stream().map(n -> n.esNode().getNodeEnvironment().nodeId()).collect(Collectors.joining(",")); - assertSSLCertsInfo(randomNodes, CertType.TYPES, ok(() -> client.get(sslCertsPath(nodeIds)))); - } - final var randomCertType = randomFrom(List.copyOf(CertType.TYPES)); - assertSSLCertsInfo( - localCluster.nodes(), - Set.of(randomCertType), - ok(() -> client.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType))) - ); - + private CheckedConsumer verifySSLCertsInfo(List expectCerts) { + return testRestClient -> { + try { + assertSSLCertsInfo(localCluster.nodes(), expectCerts, ok(() -> testRestClient.get(sslCertsPath()))); + if (localCluster.nodes().size() > 1) { + final var randomNodes = randomNodes(); + final var nodeIds = randomNodes.stream() + .map(n -> n.esNode().getNodeEnvironment().nodeId()) + .collect(Collectors.joining(",")); + assertSSLCertsInfo(randomNodes, expectCerts, ok(() -> testRestClient.get(sslCertsPath(nodeIds)))); + } + final var randomCertType = randomFrom(expectCerts); + assertSSLCertsInfo( + localCluster.nodes(), + List.of(randomCertType), + ok(() -> testRestClient.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType))) + ); + } catch (Exception e) { + fail("Verify SSLCerts info failed with exception: " + e.getMessage()); + } + }; } private void assertSSLCertsInfo( final List expectedNode, - final Set expectedCertTypes, + final List expectedCertTypes, final TestRestClient.HttpResponse response ) { final var body = response.bodyAsJsonNode(); final var prettyStringBody = body.toPrettyString(); - final var _nodes = body.get("_nodes"); assertThat(prettyStringBody, _nodes.get("total").asInt(), is(expectedNode.size())); assertThat(prettyStringBody, _nodes.get("successful").asInt(), is(expectedNode.size())); assertThat(prettyStringBody, _nodes.get("failed").asInt(), is(0)); assertThat(prettyStringBody, body.get("cluster_name").asText(), is(localCluster.getClusterName())); - final var nodes = body.get("nodes"); - for (final var n : expectedNode) { final var esNode = n.esNode(); final var node = nodes.get(esNode.getNodeEnvironment().nodeId()); assertThat(prettyStringBody, node.get("name").asText(), is(n.getNodeName())); assertThat(prettyStringBody, node.has("certificates")); final var certificates = node.get("certificates"); - if (expectedCertTypes.contains(CertType.HTTP.name().toUpperCase(Locale.ROOT))) { - final var httpCertificates = certificates.get(CertType.HTTP.name().toUpperCase(Locale.ROOT)); - assertThat(prettyStringBody, httpCertificates.isArray()); - assertThat(prettyStringBody, httpCertificates.size(), is(1)); - verifyCertsJson(n.nodeNumber(), httpCertificates.get(0)); - } - if (expectedCertTypes.contains(CertType.TRANSPORT_CLIENT.name().toUpperCase(Locale.ROOT))) { - final var transportCertificates = certificates.get(CertType.TRANSPORT.name().toUpperCase(Locale.ROOT)); + /* + Expect each node transport configured with root and issued cert. + */ + for (CertType expectCert : expectedCertTypes) { + final JsonNode transportCertificates = certificates.get(expectCert.id()); assertThat(prettyStringBody, transportCertificates.isArray()); - assertThat(prettyStringBody, transportCertificates.size(), is(1)); + assertThat(prettyStringBody, transportCertificates.size(), is(2)); verifyCertsJson(n.nodeNumber(), transportCertificates.get(0)); + verifyCertsJson(n.nodeNumber(), transportCertificates.get(1)); } } - } private void verifyCertsJson(final int nodeNumber, final JsonNode jsonNode) { + if (jsonNode.get("subject_dn").asText().contains(ROOT_CA)) { // handle root cert + assertThat(jsonNode.toPrettyString(), jsonNode.get("subject_dn").asText(), is(TestCertificates.CA_SUBJECT)); + assertThat(jsonNode.toPrettyString(), jsonNode.get("san").asText().isEmpty()); + } else { // handle non-root + assertThat( + jsonNode.toPrettyString(), + jsonNode.get("subject_dn").asText(), + is(String.format(TestCertificates.NODE_SUBJECT_PATTERN, nodeNumber)) + ); + assertThat( + jsonNode.toPrettyString(), + jsonNode.get("san").asText(), + containsString(String.format("node-%s.example.com", nodeNumber)) + ); + } assertThat(jsonNode.toPrettyString(), jsonNode.get("issuer_dn").asText(), is(TestCertificates.CA_SUBJECT)); - assertThat( - jsonNode.toPrettyString(), - jsonNode.get("subject_dn").asText(), - is(String.format(TestCertificates.NODE_SUBJECT_PATTERN, nodeNumber)) - ); - assertThat( - jsonNode.toPrettyString(), - jsonNode.get("san").asText(), - containsString(String.format("node-%s.example.com", nodeNumber)) - ); assertThat(jsonNode.toPrettyString(), jsonNode.has("not_before")); assertThat(jsonNode.toPrettyString(), jsonNode.has("not_after")); } @@ -193,5 +202,4 @@ public List randomSubsetOf(int size, Collection collection) { Collections.shuffle(tempList, RandomizedContext.current().getRandom()); return tempList.subList(0, size); } - } diff --git a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java new file mode 100644 index 0000000000..6e60b9a2a8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java @@ -0,0 +1,77 @@ +/* + * 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.grpc; + +import java.io.IOException; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.plugin.transport.grpc.GrpcPlugin; +import org.opensearch.protobufs.SearchRequest; +import org.opensearch.protobufs.SearchRequestBody; +import org.opensearch.protobufs.SearchResponse; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestGrpcClient; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class GrpcTests { + + private static final Logger log = LogManager.getLogger(GrpcTests.class); + + static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .certificates(TEST_CERTIFICATES) + .plugin(GrpcPlugin.class) + .grpc(TEST_CERTIFICATES) + .loadConfigurationIntoIndex(false) + .nodeSettings(Map.of(ConfigConstants.SECURITY_SSL_ONLY, true)) + .sslOnly(true) + .build(); + + @Test + public void testSearch() throws IOException { + try (TestRestClient client = cluster.getRestClient()) { + TestRestClient.HttpResponse response = client.put("test-index"); + response.assertStatusCode(200); + client.postJson("test-index/_doc/1", "{\"field\": \"value\"}"); + } + + // TODO this is SSL Only test, but eventually a test with authc should be added as well + TestGrpcClient client = cluster.getGrpcClient(TEST_CERTIFICATES); + // Create a search request + SearchRequestBody requestBody = SearchRequestBody.newBuilder().setFrom(0).setSize(10).build(); + + SearchRequest searchRequest = SearchRequest.newBuilder() + .addIndex("test-index") + .setRequestBody(requestBody) + .setQ("field:value") + .build(); + SearchResponse response = client.search(searchRequest); + System.out.println("Search Response: " + response); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index d7de58fef7..c37d59baaf 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -495,6 +495,31 @@ public Builder audit(AuditConfiguration auditConfiguration) { return this; } + public Builder grpc(TestCertificates testCertificates) { + final String PRIVATE_KEY_GRPC_PASSWORD = "aWVV63OJ4qzZyPrBwl2MFny4ZV8lQRZchjL"; + nodeOverrideSettingsBuilder.put("aux.transport.types", "experimental-secure-transport-grpc"); + nodeOverrideSettingsBuilder.put("aux.transport.experimental-secure-transport-grpc.port", "9400-9500"); + nodeOverrideSettingsBuilder.put("plugins.security.ssl.aux.experimental-secure-transport-grpc.enabled", true); + nodeOverrideSettingsBuilder.put( + "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemcert_filepath", + testCertificates.getNodeCertificate(0).getAbsolutePath() + ); + nodeOverrideSettingsBuilder.put( + "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemkey_filepath", + testCertificates.getNodeKey(0, null).getAbsolutePath() + ); + // nodeOverrideSettingsBuilder.put( + // "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemkey_password", + // PRIVATE_KEY_GRPC_PASSWORD + // ); + nodeOverrideSettingsBuilder.put("plugins.security.ssl.aux.experimental-secure-transport-grpc.clientauth_mode", "OPTIONAL"); + nodeOverrideSettingsBuilder.put( + "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemtrustedcas_filepath", + testCertificates.getRootCertificate().getAbsolutePath() + ); + return this; + } + public Builder internalAudit(AuditConfiguration auditConfiguration) { if (auditConfiguration != null) { testSecurityConfig.audit(auditConfiguration); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 131ff65615..0bda3bd513 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -232,6 +232,10 @@ default TestRestClient getRestClient(List
headers, CertificateData useCe return createGenericClientRestClient(headers, useCertificateData, null); } + default TestGrpcClient getGrpcClient(TestCertificates testCertificates) { + return createGenericClientGrpcClient(testCertificates); + } + default TestRestClient getSecurityDisabledRestClient() { return new TestRestClient(getHttpAddress(), List.of(), getSSLContext(null), null, false, false); } @@ -244,6 +248,10 @@ default TestRestClient createGenericClientRestClient( return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress, true, false); } + default TestGrpcClient createGenericClientGrpcClient(TestCertificates testCertificates) { + return new TestGrpcClient(getHttpAddress(), "", getSSLContext(), testCertificates); + } + default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { return new TestRestClient( getHttpAddress(), diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java new file mode 100644 index 0000000000..f496b20e58 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -0,0 +1,61 @@ +package org.opensearch.test.framework.cluster; + +import java.net.InetSocketAddress; +import javax.net.ssl.SSLContext; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.protobufs.SearchRequest; +import org.opensearch.protobufs.SearchResponse; +import org.opensearch.protobufs.services.SearchServiceGrpc; +import org.opensearch.test.framework.certificate.TestCertificates; + +import io.grpc.ChannelCredentials; +import io.grpc.Grpc; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.TlsChannelCredentials; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +public class TestGrpcClient { + private static final Logger log = LogManager.getLogger(TestRestClient.class); + + Metadata.Key AUTHORIZATION_KEY = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); + + private InetSocketAddress nodeHttpAddress; + private String authorizationHeader; + private SSLContext sslContext; + private TestCertificates testCertificates; + + public TestGrpcClient( + InetSocketAddress nodeHttpAddress, + String authorizationHeader, + SSLContext sslContext, + TestCertificates testCertificates + ) { + this.nodeHttpAddress = nodeHttpAddress; + this.authorizationHeader = authorizationHeader; + this.sslContext = sslContext; + this.testCertificates = testCertificates; + } + + public SearchResponse search(SearchRequest request) { + SearchServiceGrpc.SearchServiceBlockingStub client = client(); + Metadata md = new Metadata(); + + md.put(AUTHORIZATION_KEY, authorizationHeader); + // client = MetadataUtils.attachHeaders(client, md); + return client.search(request); + } + + private SearchServiceGrpc.SearchServiceBlockingStub client() { + ChannelCredentials credentials = null; + credentials = TlsChannelCredentials.newBuilder() + // You can use your own certificate here .trustManager(new File("cert.pem")) + .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) + .build(); + ManagedChannel channel = Grpc.newChannelBuilderForAddress("localhost", 9400, credentials).build(); + return SearchServiceGrpc.newBlockingStub(channel); + } +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfo.java b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfo.java index f529cd4731..2312b462c7 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfo.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfo.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.util.List; -import java.util.Locale; import java.util.Map; import org.opensearch.core.common.io.stream.StreamInput; @@ -32,20 +31,20 @@ public CertificatesInfo(final Map> certificates) } public CertificatesInfo(final StreamInput in) throws IOException { - certificates = in.readMap(keyIn -> keyIn.readEnum(CertType.class), listIn -> listIn.readList(CertificateInfo::new)); + certificates = in.readMap(CertType::new, listIn -> listIn.readList(CertificateInfo::new)); } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeMap(certificates, StreamOutput::writeEnum, StreamOutput::writeList); + out.writeMap(certificates, (streamOutput, certType) -> certType.writeTo(streamOutput), StreamOutput::writeList); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject("certificates") - .field(CertType.HTTP.name().toLowerCase(Locale.ROOT), certificates.get(CertType.HTTP)) - .field(CertType.TRANSPORT.name().toLowerCase(Locale.ROOT), certificates.get(CertType.TRANSPORT)) - .field(CertType.TRANSPORT_CLIENT.name().toLowerCase(Locale.ROOT), certificates.get(CertType.TRANSPORT_CLIENT)) - .endObject(); + builder.startObject("certificates"); + for (Map.Entry> entry : certificates.entrySet()) { + builder.field(entry.getKey().id(), certificates.get(entry.getKey())); + } + return builder.endObject(); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfoNodesRequest.java b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfoNodesRequest.java index fbe81225c5..7b356869e4 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfoNodesRequest.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/CertificatesInfoNodesRequest.java @@ -22,25 +22,23 @@ import org.opensearch.security.ssl.config.CertType; public class CertificatesInfoNodesRequest extends BaseNodesRequest { - - private final String certificateType; - + private final String certTypeID; private final boolean inMemory; - public CertificatesInfoNodesRequest(String certificateType, boolean inMemory, String... nodesIds) { + public CertificatesInfoNodesRequest(String certTypeID, boolean inMemory, String... nodesIds) { super(nodesIds); - this.certificateType = certificateType; + this.certTypeID = certTypeID; this.inMemory = inMemory; } public CertificatesInfoNodesRequest(final StreamInput in) throws IOException { super(in); - certificateType = in.readOptionalString(); + certTypeID = in.readOptionalString(); inMemory = in.readBoolean(); } public Optional certificateType() { - return Optional.ofNullable(certificateType); + return Optional.ofNullable(certTypeID); } public boolean inMemory() { @@ -50,15 +48,15 @@ public boolean inMemory() { @Override public void writeTo(final StreamOutput out) throws IOException { super.writeTo(out); - out.writeOptionalString(certificateType); + out.writeOptionalString(certTypeID); out.writeBoolean(inMemory); } @Override public ActionRequestValidationException validate() { - if (!Strings.isEmpty(certificateType) && !CertType.TYPES.contains(certificateType)) { + if (!Strings.isEmpty(certTypeID) && !CertType.CERT_TYPE_REGISTRY.contains(certTypeID)) { final var errorMessage = new ActionRequestValidationException(); - errorMessage.addValidationError("wrong certificate type " + certificateType + ". Please use one of " + CertType.TYPES); + errorMessage.addValidationError("wrong certificate type " + certTypeID + ". Please use one of " + CertType.CERT_TYPE_REGISTRY); return errorMessage; } return super.validate(); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/TransportCertificatesInfoNodesAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/TransportCertificatesInfoNodesAction.java index 77305f91ed..d1fd775cc2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ssl/TransportCertificatesInfoNodesAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/TransportCertificatesInfoNodesAction.java @@ -12,8 +12,8 @@ package org.opensearch.security.dlic.rest.api.ssl; import java.io.IOException; +import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -97,39 +97,18 @@ protected CertificatesNodesResponse.CertificatesNodeResponse nodeOperation(final } } - protected CertificatesInfo loadCertificates(final Optional certificateType) { - var httpCertificates = List.of(); - var transportCertificates = List.of(); - var transportClientCertificates = List.of(); - final var certType = certificateType.map(t -> CertType.valueOf(t.toUpperCase(Locale.ROOT))).orElse(null); - if (certType == null || certType == CertType.HTTP) { - httpCertificates = sslSettingsManager.sslContextHandler(CertType.HTTP) - .map(SslContextHandler::certificates) - .map(this::certificatesDetails) - .orElse(List.of()); + protected CertificatesInfo loadCertificates(final Optional loadCertType) { + Map> certInfos = new HashMap<>(); + for (CertType certType : CertType.CERT_TYPE_REGISTRY) { + if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.id())) { + List certs = sslSettingsManager.sslContextHandler(certType) + .map(SslContextHandler::certificates) + .map(this::certificatesDetails) + .orElse(List.of()); + certInfos.put(certType, certs); + } } - if (certType == null || certType == CertType.TRANSPORT) { - transportCertificates = sslSettingsManager.sslContextHandler(CertType.TRANSPORT) - .map(SslContextHandler::certificates) - .map(this::certificatesDetails) - .orElse(List.of()); - } - if (certType == null || certType == CertType.TRANSPORT_CLIENT) { - transportClientCertificates = sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) - .map(SslContextHandler::certificates) - .map(this::certificatesDetails) - .orElse(List.of()); - } - return new CertificatesInfo( - Map.of( - CertType.HTTP, - httpCertificates, - CertType.TRANSPORT, - transportCertificates, - CertType.TRANSPORT_CLIENT, - transportClientCertificates - ) - ); + return new CertificatesInfo(certInfos); } private List certificatesDetails(final Stream certificateStream) { diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 6b0d1f3c25..7f84c2c681 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -315,6 +315,9 @@ private void evaluateSystemIndicesAccess( presponse.markComplete(); return; } else { + System.out.println("Plugin user: " + user.getName() + " requested indices: " + requestedResolved.getAllIndices()); + System.out.println("action: " + action); + Thread.dumpStack(); Set matchingSystemIndices = SystemIndexRegistry.matchesSystemIndexPattern(requestedResolved.getAllIndices()); matchingSystemIndices.removeAll(matchingPluginIndices); // See if request matches other system indices not belong to the plugin diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index d6f24c125c..8f5926d9af 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -68,6 +68,7 @@ import org.opensearch.SpecialPermission; import org.opensearch.common.settings.Settings; import org.opensearch.env.Environment; +import org.opensearch.security.ssl.config.CertType; import org.opensearch.security.ssl.util.CertFileProps; import org.opensearch.security.ssl.util.CertFromFile; import org.opensearch.security.ssl.util.CertFromKeystore; @@ -807,16 +808,16 @@ private String[] getEnabledSSLProtocols(final SslProvider provider, boolean http private void initEnabledSSLCiphers() { final ImmutableSet allowedSecureHttpSSLCiphers = ImmutableSet.copyOf( - SSLConfigConstants.getSecureSSLCiphers(settings, true) + SSLConfigConstants.getSecureSSLCiphers(settings, CertType.HTTP) ); final ImmutableSet allowedSecureTransportSSLCiphers = ImmutableSet.copyOf( - SSLConfigConstants.getSecureSSLCiphers(settings, false) + SSLConfigConstants.getSecureSSLCiphers(settings, CertType.TRANSPORT) ); final ImmutableSet allowedSecureHttpSSLProtocols = ImmutableSet.copyOf( - (SSLConfigConstants.getSecureSSLProtocols(settings, true)) + (SSLConfigConstants.getSecureSSLProtocols(settings, CertType.HTTP)) ); final ImmutableSet allowedSecureTransportSSLProtocols = ImmutableSet.copyOf( - SSLConfigConstants.getSecureSSLProtocols(settings, false) + SSLConfigConstants.getSecureSSLProtocols(settings, CertType.TRANSPORT) ); SSLEngine engine = null; diff --git a/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java index 25d0599c58..ad05651b5e 100644 --- a/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java @@ -31,6 +31,7 @@ import org.opensearch.OpenSearchException; import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.config.CertType; import org.opensearch.security.ssl.util.SSLConfigConstants; public class ExternalSecurityKeyStore implements SecurityKeyStore { @@ -72,17 +73,27 @@ public SSLEngine createClientTransportSSLEngine(final String peerHost, final int final SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); engine.setSSLParameters(sslParams); - engine.setEnabledProtocols(evalSecure(engine.getEnabledProtocols(), SSLConfigConstants.getSecureSSLProtocols(settings, false))); + engine.setEnabledProtocols( + evalSecure(engine.getEnabledProtocols(), SSLConfigConstants.getSecureSSLProtocols(settings, CertType.TRANSPORT)) + ); engine.setEnabledCipherSuites( - evalSecure(engine.getEnabledCipherSuites(), SSLConfigConstants.getSecureSSLCiphers(settings, false).toArray(new String[0])) + evalSecure( + engine.getEnabledCipherSuites(), + SSLConfigConstants.getSecureSSLCiphers(settings, CertType.TRANSPORT).toArray(new String[0]) + ) ); engine.setUseClientMode(true); return engine; } else { final SSLEngine engine = externalSslContext.createSSLEngine(); - engine.setEnabledProtocols(evalSecure(engine.getEnabledProtocols(), SSLConfigConstants.getSecureSSLProtocols(settings, false))); + engine.setEnabledProtocols( + evalSecure(engine.getEnabledProtocols(), SSLConfigConstants.getSecureSSLProtocols(settings, CertType.TRANSPORT)) + ); engine.setEnabledCipherSuites( - evalSecure(engine.getEnabledCipherSuites(), SSLConfigConstants.getSecureSSLCiphers(settings, false).toArray(new String[0])) + evalSecure( + engine.getEnabledCipherSuites(), + SSLConfigConstants.getSecureSSLCiphers(settings, CertType.TRANSPORT).toArray(new String[0]) + ) ); engine.setUseClientMode(true); return engine; diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java index c228c27470..5a01cc553c 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Optional; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; @@ -35,6 +36,7 @@ import org.opensearch.security.ssl.http.netty.Netty4HttpRequestHeaderVerifier; import org.opensearch.security.ssl.transport.SSLConfig; import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.AuxTransport; import org.opensearch.transport.Transport; import org.opensearch.transport.TransportAdapterProvider; @@ -232,6 +234,33 @@ public Optional buildSecureHttpServerEngine(Settings settings, HttpSe @Override public Optional getSecureAuxTransportSettingsProvider(Settings settings) { - return Optional.empty(); + return Optional.of(new SecureAuxTransportSettingsProvider() { + + @Override + public Optional buildSecureAuxServerTransportContext(Settings settings, AuxTransport transport) { + CertType auxCertType = new CertType(transport.settingKey()); + return sslSettingsManager.sslContextHandler(auxCertType).map(SslContextHandler::tryFetchSSLContext); + } + + @Override + public Optional parameters(AuxTransport transport) { + return Optional.of(new SecureAuxTransportParameters() { + + @Override + public Optional clientAuth() { + CertType auxCertType = new CertType(transport.settingKey()); + return sslSettingsManager.sslConfiguration(auxCertType).map(config -> config.sslParameters().clientAuth().name()); + } + + @Override + public Collection cipherSuites() { + CertType auxCertType = new CertType(transport.settingKey()); + return sslSettingsManager.sslConfiguration(auxCertType) + .map(config -> config.sslParameters().allowedCiphers()) + .orElse(Collections.emptyList()); + } + }); + } + }); } } diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 7dea9441b3..6094cb5cb7 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -94,6 +94,17 @@ import io.netty.handler.ssl.OpenSsl; import io.netty.util.internal.PlatformDependent; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_CLIENTAUTH_MODE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_ENABLED_CIPHERS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_ENABLED_PROTOCOLS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMKEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH; + //For ES5 this class has only effect when SSL only plugin is installed public class OpenSearchSecuritySSLPlugin extends Plugin implements SystemIndexPlugin, NetworkPlugin { private static final Setting SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION = Setting.boolSetting( @@ -634,6 +645,24 @@ public List> getSettings() { ) ); + /** + * Expose aux transport settings. + */ + settings.addAll( + List.of( + SECURITY_SSL_AUX_ENABLED, + SECURITY_SSL_AUX_ENABLED_CIPHERS, + SECURITY_SSL_AUX_ENABLED_PROTOCOLS, + SECURITY_SSL_AUX_KEYSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMKEY_FILEPATH, + SECURITY_SSL_AUX_PEMKEY_PASSWORD, + SECURITY_SSL_AUX_PEMCERT_FILEPATH, + SECURITY_SSL_AUX_CLIENTAUTH_MODE, + SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH + ) + ); + return settings; } diff --git a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java index 1595ae1539..bec40e2c8f 100644 --- a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import org.apache.logging.log4j.LogManager; @@ -27,6 +28,7 @@ import org.opensearch.security.ssl.config.Certificate; import org.opensearch.transport.NettyAllocator; +import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.ssl.SslContext; import static java.util.function.Predicate.not; @@ -63,7 +65,22 @@ public SslConfiguration sslConfiguration() { return sslConfiguration; } - SslContext sslContext() { + /** + * Attempt to fetch the underlying io.netty.handler.ssl.SslContext as a javax SSLContext. + * As JDK is the only supported provider we expect sslContext is always of type JdkSslContext, + * allowing us to extract the javax.net.ssl.SSLContext delegate. Providing a javax SSLContext is + * desirable for dependencies which want to access security settings without taking on netty as a dependency. + * @return null if context cannot be fetched as JdkSslContext. + */ + public SSLContext tryFetchSSLContext() { + if (sslContext instanceof JdkSslContext) { + return ((JdkSslContext) sslContext).context(); + } + return null; + } + + // public for testing + public SslContext sslContext() { return sslContext; } diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index b5f3c8f2f4..2f68011050 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -27,17 +27,22 @@ import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; +import org.opensearch.common.collect.Tuple; +import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; import org.opensearch.env.Environment; import org.opensearch.security.ssl.config.CertType; +import org.opensearch.security.ssl.config.KeyStoreConfiguration; import org.opensearch.security.ssl.config.SslCertificatesLoader; import org.opensearch.security.ssl.config.SslParameters; +import org.opensearch.security.ssl.config.TrustStoreConfiguration; import org.opensearch.watcher.FileChangesListener; import org.opensearch.watcher.FileWatcher; import org.opensearch.watcher.ResourceWatcherService; import io.netty.handler.ssl.ClientAuth; +import static org.opensearch.security.ssl.config.CertType.CERT_TYPE_REGISTRY; import static org.opensearch.security.ssl.util.SSLConfigConstants.CLIENT_AUTH_MODE; import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED; import static org.opensearch.security.ssl.util.SSLConfigConstants.EXTENDED_KEY_USAGE_ENABLED; @@ -46,12 +51,8 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_CERT_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_KEY_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_TRUSTED_CAS_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_ENABLED; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_DEFAULT; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH; @@ -68,10 +69,12 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_AUX_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_CLIENT_EXTENDED_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_SERVER_EXTENDED_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.TRUSTSTORE_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.TRUSTSTORE_FILEPATH; +import static org.opensearch.transport.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; public class SslSettingsManager { @@ -87,24 +90,38 @@ public Optional sslConfiguration(final CertType certType) { return Optional.ofNullable(sslSettingsContexts.get(certType)).map(SslContextHandler::sslConfiguration); } - public Optional sslContextHandler(final CertType sslConfigPrefix) { - return Optional.ofNullable(sslSettingsContexts.get(sslConfigPrefix)); + public Optional sslContextHandler(final CertType certType) { + return Optional.ofNullable(sslSettingsContexts.get(certType)); } + /** + * Load and validate environment configuration for available CertTypes. + * @param environment settings and JDK environment. + */ private Map buildSslContexts(final Environment environment) { - final var contexts = new ImmutableMap.Builder(); - final var configurations = loadConfigurations(environment); - Optional.ofNullable(configurations.get(CertType.HTTP)) - .ifPresentOrElse( - sslConfiguration -> contexts.put(CertType.HTTP, new SslContextHandler(sslConfiguration)), - () -> LOGGER.warn("SSL Configuration for HTTP Layer hasn't been set") - ); - Optional.ofNullable(configurations.get(CertType.TRANSPORT)).ifPresentOrElse(sslConfiguration -> { - contexts.put(CertType.TRANSPORT, new SslContextHandler(sslConfiguration)); - final var transportClientConfiguration = Optional.ofNullable(configurations.get(CertType.TRANSPORT_CLIENT)) - .orElse(sslConfiguration); - contexts.put(CertType.TRANSPORT_CLIENT, new SslContextHandler(transportClientConfiguration, true)); - }, () -> LOGGER.warn("SSL Configuration for Transport Layer hasn't been set")); + final ImmutableMap.Builder contexts = new ImmutableMap.Builder<>(); + final Map configurations = loadConfigurations(environment); + configurations.forEach((cert, sslConfig) -> { + // TRANSPORT/TRANSPORT_CLIENT are exceptions. + if (cert == CertType.TRANSPORT) { + Optional.ofNullable(configurations.get(CertType.TRANSPORT)).ifPresentOrElse(sslConfiguration -> { + contexts.put(CertType.TRANSPORT, new SslContextHandler(sslConfiguration)); + final var transportClientConfiguration = Optional.ofNullable(configurations.get(CertType.TRANSPORT_CLIENT)) + .orElse(sslConfiguration); + contexts.put(CertType.TRANSPORT_CLIENT, new SslContextHandler(transportClientConfiguration, true)); + }, () -> LOGGER.warn("SSL Configuration for Transport Layer hasn't been set")); + return; + } else if (cert == CertType.TRANSPORT_CLIENT) { + return; // TRANSPORT_CLIENT is handled in TRANSPORT case. Skip. + } + + // Load all other configurations into SslContextHandlers. + Optional.ofNullable(configurations.get(cert)) + .ifPresentOrElse( + sslConfiguration -> contexts.put(cert, new SslContextHandler(sslConfiguration)), + () -> LOGGER.warn("SSL Configuration for {} Layer hasn't been set", cert.id()) + ); + }); return contexts.build(); } @@ -112,31 +129,54 @@ public synchronized void reloadSslContext(final CertType certType) { sslContextHandler(certType).ifPresentOrElse(sscContextHandler -> { try { if (sscContextHandler.reloadSslContext()) { - LOGGER.info("{} SSL context reloaded", certType.name()); + LOGGER.info("{} SSL context reloaded", certType.id()); } } catch (CertificateException e) { throw new OpenSearchException(e); } - }, () -> LOGGER.error("Missing SSL Context for {}", certType.name())); + }, () -> LOGGER.error("Missing SSL Context for {}", certType.id())); } private Map loadConfigurations(final Environment environment) { - final var settings = environment.settings(); - final var httpSettings = settings.getByPrefix(CertType.HTTP.sslConfigPrefix()); - final var transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()); - if (httpSettings.isEmpty() && transportSettings.isEmpty()) { + final Settings settings = environment.settings(); + final ImmutableMap.Builder configurationBuilder = ImmutableMap.builder(); + if (settings.getByPrefix(CertType.HTTP.sslSettingPrefix()).isEmpty() + && settings.getByPrefix(CertType.TRANSPORT.sslSettingPrefix()).isEmpty()) { throw new OpenSearchException("No SSL configuration found"); } jceWarnings(); - final var httpEnabled = httpSettings.getAsBoolean(ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); - final var transportEnabled = transportSettings.getAsBoolean(ENABLED, SECURITY_SSL_TRANSPORT_ENABLED_DEFAULT); + /* + * Fetch and load configurations for available aux transports. + * Registered all configured aux transports as new CertTypes. + */ + for (String auxType : AUX_TRANSPORT_TYPES_SETTING.get(environment.settings())) { + final CertType auxCert = new CertType(SSL_AUX_PREFIX + auxType + "."); + final Setting auxEnabled = SECURITY_SSL_AUX_ENABLED.getConcreteSettingForNamespace(auxType); + CERT_TYPE_REGISTRY.register(auxCert); + if (auxEnabled.get(settings) && !clientNode(settings)) { + validateSettings(auxCert, settings, false); + final SslParameters auxSslParameters = SslParameters.loader(auxCert, settings).load(); + final Tuple auxTrustAndKeyStore = new SslCertificatesLoader( + auxCert.sslSettingPrefix() + ).loadConfiguration(environment); + configurationBuilder.put( + auxCert, + new SslConfiguration(auxSslParameters, auxTrustAndKeyStore.v1(), auxTrustAndKeyStore.v2()) + ); + LOGGER.info("TLS {} Provider : {}", auxCert.id(), auxSslParameters.provider()); + LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.id(), auxSslParameters.allowedProtocols()); + } + } - final var configurationBuilder = ImmutableMap.builder(); + /* + * Load HTTP SslConfiguration. + */ + final boolean httpEnabled = settings.getAsBoolean(CertType.HTTP.sslSettingPrefix() + ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); if (httpEnabled && !clientNode(settings)) { - validateHttpSettings(httpSettings); - final var httpSslParameters = SslParameters.loader(httpSettings).load(true); - final var httpTrustAndKeyStore = new SslCertificatesLoader(CertType.HTTP.sslConfigPrefix()).loadConfiguration(environment); + validateSettings(CertType.HTTP, settings, SECURITY_SSL_HTTP_ENABLED_DEFAULT); + final var httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); + final var httpTrustAndKeyStore = new SslCertificatesLoader(CertType.HTTP.sslSettingPrefix()).loadConfiguration(environment); configurationBuilder.put( CertType.HTTP, new SslConfiguration(httpSslParameters, httpTrustAndKeyStore.v1(), httpTrustAndKeyStore.v2()) @@ -144,12 +184,17 @@ private Map loadConfigurations(final Environment env LOGGER.info("TLS HTTP Provider : {}", httpSslParameters.provider()); LOGGER.info("Enabled TLS protocols for HTTP layer : {}", httpSslParameters.allowedProtocols()); } - final var transportSslParameters = SslParameters.loader(transportSettings).load(false); - if (transportEnabled) { + + /* + * Load transport layer SslConfigurations. + */ + final Settings transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslSettingPrefix()); + final SslParameters transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); + if (transportSettings.getAsBoolean(ENABLED, SECURITY_SSL_TRANSPORT_ENABLED_DEFAULT)) { if (hasExtendedKeyUsageEnabled(transportSettings)) { validateTransportSettings(transportSettings); final var transportServerTrustAndKeyStore = new SslCertificatesLoader( - CertType.TRANSPORT.sslConfigPrefix(), + CertType.TRANSPORT.sslSettingPrefix(), SSL_TRANSPORT_SERVER_EXTENDED_PREFIX ).loadConfiguration(environment); configurationBuilder.put( @@ -157,7 +202,7 @@ private Map loadConfigurations(final Environment env new SslConfiguration(transportSslParameters, transportServerTrustAndKeyStore.v1(), transportServerTrustAndKeyStore.v2()) ); final var transportClientTrustAndKeyStore = new SslCertificatesLoader( - CertType.TRANSPORT.sslConfigPrefix(), + CertType.TRANSPORT.sslSettingPrefix(), SSL_TRANSPORT_CLIENT_EXTENDED_PREFIX ).loadConfiguration(environment); configurationBuilder.put( @@ -166,7 +211,7 @@ private Map loadConfigurations(final Environment env ); } else { validateTransportSettings(transportSettings); - final var transportTrustAndKeyStore = new SslCertificatesLoader(CertType.TRANSPORT.sslConfigPrefix()).loadConfiguration( + final var transportTrustAndKeyStore = new SslCertificatesLoader(CertType.TRANSPORT.sslSettingPrefix()).loadConfiguration( environment ); configurationBuilder.put( @@ -229,38 +274,94 @@ private boolean clientNode(final Settings settings) { return !"node".equals(settings.get(OpenSearchSecuritySSLPlugin.CLIENT_TYPE)); } - private void validateHttpSettings(final Settings httpSettings) { - if (httpSettings == null) return; - if (!httpSettings.getAsBoolean(ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT)) return; + /** + * Validates configuration of transport pem store/keystore for provided CertType. + * {@link org.opensearch.OpenSearchException} thrown on invalid config. + * @param certType cert type to validate. + * @param settings {@link org.opensearch.env.Environment} settings. + */ + private void validateSettings(final CertType certType, final Settings settings, final boolean enabled_default) { + final Settings certSettings = settings.getByPrefix(certType.sslSettingPrefix()); + if (certSettings.isEmpty()) return; + if (!certSettings.getAsBoolean(ENABLED, enabled_default)) return; + if (hasPemStoreSettings(certSettings)) { + validatePemStoreSettings(certType, settings); + } else if (hasKeyOrTrustStoreSettings(certSettings)) { + validateKeyStoreSettings(certType, settings); + } else { + throw new OpenSearchException( + "Wrong " + + certType.id() + + " SSL configuration. One of Keystore and Truststore files or X.509 PEM certificates and " + + "PKCS#8 keys groups should be set to configure " + + certType.id() + + " layer" + ); + } + } - final var clientAuth = ClientAuth.valueOf(httpSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT)); + /** + * Validate pem store settings for transport of given type. + * Throws an {@link org.opensearch.OpenSearchException} if: + * - Either of the pem certificate or pem private key paths are not set. + * - Client auth is set to REQUIRE but pem trusted certificates filepath is not set. + * @param transportType transport type to validate + * @param settings {@link org.opensearch.env.Environment} settings. + */ + private void validatePemStoreSettings(CertType transportType, final Settings settings) throws OpenSearchException { + final var transportSettings = settings.getByPrefix(transportType.sslSettingPrefix()); + final var clientAuth = ClientAuth.valueOf( + transportSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT) + ); + if (!transportSettings.hasValue(PEM_CERT_FILEPATH) || !transportSettings.hasValue(PEM_KEY_FILEPATH)) { + throw new OpenSearchException( + "Wrong " + + transportType.id().toLowerCase(Locale.ROOT) + + " SSL configuration. " + + String.join(", ", transportSettings.get(PEM_CERT_FILEPATH), transportSettings.get(PEM_KEY_FILEPATH)) + + " must be set" + ); + } + if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(PEM_TRUSTED_CAS_FILEPATH)) { + throw new OpenSearchException( + "Wrong " + + transportType.id().toLowerCase(Locale.ROOT) + + " SSL configuration. " + + PEM_TRUSTED_CAS_FILEPATH + + " must be set if client auth is required" + ); + } + } - if (hasPemStoreSettings(httpSettings)) { - if (!httpSettings.hasValue(PEM_CERT_FILEPATH) || !httpSettings.hasValue(PEM_KEY_FILEPATH)) { - throw new OpenSearchException( - "Wrong HTTP SSL configuration. " - + String.join(", ", SECURITY_SSL_HTTP_PEMCERT_FILEPATH, SECURITY_SSL_HTTP_PEMKEY_FILEPATH) - + " must be set" - ); - } - if (clientAuth == ClientAuth.REQUIRE && !httpSettings.hasValue(PEM_TRUSTED_CAS_FILEPATH)) { - throw new OpenSearchException( - "Wrong HTTP SSL configuration. " + SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH + " must be set if client auth is required" - ); - } - } else if (hasKeyOrTrustStoreSettings(httpSettings)) { - if (!httpSettings.hasValue(KEYSTORE_FILEPATH)) { - throw new OpenSearchException("Wrong HTTP SSL configuration. " + SECURITY_SSL_HTTP_KEYSTORE_FILEPATH + " must be set"); - } - if (clientAuth == ClientAuth.REQUIRE && !httpSettings.hasValue(TRUSTSTORE_FILEPATH)) { - throw new OpenSearchException( - "Wrong HTTP SSL configuration. " + SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH + " must be set if client auth is required" - ); - } - } else { + /** + * Validate key store settings for transport of given type. + * Throws an {@link org.opensearch.OpenSearchException} if: + * - Keystore filepath is not set. + * - Client auth is set to REQUIRE but trust store filepath is not set. + * @param transportType transport type to validate + * @param settings {@link org.opensearch.env.Environment} settings. + */ + private void validateKeyStoreSettings(CertType transportType, final Settings settings) throws OpenSearchException { + final var transportSettings = settings.getByPrefix(transportType.sslSettingPrefix()); + final var clientAuth = ClientAuth.valueOf( + transportSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT) + ); + if (!transportSettings.hasValue(KEYSTORE_FILEPATH)) { throw new OpenSearchException( - "Wrong HTTP SSL configuration. One of Keystore and Truststore files or X.509 PEM certificates and " - + "PKCS#8 keys groups should be set to configure HTTP layer" + "Wrong " + + transportType.id().toLowerCase(Locale.ROOT) + + " SSL configuration. " + + transportSettings.get(KEYSTORE_FILEPATH) + + " must be set" + ); + } + if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(TRUSTSTORE_FILEPATH)) { + throw new OpenSearchException( + "Wrong " + + transportType.id().toLowerCase(Locale.ROOT) + + " SSL configuration. " + + TRUSTSTORE_FILEPATH + + " must be set if client auth is required" ); } } @@ -386,5 +487,4 @@ void jceWarnings() { LOGGER.error("AES encryption not supported (SG 1). ", e); } } - } diff --git a/src/main/java/org/opensearch/security/ssl/config/CertType.java b/src/main/java/org/opensearch/security/ssl/config/CertType.java index 0c7a698ede..3eaf529e39 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -11,32 +11,142 @@ package org.opensearch.security.ssl.config; -import java.util.Arrays; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_HTTP_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_CLIENT_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_SERVER_PREFIX; + +/** + * CertTypes identify the setting prefix under which configuration settings for a set of certificates + * are located as well as the id which uniquely identifies a certificate type to the end user. + * CertTypes have a 1-to-1 relationship with ssl contexts and are registered in the global + * CERT_TYPE_REGISTRY but default for mandatory transports, or dynamically for pluggable auxiliary transports. + */ +public class CertType implements Writeable { + private final String certSettingPrefix; + private final String certID; -public enum CertType { - HTTP(SSL_HTTP_PREFIX), - TRANSPORT(SSL_TRANSPORT_PREFIX), - TRANSPORT_CLIENT(SSL_TRANSPORT_CLIENT_PREFIX); + /** + * In most cases the certID is the last element of the setting prefix. + * We expect this to be the case for all auxiliary transports. + * Exceptions where this pattern does not hold include: + * "plugins.security.ssl.transport.server." + * "plugins.security.ssl.transport.client." + * Where users identify these certificates respectively as: + * "transport_server" & "transport_client" + */ + public CertType(String certSettingPrefix) { + this.certSettingPrefix = certSettingPrefix; + String[] parts = certSettingPrefix.split("\\."); + this.certID = parts[parts.length - 1].toLowerCase(Locale.ROOT); + } - public static Set TYPES = Arrays.stream(CertType.values()) - .map(CertType::name) - .map(String::toLowerCase) - .collect(Collectors.toSet()); + public CertType(String certSettingPrefix, String certID) { + this.certSettingPrefix = certSettingPrefix; + this.certID = certID; + } - private final String sslConfigPrefix; + public CertType(final StreamInput in) throws IOException { + this.certSettingPrefix = in.readString(); + this.certID = in.readString(); + } - private CertType(String sslConfigPrefix) { - this.sslConfigPrefix = sslConfigPrefix; + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(this.certSettingPrefix); + out.writeString(this.certID); } - public String sslConfigPrefix() { - return sslConfigPrefix; + public String sslSettingPrefix() { + return certSettingPrefix; + } + + public String id() { + return this.certID; + } + + @Override + public String toString() { + return this.id(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CertType certType = (CertType) o; + return this.id().equals(certType.id()); + } + + @Override + public int hashCode() { + return Objects.hash(this.id()); + } + + /* + Write only set for tracking certificate types discovered and registered on a node. + Not all ssl context configurations are known at compile time, so we track newly discovered CertTypes here. + */ + public static class NodeCertTypeRegistry implements Iterable { + private final Set RegisteredCertType = new HashSet<>(); + + public NodeCertTypeRegistry(CertType... initialCertTypes) { + for (CertType certType : initialCertTypes) { + register(certType); + } + } + + public void register(CertType certType) { + if (RegisteredCertType.contains(certType)) { + throw new IllegalArgumentException("Cert type " + certType + " is already registered in CertType registry"); + } + RegisteredCertType.add(certType); + } + + public boolean contains(CertType certType) { + return RegisteredCertType.contains(certType); + } + + public boolean contains(String certID) { + for (CertType certType : RegisteredCertType) { + if (Objects.equals(certType.id(), certID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(RegisteredCertType).iterator(); + } } + /* + Mandatory transports. + */ + public static CertType HTTP = new CertType(SSL_HTTP_PREFIX); + public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX); + public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX, "transport_client"); + public static CertType TRANSPORT_SERVER = new CertType(SSL_TRANSPORT_SERVER_PREFIX, "transport_server"); + public static final NodeCertTypeRegistry CERT_TYPE_REGISTRY = new NodeCertTypeRegistry( + HTTP, + TRANSPORT, + TRANSPORT_CLIENT, + TRANSPORT_SERVER + ); } diff --git a/src/main/java/org/opensearch/security/ssl/config/SslParameters.java b/src/main/java/org/opensearch/security/ssl/config/SslParameters.java index 7a1fa0d828..169de00ae3 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslParameters.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslParameters.java @@ -35,6 +35,7 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED_CIPHERS; import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED_PROTOCOLS; import static org.opensearch.security.ssl.util.SSLConfigConstants.ENFORCE_CERT_RELOAD_DN_VERIFICATION; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENTAUTH_MODE_DEFAULT; public class SslParameters { @@ -95,21 +96,22 @@ public int hashCode() { return Objects.hash(provider, ciphers, protocols); } - public static Loader loader(final Settings sslConfigSettings) { - return new Loader(sslConfigSettings); + public static Loader loader(final CertType certType, final Settings settings) { + return new Loader(certType, settings); } public static final class Loader { - private final static Logger LOGGER = LogManager.getLogger(SslParameters.class); + private final CertType certType; private final Settings sslConfigSettings; - public Loader(final Settings sslConfigSettings) { - this.sslConfigSettings = sslConfigSettings; + public Loader(final CertType certType, final Settings settings) { + this.certType = certType; + this.sslConfigSettings = settings.getByPrefix(certType.sslSettingPrefix()); } - private SslProvider provider(final Settings settings) { + private SslProvider provider() { return SslProvider.JDK; } @@ -117,7 +119,7 @@ private boolean validateCertDNsOnReload(final Settings settings) { return settings.getAsBoolean(ENFORCE_CERT_RELOAD_DN_VERIFICATION, true); } - private List protocols(final SslProvider provider, final Settings settings, boolean http) { + private List protocols(final Settings settings) { final var allowedProtocols = settings.getAsList(ENABLED_PROTOCOLS, List.of(ALLOWED_SSL_PROTOCOLS)); return jdkProtocols(allowedProtocols); } @@ -132,7 +134,7 @@ private List jdkProtocols(final List allowedSslProtocols) { } } - private List ciphers(final SslProvider provider, final Settings settings) { + private List ciphers(final Settings settings) { final var allowed = settings.getAsList(ENABLED_CIPHERS, List.of(ALLOWED_SSL_CIPHERS)); final Stream allowedCiphers; try { @@ -145,28 +147,31 @@ private List ciphers(final SslProvider provider, final Settings settings return allowedCiphers.sorted(String::compareTo).collect(Collectors.toList()); } - public SslParameters load(final boolean http) { - final var clientAuth = http - ? ClientAuth.valueOf(sslConfigSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT)) - : ClientAuth.REQUIRE; + public SslParameters load() { + ClientAuth clientAuth; + if (certType == CertType.TRANSPORT || certType == CertType.TRANSPORT_CLIENT) { + clientAuth = SECURITY_SSL_TRANSPORT_CLIENTAUTH_MODE_DEFAULT; + } else { + clientAuth = ClientAuth.valueOf( + sslConfigSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT) + ); + } - final var provider = provider(sslConfigSettings); + final var provider = provider(); final var sslParameters = new SslParameters( provider, clientAuth, - protocols(provider, sslConfigSettings, http), - ciphers(provider, sslConfigSettings), + protocols(sslConfigSettings), + ciphers(sslConfigSettings), validateCertDNsOnReload(sslConfigSettings) ); if (sslParameters.allowedProtocols().isEmpty()) { - throw new OpenSearchSecurityException("No ssl protocols for " + (http ? "HTTP" : "Transport") + " layer"); + throw new OpenSearchSecurityException("No ssl protocols for " + certType.id() + " layer"); } if (sslParameters.allowedCiphers().isEmpty()) { - throw new OpenSearchSecurityException("No valid cipher suites for " + (http ? "HTTP" : "Transport") + " layer"); + throw new OpenSearchSecurityException("No valid cipher suites for " + certType.id() + " layer"); } return sslParameters; } - } - } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java index 93a8be9df3..dd9b6de292 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -20,8 +20,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; +import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.config.CertType; + +import io.netty.handler.ssl.ClientAuth; public final class SSLConfigConstants { /** @@ -98,6 +103,77 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_HTTP_CRL_DISABLE_CRLDP = SSL_HTTP_CRL_PREFIX + "disable_crldp"; public static final String SECURITY_SSL_HTTP_CRL_VALIDATION_DATE = SSL_HTTP_CRL_PREFIX + "validation_date"; + /** + * Auxiliary transport security settings. + * Aux transport settings are affix settings with individual configurations identified by their AUX_TRANSPORT_TYPES_KEY. + */ + public static final String AUX_SETTINGS = "aux"; + public static final String SSL_AUX_PREFIX = SSL_PREFIX + AUX_SETTINGS + "."; + + // aux enable settings + public static final boolean SECURITY_SSL_AUX_ENABLED_DEFAULT = false; // aux transports are optional + public static final Setting.AffixSetting SECURITY_SSL_AUX_ENABLED = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.ENABLED, + key -> Setting.boolSetting(key, SECURITY_SSL_AUX_ENABLED_DEFAULT, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting> SECURITY_SSL_AUX_ENABLED_CIPHERS = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.ENABLED_CIPHERS, + key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope) + ); + public static final Setting.AffixSetting> SECURITY_SSL_AUX_ENABLED_PROTOCOLS = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.ENABLED_PROTOCOLS, + key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_FILEPATH = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.KEYSTORE_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_PEMKEY_FILEPATH = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_KEY_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_PEMKEY_PASSWORD = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_KEY_PASSWORD, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_PEMCERT_FILEPATH = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_CERT_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + // aux truststore settings + public static final Setting.AffixSetting SECURITY_SSL_AUX_CLIENTAUTH_MODE = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.CLIENT_AUTH_MODE, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + public static final Setting.AffixSetting SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_TRUSTED_CAS_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + // helper to resolve setting key for CertType namespace + public static String getStringAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { + return affix.getConcreteSettingForNamespace(certType.id()).getKey(); + } + + public static String getBoolAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { + return affix.getConcreteSettingForNamespace(certType.id()).getKey(); + } + /** * Transport layer (node-to-node) settings. * Transport layer acts both as client and server within the cluster. @@ -135,6 +211,12 @@ public final class SSLConfigConstants { + ENFORCE_CERT_RELOAD_DN_VERIFICATION; public static final String SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH = SSL_TRANSPORT_PREFIX + PEM_TRUSTED_CAS_FILEPATH; + /* + On the transport layer we enforce ClientAuth.REQUIRE. + This setting is fixed and not exposed to users. + */ + public static final ClientAuth SECURITY_SSL_TRANSPORT_CLIENTAUTH_MODE_DEFAULT = ClientAuth.REQUIRE; + // transport server keystore settings public static final String SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS = SSL_TRANSPORT_SERVER_PREFIX + KEYSTORE_ALIAS; public static final String SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH = SSL_TRANSPORT_SERVER_PREFIX + PEM_KEY_FILEPATH; @@ -164,21 +246,19 @@ public final class SSLConfigConstants { + "resolve_hostname"; public static final String SECURITY_SSL_CLIENT_EXTERNAL_CONTEXT_ID = SSL_PREFIX + "client.external_context_id"; - public static String[] getSecureSSLProtocols(Settings settings, boolean http) { - List configuredProtocols = null; - - if (settings != null) { - if (http) { - configuredProtocols = settings.getAsList(SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, Collections.emptyList()); - } else { - configuredProtocols = settings.getAsList(SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, Collections.emptyList()); - } + public static List getSecureSSLCiphers(Settings settings, CertType certType) { + List configuredCiphers = settings.getAsList(certType.sslSettingPrefix() + ENABLED_CIPHERS, Collections.emptyList()); + if (configuredCiphers != null && !configuredCiphers.isEmpty()) { + return configuredCiphers; } + return Collections.unmodifiableList(Arrays.asList(ALLOWED_SSL_CIPHERS)); + } - if (configuredProtocols != null && configuredProtocols.size() > 0) { + public static String[] getSecureSSLProtocols(Settings settings, CertType certType) { + List configuredProtocols = settings.getAsList(certType.sslSettingPrefix() + ENABLED_PROTOCOLS, Collections.emptyList()); + if (configuredProtocols != null && !configuredProtocols.isEmpty()) { return configuredProtocols.toArray(new String[0]); } - return ALLOWED_SSL_PROTOCOLS.clone(); } @@ -296,27 +376,5 @@ public static String[] getSecureSSLProtocols(Settings settings, boolean http) { }; // @formatter:on - public static List getSecureSSLCiphers(Settings settings, boolean http) { - - List configuredCiphers = null; - - if (settings != null) { - if (http) { - configuredCiphers = settings.getAsList(SECURITY_SSL_HTTP_ENABLED_CIPHERS, Collections.emptyList()); - } else { - configuredCiphers = settings.getAsList(SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, Collections.emptyList()); - } - } - - if (configuredCiphers != null && configuredCiphers.size() > 0) { - return configuredCiphers; - } - - return Collections.unmodifiableList(Arrays.asList(ALLOWED_SSL_CIPHERS)); - } - - private SSLConfigConstants() { - - } - + private SSLConfigConstants() {} } diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index 598655fbea..e09060b510 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -51,6 +51,7 @@ import org.opensearch.node.Node; import org.opensearch.node.PluginAwareNode; import org.opensearch.security.OpenSearchSecurityPlugin; +import org.opensearch.security.ssl.config.CertType; import org.opensearch.security.ssl.util.ExceptionUtils; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; @@ -796,7 +797,7 @@ public void testAvailCiphers() throws Exception { serverContext.init(null, null, null); final SSLEngine engine = serverContext.createSSLEngine(); final List jdkSupportedCiphers = new ArrayList<>(Arrays.asList(engine.getSupportedCipherSuites())); - jdkSupportedCiphers.retainAll(SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, false)); + jdkSupportedCiphers.retainAll(SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, CertType.TRANSPORT)); engine.setEnabledCipherSuites(jdkSupportedCiphers.toArray(new String[0])); final List jdkEnabledCiphers = Arrays.asList(engine.getEnabledCipherSuites()); @@ -808,11 +809,11 @@ public void testAvailCiphers() throws Exception { @Test public void testUnmodifieableCipherProtocolConfig() throws Exception { - SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, false)[0] = "bogus"; - assertThat(SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, false)[0], is("TLSv1.3")); + SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, CertType.TRANSPORT)[0] = "bogus"; + assertThat(SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, CertType.TRANSPORT)[0], is("TLSv1.3")); try { - SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, false).set(0, "bogus"); + SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, CertType.TRANSPORT).set(0, "bogus"); Assert.fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/src/test/java/org/opensearch/security/ssl/SslContextHandlerTest.java b/src/test/java/org/opensearch/security/ssl/SslContextHandlerTest.java index b0605672aa..1d31867d49 100644 --- a/src/test/java/org/opensearch/security/ssl/SslContextHandlerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslContextHandlerTest.java @@ -29,6 +29,7 @@ import org.bouncycastle.cert.X509CertificateHolder; import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.config.CertType; import org.opensearch.security.ssl.config.KeyStoreConfiguration; import org.opensearch.security.ssl.config.SslParameters; import org.opensearch.security.ssl.config.TrustStoreConfiguration; @@ -295,7 +296,7 @@ List shuffledSans(Extension currentSans) { // CS-ENFORCE-SINGLE SslContextHandler sslContextHandler() { - final var sslParameters = SslParameters.loader(Settings.EMPTY).load(false); + final var sslParameters = SslParameters.loader(CertType.TRANSPORT, Settings.EMPTY).load(); final var trustStoreConfiguration = new TrustStoreConfiguration.PemTrustStoreConfiguration(caCertificatePath); final var keyStoreConfiguration = new KeyStoreConfiguration.PemKeyStoreConfiguration( accessCertificatePath, diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java index 64308d0abb..8f02f16613 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java @@ -18,6 +18,8 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import com.carrotsearch.randomizedtesting.RandomizedTest; @@ -40,24 +42,17 @@ import static org.opensearch.security.ssl.CertificatesUtils.privateKeyToPemObject; import static org.opensearch.security.ssl.CertificatesUtils.writePemContent; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_TYPE; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_CERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_KEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.PEM_TRUSTED_CAS_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_HTTP_PREFIX; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_AUX_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.TRUSTSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.TRUSTSTORE_TYPE; +import static org.opensearch.transport.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; public class SslSettingsManagerReloadListenerTest extends RandomizedTest { @@ -68,6 +63,9 @@ public class SslSettingsManagerReloadListenerTest extends RandomizedTest { ResourceWatcherService resourceWatcherService; + private static final String MOCK_AUX_PREFIX_FOO = SSL_AUX_PREFIX + "foo."; + private static final CertType MOCK_AUX_CERT_TYPE_FOO = new CertType(MOCK_AUX_PREFIX_FOO); + @FunctionalInterface interface CertificatesWriter { void write( @@ -101,54 +99,67 @@ public void cleanUp() { } @Test - public void reloadsSslContextOnPemFilesChanged() throws Exception { - final var securitySettings = new MockSecureSettings(); - securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); - securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); - reloadSslContextOnFilesChanged( - defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) - .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, path("http_ca_certificate.pem")) - .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, path("http_access_certificate.pem")) - .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, path("http_access_certificate_pk.pem")) - .put(SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, path("transport_ca_certificate.pem")) - .put(SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, path("transport_access_certificate.pem")) - .put(SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, path("transport_access_certificate_pk.pem")) - .setSecureSettings(securitySettings) - .build(), - (filePrefix, caCertificate, accessKeyAndCertificate) -> { - writePemContent(path(String.format("%s_ca_certificate.pem", filePrefix)), caCertificate); - writePemContent(path(String.format("%s_access_certificate.pem", filePrefix)), accessKeyAndCertificate.v2()); - writePemContent( - path(String.format("%s_access_certificate_pk.pem", filePrefix)), - privateKeyToPemObject(accessKeyAndCertificate.v1(), certificatesRule.privateKeyPassword()) - ); - } - ); + public void testReloadsSslContextOnPemStoreFilesChangedForHttp() throws Exception { + reloadSslContextOnPemFilesChangedForTransportType(CertType.HTTP, defaultSettingsBuilder()); } @Test - public void reloadsSslContextOnJdkStoreFilesChanged() throws Exception { + public void testReloadsSslContextOnPemStoreFilesChangedForAux() throws Exception { + Settings.Builder settings = defaultSettingsBuilder().putList( + AUX_TRANSPORT_TYPES_SETTING.getKey(), + List.of(MOCK_AUX_CERT_TYPE_FOO.id()) + ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); + reloadSslContextOnPemFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); + } + + @Test + public void testReloadsSslContextOnPemStoreFilesChangedForTransport() throws Exception { + reloadSslContextOnPemFilesChangedForTransportType(CertType.TRANSPORT, defaultSettingsBuilder()); + } + + @Test + public void testReloadsSslContextOnJdkStoreFilesChangedForHttp() throws Exception { + reloadSslContextOnJdkStoreFilesChangedForTransportType(CertType.HTTP, defaultSettingsBuilder()); + } + + @Test + public void testReloadsSslContextOnJdkStoreFilesChangedForAux() throws Exception { + Settings.Builder settings = defaultSettingsBuilder().putList( + AUX_TRANSPORT_TYPES_SETTING.getKey(), + List.of(MOCK_AUX_CERT_TYPE_FOO.id()) + ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); + reloadSslContextOnJdkStoreFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); + } + + @Test + public void testReloadsSslContextOnJdkStoreFilesChangedForTransport() throws Exception { + reloadSslContextOnJdkStoreFilesChangedForTransportType(CertType.TRANSPORT, defaultSettingsBuilder()); + } + + private void reloadSslContextOnJdkStoreFilesChangedForTransportType(CertType certType, Settings.Builder settings) throws Exception { + final String settingPrefix = certType.sslSettingPrefix(); + final String enabledSetting = settingPrefix + ENABLED; + final String trustStorePathSetting = settingPrefix + TRUSTSTORE_FILEPATH; + final String trustStoreTypeSetting = settingPrefix + TRUSTSTORE_TYPE; + final String keyStorePathSetting = settingPrefix + KEYSTORE_FILEPATH; + final String keyStoreTypeSetting = settingPrefix + KEYSTORE_TYPE; + final String certTypeFilePrefix = certType.id().toLowerCase(Locale.ROOT); final var keyStorePassword = randomAsciiAlphanumOfLength(10); final var secureSettings = new MockSecureSettings(); - secureSettings.setString(SSL_HTTP_PREFIX + "truststore_password_secure", keyStorePassword); - secureSettings.setString(SSL_HTTP_PREFIX + "keystore_password_secure", keyStorePassword); - secureSettings.setString(SSL_HTTP_PREFIX + "keystore_keypassword_secure", certificatesRule.privateKeyPassword()); - - secureSettings.setString(SSL_TRANSPORT_PREFIX + "truststore_password_secure", keyStorePassword); - secureSettings.setString(SSL_TRANSPORT_PREFIX + "keystore_password_secure", keyStorePassword); - secureSettings.setString(SSL_TRANSPORT_PREFIX + "keystore_keypassword_secure", certificatesRule.privateKeyPassword()); + secureSettings.setString(settingPrefix + "truststore_password_secure", keyStorePassword); + secureSettings.setString(settingPrefix + "keystore_password_secure", keyStorePassword); + secureSettings.setString(settingPrefix + "keystore_keypassword_secure", certificatesRule.privateKeyPassword()); reloadSslContextOnFilesChanged( - defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) - .put(SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, path("http_truststore.jks")) - .put(SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, "jks") - .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, path("http_keystore.p12")) - .put(SECURITY_SSL_HTTP_KEYSTORE_TYPE, "pkcs12") - .put(SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, path("transport_truststore.jks")) - .put(SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE, "jks") - .put(SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, path("transport_keystore.p12")) - .put(SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "pkcs12") + certType, + settings + // Disable transport layer to test server transports independently. + // If certType is TRANSPORT the following line will re-enable it. + .put(SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(enabledSetting, true) + .put(trustStorePathSetting, path(certTypeFilePrefix + "_truststore.jks")) + .put(trustStoreTypeSetting, "jks") + .put(keyStorePathSetting, path(certTypeFilePrefix + "_keystore.p12")) + .put(keyStoreTypeSetting, "pkcs12") .setSecureSettings(secureSettings) .build(), (filePrefix, caCertificate, accessKeyAndCertificate) -> { @@ -156,7 +167,6 @@ public void reloadsSslContextOnJdkStoreFilesChanged() throws Exception { trustStore.load(null, null); trustStore.setCertificateEntry("ca", certificatesRule.toX509Certificate(caCertificate)); writeStore(trustStore, path(String.format("%s_truststore.jks", filePrefix)), keyStorePassword); - final var keyStore = KeyStore.getInstance("pkcs12"); keyStore.load(null, null); keyStore.setKeyEntry( @@ -170,66 +180,66 @@ public void reloadsSslContextOnJdkStoreFilesChanged() throws Exception { ); } - void reloadSslContextOnFilesChanged(final Settings settings, final CertificatesWriter certificatesWriter) throws Exception { - final var defaultHttpCertificates = generateCertificates(); - final var defaultHttpKeyPair = defaultHttpCertificates.v1(); - final var httpCaCertificate = defaultHttpCertificates.v2().v1(); - final var httpAccessKeyAndCertificate = defaultHttpCertificates.v2().v2(); - - final var defaultTransportCertificates = generateCertificates(); - final var defaultTransportKeyPair = defaultTransportCertificates.v1(); - final var transportCaCertificate = defaultTransportCertificates.v2().v1(); - final var transportAccessKeyAndCertificate = defaultTransportCertificates.v2().v2(); - - final var reloadHttpCertificates = randomBoolean(); - - certificatesWriter.write("http", httpCaCertificate, httpAccessKeyAndCertificate); - certificatesWriter.write("transport", transportCaCertificate, transportAccessKeyAndCertificate); + private void reloadSslContextOnPemFilesChangedForTransportType(CertType certType, Settings.Builder settings) throws Exception { + final String settingPrefix = certType.sslSettingPrefix(); + final String enabledSetting = settingPrefix + ENABLED; + final String pemTrustCasPathSetting = settingPrefix + PEM_TRUSTED_CAS_FILEPATH; + final String pemCertPathSetting = settingPrefix + PEM_CERT_FILEPATH; + final String pemKeyPathSetting = settingPrefix + PEM_KEY_FILEPATH; + final String certTypeFilePrefix = certType.id().toLowerCase(Locale.ROOT); + MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString(settingPrefix + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + reloadSslContextOnFilesChanged( + certType, + settings + // Disable transport layer to test server transports independently. + // If certType is TRANSPORT the following line will re-enable it. + .put(SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(enabledSetting, true) + .put(pemTrustCasPathSetting, path(certTypeFilePrefix + "_ca_certificate.pem")) + .put(pemCertPathSetting, path(certTypeFilePrefix + "_access_certificate.pem")) + .put(pemKeyPathSetting, path(certTypeFilePrefix + "_access_certificate_pk.pem")) + .setSecureSettings(secureSettings) + .build(), + (filePrefix, caCertificate, accessKeyAndCertificate) -> { + writePemContent(path(String.format("%s_ca_certificate.pem", filePrefix)), caCertificate); + writePemContent(path(String.format("%s_access_certificate.pem", filePrefix)), accessKeyAndCertificate.v2()); + writePemContent( + path(String.format("%s_access_certificate_pk.pem", filePrefix)), + privateKeyToPemObject(accessKeyAndCertificate.v1(), certificatesRule.privateKeyPassword()) + ); + } + ); + } + private void reloadSslContextOnFilesChanged(CertType certType, final Settings settings, final CertificatesWriter certificatesWriter) + throws Exception { + final String certNamePrefix = certType.id().toLowerCase(Locale.ROOT); + final var defaultCertificates = generateCertificates(); + var defaultKeyPair = defaultCertificates.v1(); + var caCertificate = defaultCertificates.v2().v1(); + var accessKeyAndCertificate = defaultCertificates.v2().v2(); + certificatesWriter.write(certNamePrefix, caCertificate, accessKeyAndCertificate); final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settings)); sslSettingsManager.addSslConfigurationsChangeListener(resourceWatcherService); - - final var httpSslContextBefore = sslSettingsManager.sslContextHandler(CertType.HTTP).orElseThrow().sslContext(); - final var transportSslContextBefore = sslSettingsManager.sslContextHandler(CertType.TRANSPORT).orElseThrow().sslContext(); - final var transportClientSslContextBefore = sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) - .orElseThrow() - .sslContext(); - - final var filePrefix = reloadHttpCertificates ? "http" : "transport"; - final var keyPair = reloadHttpCertificates ? defaultHttpKeyPair : defaultTransportKeyPair; - var caCertificate = reloadHttpCertificates ? httpCaCertificate : transportCaCertificate; - var keyAndCertificate = reloadHttpCertificates ? httpAccessKeyAndCertificate : transportAccessKeyAndCertificate; - + final var sslContextBefore = sslSettingsManager.sslContextHandler(certType).orElseThrow().sslContext(); if (randomBoolean()) { caCertificate = certificatesRule.generateCaCertificate( - keyPair, + defaultKeyPair, caCertificate.getNotBefore().toInstant(), caCertificate.getNotAfter().toInstant().plus(365, ChronoUnit.DAYS) ); } else { - keyAndCertificate = certificatesRule.generateAccessCertificate( - keyPair, - keyAndCertificate.v2().getNotBefore().toInstant(), - keyAndCertificate.v2().getNotAfter().toInstant().plus(365, ChronoUnit.DAYS) + accessKeyAndCertificate = certificatesRule.generateAccessCertificate( + defaultKeyPair, + accessKeyAndCertificate.v2().getNotBefore().toInstant(), + accessKeyAndCertificate.v2().getNotAfter().toInstant().plus(365, ChronoUnit.DAYS) ); } - certificatesWriter.write(filePrefix, caCertificate, keyAndCertificate); + certificatesWriter.write(certNamePrefix, caCertificate, accessKeyAndCertificate); Awaitility.await("Wait for reloading SSL context").until(() -> { - final var httpSslContextAfter = sslSettingsManager.sslContextHandler(CertType.HTTP).orElseThrow().sslContext(); - final var transportSslContextAfter = sslSettingsManager.sslContextHandler(CertType.TRANSPORT).orElseThrow().sslContext(); - final var transportClientSslContextAfter = sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) - .orElseThrow() - .sslContext(); - - if (reloadHttpCertificates) { - return !httpSslContextAfter.equals(httpSslContextBefore) - && transportSslContextBefore.equals(transportSslContextAfter) - && transportClientSslContextBefore.equals(transportClientSslContextAfter); - } else { - return httpSslContextAfter.equals(httpSslContextBefore) - && !transportSslContextBefore.equals(transportSslContextAfter) - && !transportClientSslContextBefore.equals(transportClientSslContextAfter); - } + final var sslContextAfter = sslSettingsManager.sslContextHandler(certType).orElseThrow().sslContext(); + return !sslContextAfter.equals(sslContextBefore); }); } diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index 1aa2c47eb3..a5d9c8002a 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -33,6 +33,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.ssl.CertificatesUtils.privateKeyToPemObject; import static org.opensearch.security.ssl.CertificatesUtils.writePemContent; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_CLIENTAUTH_MODE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMKEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; @@ -52,11 +58,15 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_AUX_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_HTTP_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_CLIENT_EXTENDED_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_PREFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_SERVER_EXTENDED_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.getBoolAffixKeyForCertType; +import static org.opensearch.security.ssl.util.SSLConfigConstants.getStringAffixKeyForCertType; import static org.opensearch.security.support.ConfigConstants.SECURITY_SSL_ONLY; +import static org.opensearch.transport.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; import static org.junit.Assert.assertThrows; public class SslSettingsManagerTest extends RandomizedTest { @@ -64,10 +74,105 @@ public class SslSettingsManagerTest extends RandomizedTest { @ClassRule public static CertificatesRule certificatesRule = new CertificatesRule(); + /* + Settings for a mock aux transport - foo + */ + private static final String MOCK_AUX_PREFIX_FOO = SSL_AUX_PREFIX + "foo."; + private static final CertType MOCK_AUX_CERT_TYPE_FOO = new CertType(MOCK_AUX_PREFIX_FOO); + private static final Settings ENABLE_FOO_SETTINGS_BUILDER = Settings.builder() + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_FOO.id())) + .put(getBoolAffixKeyForCertType(SECURITY_SSL_AUX_ENABLED, MOCK_AUX_CERT_TYPE_FOO), true) + .build(); + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMTRUSTEDCAS_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH, + MOCK_AUX_CERT_TYPE_FOO + ); + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMCERT_FILEPATH, + MOCK_AUX_CERT_TYPE_FOO + ); + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMKEY_FILEPATH, + MOCK_AUX_CERT_TYPE_FOO + ); + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMTRUSTEDCAS_NAME = "ca_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate_pk.pem"; + + /* + Settings for a mock aux transport - bar + */ + private static final String MOCK_AUX_PREFIX_BAR = SSL_AUX_PREFIX + "bar."; + private static final CertType MOCK_AUX_CERT_TYPE_BAR = new CertType(MOCK_AUX_PREFIX_BAR); + private static final Settings ENABLE_BAR_SETTINGS_BUILDER = Settings.builder() + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.id())) + .put(getBoolAffixKeyForCertType(SECURITY_SSL_AUX_ENABLED, MOCK_AUX_CERT_TYPE_BAR), true) + .build(); + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMTRUSTEDCAS_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH, + MOCK_AUX_CERT_TYPE_BAR + ); + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMCERT_FILEPATH, + MOCK_AUX_CERT_TYPE_BAR + ); + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_FILEPATH = getStringAffixKeyForCertType( + SECURITY_SSL_AUX_PEMKEY_FILEPATH, + MOCK_AUX_CERT_TYPE_BAR + ); + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMTRUSTEDCAS_NAME = "ca_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate_pk.pem"; + + static Settings.Builder defaultSettingsBuilder() { + return Settings.builder() + .put(Environment.PATH_HOME_SETTING.getKey(), certificatesRule.configRootFolder().toString()) + .put("client.type", "node"); + } + + private void withTransportSslSettings(final Settings.Builder settingsBuilder) { + settingsBuilder.put(SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, path("ca_transport_certificate.pem")) + .put(SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, path("access_transport_certificate.pem")) + .put(SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, path("access_transport_certificate_pk.pem")); + } + + private void withHttpSslSettings(final Settings.Builder settingsBuilder) { + settingsBuilder.put(SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(SECURITY_SSL_HTTP_ENABLED, true) + .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, path("ca_http_certificate.pem")) + .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, path("access_http_certificate.pem")) + .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, path("access_http_certificate_pk.pem")); + } + + private void withAuxFooSslSettings(final Settings.Builder settingsBuilder) { + settingsBuilder.put(ENABLE_FOO_SETTINGS_BUILDER) + .put(MOCK_AUX_CERT_TYPE_FOO_PEMTRUSTEDCAS_FILEPATH, path(MOCK_AUX_CERT_TYPE_FOO_PEMTRUSTEDCAS_NAME)) + .put(MOCK_AUX_CERT_TYPE_FOO_PEMCERT_FILEPATH, path(MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME)) + .put(MOCK_AUX_CERT_TYPE_FOO_PEMKEY_FILEPATH, path(MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME)); + } + + private void withAuxBarSslSettings(final Settings.Builder settingsBuilder) { + settingsBuilder.put(ENABLE_BAR_SETTINGS_BUILDER) + .put(MOCK_AUX_CERT_TYPE_BAR_PEMTRUSTEDCAS_FILEPATH, path(MOCK_AUX_CERT_TYPE_BAR_PEMTRUSTEDCAS_NAME)) + .put(MOCK_AUX_CERT_TYPE_BAR_PEMCERT_FILEPATH, path(MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME)) + .put(MOCK_AUX_CERT_TYPE_BAR_PEMKEY_FILEPATH, path(MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME)); + } + @BeforeClass public static void setUp() throws Exception { writeCertificates("ca_http_certificate.pem", "access_http_certificate.pem", "access_http_certificate_pk.pem"); writeCertificates("ca_transport_certificate.pem", "access_transport_certificate.pem", "access_transport_certificate_pk.pem"); + writeCertificates( + "ca_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.id() + "_certificate_pk.pem" + ); + writeCertificates( + "ca_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.id() + "_certificate_pk.pem" + ); } static void writeCertificates(final String trustedFileName, final String accessFileName, final String accessPkFileName) @@ -85,28 +190,37 @@ static Path path(final String fileName) { } @Test - public void failsIfNoSslSet() throws Exception { + public void failsIfNoSslSet() { final var settings = defaultSettingsBuilder().build(); assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); } @Test - public void transportFailsIfNoConfigDefine() throws Exception { - final var noTransportSettings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true).build(); - assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(noTransportSettings))); - } - - @Test - public void transportFailsIfConfigEnabledButNotDefined() throws Exception { - final var noTransportSettingsButItEnabled = defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true).build(); + public void testFailsIfNoConfigDefine() { assertThrows( OpenSearchException.class, - () -> new SslSettingsManager(TestEnvironment.newEnvironment(noTransportSettingsButItEnabled)) + () -> new SslSettingsManager( + TestEnvironment.newEnvironment(defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true).build()) + ) + ); + assertThrows( + OpenSearchException.class, + () -> new SslSettingsManager( + TestEnvironment.newEnvironment(defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true).build()) + ) + ); + assertThrows( + OpenSearchException.class, + () -> new SslSettingsManager(TestEnvironment.newEnvironment(defaultSettingsBuilder().put(ENABLE_FOO_SETTINGS_BUILDER).build())) + ); + assertThrows( + OpenSearchException.class, + () -> new SslSettingsManager(TestEnvironment.newEnvironment(defaultSettingsBuilder().put(ENABLE_BAR_SETTINGS_BUILDER).build())) ); } @Test - public void transportFailsIfJdkTrustStoreHasNotBeenSet() throws Exception { + public void transportFailsIfJdkTrustStoreHasNotBeenSet() { final var noTransportSettingsButItEnabled = defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true) .put(SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, certificatesRule.configRootFolder().toString()) .build(); @@ -117,7 +231,7 @@ public void transportFailsIfJdkTrustStoreHasNotBeenSet() throws Exception { } @Test - public void transportFailsIfExtendedKeyUsageEnabledForJdkKeyStoreButNotConfigured() throws Exception { + public void transportFailsIfExtendedKeyUsageEnabledForJdkKeyStoreButNotConfigured() { final var noTransportSettingsButItEnabled = defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true) .put(SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, certificatesRule.configRootFolder().toString()) .put(SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, certificatesRule.configRootFolder().toString()) @@ -130,7 +244,7 @@ public void transportFailsIfExtendedKeyUsageEnabledForJdkKeyStoreButNotConfigure } @Test - public void transportFailsIfExtendedKeyUsageEnabledForPemKeyStoreButNotConfigured() throws Exception { + public void transportFailsIfExtendedKeyUsageEnabledForPemKeyStoreButNotConfigured() { final var noTransportSettingsButItEnabled = defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true) .put(SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, certificatesRule.configRootFolder().toString()) .put(SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, certificatesRule.configRootFolder().toString()) @@ -143,64 +257,124 @@ public void transportFailsIfExtendedKeyUsageEnabledForPemKeyStoreButNotConfigure } @Test - public void transportFailsIfConfigDisabled() throws Exception { + public void transportFailsIfConfigDisabled() { Settings settings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) .put(SECURITY_SSL_TRANSPORT_ENABLED, false) .build(); assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); } + /** + * Security plugin enforces common store type for a single transport configuration. + * Pem store and JKS (Java KeyStore) cannot both be used for transport. + */ + private void configFailsIfBothPemAndJDKSettingsWereSet( + Settings.Builder settingsBuilder, + List transportJKSSettings, + List transportPemStoreSettings + ) { + Settings settings = settingsBuilder.put(randomFrom(transportJKSSettings), "aaa") + .put(randomFrom(transportPemStoreSettings), "bbb") + .build(); + assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); + } + @Test - public void httpConfigFailsIfBothPemAndJDKSettingsWereSet() throws Exception { - final var keyStoreSettings = randomFrom(List.of(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH)); - final var pemKeyStoreSettings = randomFrom( + public void configFailsIfBothPemAndJDKSettingsWereSet() { + configFailsIfBothPemAndJDKSettingsWereSet( + defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true), + List.of(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH), List.of(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, SECURITY_SSL_HTTP_PEMCERT_FILEPATH, SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH) ); - final var settings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) - .put(keyStoreSettings, "aaa") - .put(pemKeyStoreSettings, "bbb") + configFailsIfBothPemAndJDKSettingsWereSet( + defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true), + List.of(SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), + List.of( + SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH + ) + ); + configFailsIfBothPemAndJDKSettingsWereSet( + defaultSettingsBuilder().put(ENABLE_FOO_SETTINGS_BUILDER), + List.of(getStringAffixKeyForCertType(SECURITY_SSL_AUX_KEYSTORE_FILEPATH, MOCK_AUX_CERT_TYPE_FOO)), + List.of( + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMKEY_FILEPATH, MOCK_AUX_CERT_TYPE_FOO), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMCERT_FILEPATH, MOCK_AUX_CERT_TYPE_FOO), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH, MOCK_AUX_CERT_TYPE_FOO) + ) + ); + } + + private void configFailsIfClientAuthRequiredAndJdkTrustStoreNotSet( + Settings.Builder settingsBuilder, + String clientAuthEnabledSetting, + String keystorePathSetting + ) { + Settings settings = settingsBuilder.put(clientAuthEnabledSetting, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .put(keystorePathSetting, certificatesRule.configRootFolder().toString()) .build(); assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); } @Test - public void httpConfigFailsIfHttpEnabledButButNotDefined() throws Exception { - final var settings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true).build(); - assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); + public void serverTransportConfigFailsIfClientAuthRequiredAndJdkTrustStoreNotSet() { + configFailsIfClientAuthRequiredAndJdkTrustStoreNotSet( + defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true), + SECURITY_SSL_HTTP_CLIENTAUTH_MODE, + SECURITY_SSL_HTTP_KEYSTORE_FILEPATH + ); + configFailsIfClientAuthRequiredAndJdkTrustStoreNotSet( + defaultSettingsBuilder().put(ENABLE_FOO_SETTINGS_BUILDER), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_CLIENTAUTH_MODE, MOCK_AUX_CERT_TYPE_FOO), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_KEYSTORE_FILEPATH, MOCK_AUX_CERT_TYPE_FOO) + ); } - @Test - public void httpConfigFailsIfClientAuthRequiredAndJdkTrustStoreNotSet() throws Exception { - final var settings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) - .put(SECURITY_SSL_HTTP_CLIENTAUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) - .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, certificatesRule.configRootFolder().toString()) + private void configFailsIfClientAuthRequiredAndPemTrustedCasNotSet( + Settings.Builder settingsBuilder, + String clientAuthEnabledSetting, + String pemkeyPathSetting, + String pemcertPathSetting + ) { + Settings settings = settingsBuilder.put(clientAuthEnabledSetting, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .put(pemkeyPathSetting, "aaa") + .put(pemcertPathSetting, "bbb") .build(); assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); } @Test - public void httpConfigFailsIfClientAuthRequiredAndPemTrustedCasNotSet() throws Exception { - final var settings = defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) - .put(SECURITY_SSL_HTTP_CLIENTAUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) - .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, "aaa") - .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, "bbb") - .build(); - assertThrows(OpenSearchException.class, () -> new SslSettingsManager(TestEnvironment.newEnvironment(settings))); + public void serverTransportConfigFailsIfClientAuthRequiredAndPemTrustedCasNotSet() { + configFailsIfClientAuthRequiredAndPemTrustedCasNotSet( + defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true), + SECURITY_SSL_HTTP_CLIENTAUTH_MODE, + SECURITY_SSL_HTTP_PEMKEY_FILEPATH, + SECURITY_SSL_HTTP_PEMCERT_FILEPATH + ); + configFailsIfClientAuthRequiredAndPemTrustedCasNotSet( + defaultSettingsBuilder().put(ENABLE_FOO_SETTINGS_BUILDER), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_CLIENTAUTH_MODE, MOCK_AUX_CERT_TYPE_FOO), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMKEY_FILEPATH, MOCK_AUX_CERT_TYPE_FOO), + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMCERT_FILEPATH, MOCK_AUX_CERT_TYPE_FOO) + ); } @Test - public void loadConfigurationAndBuildHSslContextForSslOnlyMode() throws Exception { + public void loadConfigurationAndBuildSslContextForSslOnlyMode() { final var securitySettings = new MockSecureSettings(); securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); - final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); - withTransportSslSettings( - settingsBuilder, - "ca_transport_certificate.pem", - "access_transport_certificate.pem", - "access_transport_certificate_pk.pem" + securitySettings.setString( + MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "pemkey_password_secure", + certificatesRule.privateKeyPassword() ); + + final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); + withTransportSslSettings(settingsBuilder); withHttpSslSettings(settingsBuilder); + withAuxFooSslSettings(settingsBuilder); + final var transportEnabled = randomBoolean(); final var sslSettingsManager = new SslSettingsManager( TestEnvironment.newEnvironment( @@ -209,6 +383,7 @@ public void loadConfigurationAndBuildHSslContextForSslOnlyMode() throws Exceptio ); assertThat("Loaded HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isPresent()); + assertThat("Loaded AUX FOO configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_FOO).isPresent()); if (transportEnabled) { assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); @@ -219,8 +394,8 @@ public void loadConfigurationAndBuildHSslContextForSslOnlyMode() throws Exceptio sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isEmpty() ); } - assertThat("Built HTTP SSL Context", sslSettingsManager.sslContextHandler(CertType.HTTP).isPresent()); + assertThat("Built AUX FOO SSL Context", sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO).isPresent()); if (transportEnabled) { assertThat("Built Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isPresent()); assertThat("Built Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isPresent()); @@ -228,26 +403,29 @@ public void loadConfigurationAndBuildHSslContextForSslOnlyMode() throws Exceptio assertThat("Didn't build Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isEmpty()); assertThat("Didn't build Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isEmpty()); } - assertThat( "Built Server SSL context for HTTP", sslSettingsManager.sslContextHandler(CertType.HTTP).map(SslContextHandler::sslContext).map(SslContext::isServer).orElse(false) ); + assertThat( + "Built Server SSL context for AUX FOO", + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) + ); } @Test - public void loadConfigurationAndBuildSslContextForClientNode() throws Exception { + public void loadConfigurationAndBuildSslContextForClientNode() { final var securitySettings = new MockSecureSettings(); securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); - withTransportSslSettings( - settingsBuilder, - "ca_transport_certificate.pem", - "access_transport_certificate.pem", - "access_transport_certificate_pk.pem" - ); + withTransportSslSettings(settingsBuilder); withHttpSslSettings(settingsBuilder); + final var sslSettingsManager = new SslSettingsManager( TestEnvironment.newEnvironment( settingsBuilder.put("client.type", "client").put(SECURITY_SSL_HTTP_ENABLED, randomBoolean()).build() @@ -257,11 +435,9 @@ public void loadConfigurationAndBuildSslContextForClientNode() throws Exception assertThat("Didn't load HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isEmpty()); assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); - assertThat("Didn't build HTTP SSL Context", sslSettingsManager.sslContextHandler(CertType.HTTP).isEmpty()); assertThat("Built Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isPresent()); assertThat("Built Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isPresent()); - assertThat( "Built Server SSL context for Transport", sslSettingsManager.sslContextHandler(CertType.TRANSPORT) @@ -280,31 +456,104 @@ public void loadConfigurationAndBuildSslContextForClientNode() throws Exception } @Test - public void loadConfigurationAndBuildSslContexts() throws Exception { + public void loadConfigurationAndBuildSslContextsMultipleAuxTransports() { final var securitySettings = new MockSecureSettings(); securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); - final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); - withTransportSslSettings( - settingsBuilder, - "ca_transport_certificate.pem", - "access_transport_certificate.pem", - "access_transport_certificate_pk.pem" + securitySettings.setString( + MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "pemkey_password_secure", + certificatesRule.privateKeyPassword() + ); + securitySettings.setString( + MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + "pemkey_password_secure", + certificatesRule.privateKeyPassword() ); + + final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); + withTransportSslSettings(settingsBuilder); withHttpSslSettings(settingsBuilder); + withAuxBarSslSettings(settingsBuilder); + withAuxFooSslSettings(settingsBuilder); + settingsBuilder.put( + Settings.builder() + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.id(), MOCK_AUX_CERT_TYPE_FOO.id())) + .build() + ); + final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settingsBuilder.build())); + assertThat("Loaded HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isPresent()); + assertThat("Loaded AUX FOO configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_FOO).isPresent()); + assertThat("Loaded AUX BAR configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_BAR).isPresent()); assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); + assertThat( + "Built Server SSL context for HTTP", + sslSettingsManager.sslContextHandler(CertType.HTTP).map(SslContextHandler::sslContext).map(SslContext::isServer).orElse(false) + ); + assertThat( + "Built Server SSL context for AUX", + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) + ); + assertThat( + "Built Server SSL context for AUX", + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_BAR) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) + ); + assertThat( + "Built Server SSL context for Transport", + sslSettingsManager.sslContextHandler(CertType.TRANSPORT) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) + ); + assertThat( + "Built Client SSL context for Transport Client", + sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) + .map(SslContextHandler::sslContext) + .map(SslContext::isClient) + .orElse(false) - assertThat("Built HTTP SSL Context", sslSettingsManager.sslContextHandler(CertType.HTTP).isPresent()); - assertThat("Built Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isPresent()); - assertThat("Built Transport Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isPresent()); + ); + } + @Test + public void loadConfigurationAndBuildSslContexts() { + final var securitySettings = new MockSecureSettings(); + securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + securitySettings.setString( + MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "pemkey_password_secure", + certificatesRule.privateKeyPassword() + ); + + final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); + withTransportSslSettings(settingsBuilder); + withHttpSslSettings(settingsBuilder); + withAuxFooSslSettings(settingsBuilder); + + final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settingsBuilder.build())); + + assertThat("Loaded HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isPresent()); + assertThat("Loaded AUX FOO configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_FOO).isPresent()); + assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); + assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); assertThat( "Built Server SSL context for HTTP", sslSettingsManager.sslContextHandler(CertType.HTTP).map(SslContextHandler::sslContext).map(SslContext::isServer).orElse(false) ); + assertThat( + "Built Server SSL context for AUX", + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) + ); assertThat( "Built Server SSL context for Transport", sslSettingsManager.sslContextHandler(CertType.TRANSPORT) @@ -323,19 +572,17 @@ public void loadConfigurationAndBuildSslContexts() throws Exception { } @Test - public void loadConfigurationAndBuildTransportSslContext() throws Exception { + public void loadConfigurationAndBuildTransportSslContext() { final var securitySettings = new MockSecureSettings(); securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); - withTransportSslSettings( - settingsBuilder, - "ca_transport_certificate.pem", - "access_transport_certificate.pem", - "access_transport_certificate_pk.pem" - ); + withTransportSslSettings(settingsBuilder); + final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settingsBuilder.build())); assertThat("Didn't load HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isEmpty()); + assertThat("Didn't load AUX configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_FOO).isEmpty()); assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); assertThat( @@ -344,11 +591,10 @@ public void loadConfigurationAndBuildTransportSslContext() throws Exception { .flatMap(t -> sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).map(tc -> tc.equals(t))) .orElse(false) ); - assertThat("Built HTTP SSL Context", sslSettingsManager.sslContextHandler(CertType.HTTP).isEmpty()); + assertThat("Built AUX SSL Context", sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO).isEmpty()); assertThat("Built Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isPresent()); assertThat("Built Transport Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isPresent()); - assertThat( "Built Server SSL context for Transport", sslSettingsManager.sslContextHandler(CertType.TRANSPORT) @@ -389,6 +635,7 @@ public void loadConfigurationAndBuildExtendedTransportSslContexts() throws Excep SSL_TRANSPORT_PREFIX + SSL_TRANSPORT_CLIENT_EXTENDED_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword() ); + final var sslSettingsManager = new SslSettingsManager( TestEnvironment.newEnvironment( defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true) @@ -405,6 +652,7 @@ public void loadConfigurationAndBuildExtendedTransportSslContexts() throws Excep ); assertThat("Didn't load HTTP configuration", sslSettingsManager.sslConfiguration(CertType.HTTP).isEmpty()); + assertThat("Didn't load AUX configuration", sslSettingsManager.sslConfiguration(MOCK_AUX_CERT_TYPE_FOO).isEmpty()); assertThat("Loaded Transport configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT).isPresent()); assertThat("Loaded Transport Client configuration", sslSettingsManager.sslConfiguration(CertType.TRANSPORT_CLIENT).isPresent()); assertThat( @@ -414,9 +662,9 @@ public void loadConfigurationAndBuildExtendedTransportSslContexts() throws Excep .orElse(true) ); assertThat("Built HTTP SSL Context", sslSettingsManager.sslContextHandler(CertType.HTTP).isEmpty()); + assertThat("Built AUX SSL Context", sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO).isEmpty()); assertThat("Built Transport SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT).isPresent()); assertThat("Built Transport Client SSL Context", sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT).isPresent()); - assertThat( "Built Server SSL context for Transport", sslSettingsManager.sslContextHandler(CertType.TRANSPORT) @@ -434,31 +682,4 @@ public void loadConfigurationAndBuildExtendedTransportSslContexts() throws Excep ); } - - private void withTransportSslSettings( - final Settings.Builder settingsBuilder, - final String caFileName, - final String accessFileName, - final String accessPkFileName - ) { - settingsBuilder.put(SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, path(caFileName)) - .put(SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, path(accessFileName)) - .put(SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, path(accessPkFileName)); - } - - private void withHttpSslSettings(final Settings.Builder settingsBuilder) { - settingsBuilder.put(SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SECURITY_SSL_HTTP_ENABLED, true) - .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, path("ca_http_certificate.pem")) - .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, path("access_http_certificate.pem")) - .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, path("access_http_certificate_pk.pem")); - } - - Settings.Builder defaultSettingsBuilder() { - return Settings.builder() - .put(Environment.PATH_HOME_SETTING.getKey(), certificatesRule.configRootFolder().toString()) - .put("client.type", "node"); - } - } diff --git a/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java index d95c336e15..fac8c9c255 100644 --- a/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java @@ -11,14 +11,17 @@ package org.opensearch.security.ssl.config; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.net.ssl.SSLContext; +import org.junit.Before; import org.junit.Test; +import org.opensearch.OpenSearchSecurityException; import org.opensearch.common.settings.Settings; import io.netty.handler.ssl.ClientAuth; @@ -27,64 +30,162 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.ssl.util.SSLConfigConstants.ALLOWED_SSL_CIPHERS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.CLIENT_AUTH_MODE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED_CIPHERS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED_PROTOCOLS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_HTTP_PREFIX; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_AUX_PREFIX; +import static org.junit.Assert.assertThrows; public class SslParametersTest { - @Test - public void testDefaultSslParameters() throws Exception { - final var settings = Settings.EMPTY; - final var httpSslParameters = SslParameters.loader(settings).load(true); - final var transportSslParameters = SslParameters.loader(settings).load(false); + private List finalDefaultCiphers; + + private static final String MOCK_AUX_PREFIX_FOO = SSL_AUX_PREFIX + "foo."; + private static final String MOCK_AUX_PREFIX_BAR = SSL_AUX_PREFIX + "bar."; + private static final CertType MOCK_AUX_CERT_TYPE_FOO = new CertType(MOCK_AUX_PREFIX_FOO); + private static final CertType MOCK_AUX_CERT_TYPE_BAR = new CertType(MOCK_AUX_PREFIX_BAR); + @Before + public void setup() throws NoSuchAlgorithmException { final var defaultCiphers = List.of(ALLOWED_SSL_CIPHERS); - final var finalDefaultCiphers = Stream.of(SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites()) + finalDefaultCiphers = Stream.of(SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites()) .filter(defaultCiphers::contains) .sorted(String::compareTo) .collect(Collectors.toList()); + } + @Test + public void testDefaultSslParametersForHttp() { + final var httpSslParameters = SslParameters.loader(CertType.HTTP, Settings.EMPTY).load(); assertThat(httpSslParameters.provider(), is(SslProvider.JDK)); - assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); - assertThat(httpSslParameters.allowedProtocols(), is(List.of("TLSv1.3", "TLSv1.2"))); assertThat(httpSslParameters.allowedCiphers(), is(finalDefaultCiphers)); + assertThat(httpSslParameters.clientAuth(), is(ClientAuth.OPTIONAL)); + } + + @Test + public void testDefaultSslParametersForAux() { + final var auxSslParameters = SslParameters.loader(MOCK_AUX_CERT_TYPE_FOO, Settings.EMPTY).load(); + assertThat(auxSslParameters.provider(), is(SslProvider.JDK)); + assertThat(auxSslParameters.allowedProtocols(), is(List.of("TLSv1.3", "TLSv1.2"))); + assertThat(auxSslParameters.allowedCiphers(), is(finalDefaultCiphers)); + assertThat(auxSslParameters.clientAuth(), is(ClientAuth.OPTIONAL)); + } + @Test + public void testDefaultSslParametersForTransport() { + final var transportSslParameters = SslParameters.loader(CertType.TRANSPORT, Settings.EMPTY).load(); + assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); assertThat(transportSslParameters.allowedProtocols(), is(List.of("TLSv1.3", "TLSv1.2"))); assertThat(transportSslParameters.allowedCiphers(), is(finalDefaultCiphers)); + assertThat(transportSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + } - assertThat(httpSslParameters.clientAuth(), is(ClientAuth.OPTIONAL)); + @Test + public void testCustomSSlParametersForHttp() { + final Settings settings = Settings.builder() + .put(SECURITY_SSL_HTTP_CLIENTAUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, List.of("TLSv1.2", "TLSv1")) + .putList(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) + .build(); + final SslParameters httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); + assertThat(httpSslParameters.provider(), is(SslProvider.JDK)); + assertThat(httpSslParameters.allowedProtocols(), is(List.of("TLSv1.2"))); + assertThat(httpSslParameters.allowedCiphers(), is(List.of("TLS_AES_256_GCM_SHA384"))); + assertThat(httpSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + } + + @Test + public void testCustomSSlParametersForAux() { + final Settings settings = Settings.builder() + .put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + CLIENT_AUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED_PROTOCOLS, List.of("TLSv1.2", "TLSv1")) + .putList(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) + .build(); + final SslParameters auxSslParameters = SslParameters.loader(MOCK_AUX_CERT_TYPE_FOO, settings).load(); + assertThat(auxSslParameters.provider(), is(SslProvider.JDK)); + assertThat(auxSslParameters.allowedProtocols(), is(List.of("TLSv1.2"))); + assertThat(auxSslParameters.allowedCiphers(), is(List.of("TLS_AES_256_GCM_SHA384"))); + assertThat(auxSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + } + + @Test + public void testCustomSSlParametersForMultiAux() { + final Settings settings = Settings.builder() + .put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + CLIENT_AUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED_PROTOCOLS, List.of("TLSv1.2", "TLSv1")) + .putList(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) + .put(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + CLIENT_AUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_PROTOCOLS, List.of("TLSv1.3")) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_CIPHERS, List.of("TLS_AES_128_GCM_SHA256", "DNE")) + .build(); + final SslParameters fooSslParameters = SslParameters.loader(MOCK_AUX_CERT_TYPE_FOO, settings).load(); + assertThat(fooSslParameters.provider(), is(SslProvider.JDK)); + assertThat(fooSslParameters.allowedProtocols(), is(List.of("TLSv1.2"))); + assertThat(fooSslParameters.allowedCiphers(), is(List.of("TLS_AES_256_GCM_SHA384"))); + assertThat(fooSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + final SslParameters barSslParameters = SslParameters.loader(MOCK_AUX_CERT_TYPE_BAR, settings).load(); + assertThat(barSslParameters.provider(), is(SslProvider.JDK)); + assertThat(barSslParameters.allowedProtocols(), is(List.of("TLSv1.3"))); + assertThat(barSslParameters.allowedCiphers(), is(List.of("TLS_AES_128_GCM_SHA256"))); + assertThat(barSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + } + + @Test + public void testSSlParametersEmptyProtocolsFails() { + final Settings settings = Settings.builder() + .put(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + CLIENT_AUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_PROTOCOLS, List.of("TLSv1")) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) + .build(); + // Intersection of enabled protocols and allowed protocols is empty list. + assertThrows(OpenSearchSecurityException.class, () -> SslParameters.loader(MOCK_AUX_CERT_TYPE_BAR, settings).load()); + } + + @Test + public void testCustomSSlParametersForTransport() { + final Settings settings = Settings.builder() + .putList(SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, List.of("TLSv1.3", "TLSv1.2")) + .putList(SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, List.of("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384")) + .build(); + final SslParameters transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); + assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); + assertThat(transportSslParameters.allowedProtocols(), is(List.of("TLSv1.3", "TLSv1.2"))); + assertThat(transportSslParameters.allowedCiphers(), is(List.of("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"))); assertThat(transportSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); } @Test - public void testCustomSSlParameters() { + public void testCustomSSlParametersForHttpAndAuxAndTransport() { final var settings = Settings.builder() .put(SECURITY_SSL_HTTP_CLIENTAUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) .putList(SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, List.of("TLSv1.2", "TLSv1")) .putList(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) + .put(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + CLIENT_AUTH_MODE, ClientAuth.REQUIRE.name().toLowerCase(Locale.ROOT)) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_PROTOCOLS, List.of("TLSv1.2", "TLSv1")) + .putList(MOCK_AUX_CERT_TYPE_BAR.sslSettingPrefix() + ENABLED_CIPHERS, List.of("TLS_AES_256_GCM_SHA384")) .putList(SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, List.of("TLSv1.3", "TLSv1.2")) .putList(SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, List.of("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384")) .build(); - final var httpSslParameters = SslParameters.loader(settings.getByPrefix(SSL_HTTP_PREFIX)).load(true); - final var transportSslParameters = SslParameters.loader(settings.getByPrefix(SSL_TRANSPORT_PREFIX)).load(false); - + final SslParameters httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); + final SslParameters auxSslParameters = SslParameters.loader(MOCK_AUX_CERT_TYPE_BAR, settings).load(); + final SslParameters transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); assertThat(httpSslParameters.provider(), is(SslProvider.JDK)); - assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); - assertThat(httpSslParameters.allowedProtocols(), is(List.of("TLSv1.2"))); assertThat(httpSslParameters.allowedCiphers(), is(List.of("TLS_AES_256_GCM_SHA384"))); - + assertThat(httpSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + assertThat(auxSslParameters.provider(), is(SslProvider.JDK)); + assertThat(auxSslParameters.allowedProtocols(), is(List.of("TLSv1.2"))); + assertThat(auxSslParameters.allowedCiphers(), is(List.of("TLS_AES_256_GCM_SHA384"))); + assertThat(auxSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); + assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); assertThat(transportSslParameters.allowedProtocols(), is(List.of("TLSv1.3", "TLSv1.2"))); assertThat(transportSslParameters.allowedCiphers(), is(List.of("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"))); - - assertThat(httpSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); assertThat(transportSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); } - } diff --git a/src/test/java/org/opensearch/security/ssl/util/SSLConfigConstantsTest.java b/src/test/java/org/opensearch/security/ssl/util/SSLConfigConstantsTest.java index b51efeda03..9a8da523d8 100644 --- a/src/test/java/org/opensearch/security/ssl/util/SSLConfigConstantsTest.java +++ b/src/test/java/org/opensearch/security/ssl/util/SSLConfigConstantsTest.java @@ -15,6 +15,7 @@ import org.junit.Test; import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.config.CertType; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS; @@ -24,13 +25,13 @@ public class SSLConfigConstantsTest { @Test public void testDefaultTLSProtocols() { - final var tlsDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, false); + final var tlsDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, CertType.TRANSPORT); assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2", "TLSv1.1" }, tlsDefaultProtocols); } @Test public void testDefaultSSLProtocols() { - final var sslDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, true); + final var sslDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, CertType.HTTP); assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2", "TLSv1.1" }, sslDefaultProtocols); } @@ -38,7 +39,7 @@ public void testDefaultSSLProtocols() { public void testCustomTLSProtocols() { final var tlsDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols( Settings.builder().putList(SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, List.of("TLSv1", "TLSv1.1")).build(), - false + CertType.TRANSPORT ); assertArrayEquals(new String[] { "TLSv1", "TLSv1.1" }, tlsDefaultProtocols); } @@ -47,7 +48,7 @@ public void testCustomTLSProtocols() { public void testCustomSSLProtocols() { final var sslDefaultProtocols = SSLConfigConstants.getSecureSSLProtocols( Settings.builder().putList(SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, List.of("TLSv1", "TLSv1.1")).build(), - true + CertType.HTTP ); assertArrayEquals(new String[] { "TLSv1", "TLSv1.1" }, sslDefaultProtocols); }