From 39cdb6bccc763be09450751a3d4afb779938d2e8 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Thu, 15 May 2025 10:35:18 -0700 Subject: [PATCH 01/29] Affix settings for aux transports. Signed-off-by: Finn Carroll --- .../ssl/OpenSearchSecuritySSLPlugin.java | 34 +++++++ .../security/ssl/util/SSLConfigConstants.java | 88 +++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 7dea9441b3..c4d89a3d74 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -94,6 +94,21 @@ 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_DEFAULT; +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_ALIAS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_TYPE; +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_AUX_TRUSTSTORE_ALIAS; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_TRUSTSTORE_TYPE; + //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 +649,25 @@ 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_TYPE, + SECURITY_SSL_AUX_KEYSTORE_ALIAS, + SECURITY_SSL_AUX_KEYSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMKEY_FILEPATH, + SECURITY_SSL_AUX_PEMCERT_FILEPATH, + SECURITY_SSL_AUX_CLIENTAUTH_MODE, + SECURITY_SSL_AUX_TRUSTSTORE_TYPE, + SECURITY_SSL_AUX_TRUSTSTORE_ALIAS, + SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH + )); + return settings; } 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..8a61e66895 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -20,7 +20,9 @@ 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; public final class SSLConfigConstants { @@ -98,6 +100,92 @@ 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 + "."; + + public static final boolean SECURITY_SSL_AUX_ENABLED_DEFAULT = false; // aux transports are optional + public static final Setting 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> 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> 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 SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.KEYSTORE_TYPE, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + public static final Setting SECURITY_SSL_AUX_KEYSTORE_ALIAS = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.KEYSTORE_ALIAS, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + public static final Setting 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 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 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) + ); + + public static final Setting 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 SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_TYPE, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_ALIAS, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + ); + + public static final Setting 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 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) + ); + /** * Transport layer (node-to-node) settings. * Transport layer acts both as client and server within the cluster. From 109ba06c53e94a150926e406d0083adfd8432d09 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Fri, 16 May 2025 12:47:51 -0700 Subject: [PATCH 02/29] Refactor CertType fron enum to class. Moving CertType from enum to class to enable dynamic additions of new cert types as aux transports are not known on initialization of security plugin and require distinct certificate configurations. Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 12 ++--- .../dlic/rest/api/ssl/CertificatesInfo.java | 15 +++--- .../api/ssl/CertificatesInfoNodesRequest.java | 6 ++- .../TransportCertificatesInfoNodesAction.java | 45 +++++----------- .../ssl/OpenSearchSecuritySSLPlugin.java | 33 ++++++------ .../security/ssl/SslSettingsManager.java | 16 +++++- .../security/ssl/config/CertType.java | 44 +++++++++++---- .../security/ssl/util/SSLConfigConstants.java | 54 +++++++++---------- 8 files changed, 120 insertions(+), 105 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index 175eb109e8..b5514906d2 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -108,13 +108,13 @@ private void verifyTimeoutRequest(final TestRestClient client) throws Exception } private void verifySSLCertsInfo(final TestRestClient client) throws Exception { - assertSSLCertsInfo(localCluster.nodes(), CertType.TYPES, ok(() -> client.get(sslCertsPath()))); + assertSSLCertsInfo(localCluster.nodes(), CertType.REGISTERED_CERT_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)))); + assertSSLCertsInfo(randomNodes, CertType.REGISTERED_CERT_TYPES, ok(() -> client.get(sslCertsPath(nodeIds)))); } - final var randomCertType = randomFrom(List.copyOf(CertType.TYPES)); + final var randomCertType = randomFrom(List.copyOf(CertType.REGISTERED_CERT_TYPES)); assertSSLCertsInfo( localCluster.nodes(), Set.of(randomCertType), @@ -125,7 +125,7 @@ private void verifySSLCertsInfo(final TestRestClient client) throws Exception { private void assertSSLCertsInfo( final List expectedNode, - final Set expectedCertTypes, + final Set expectedCertTypes, final TestRestClient.HttpResponse response ) { final var body = response.bodyAsJsonNode(); @@ -145,13 +145,13 @@ private void assertSSLCertsInfo( 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))) { + if (expectedCertTypes.contains(CertType.HTTP)) { 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))) { + if (expectedCertTypes.contains(CertType.TRANSPORT_CLIENT)) { final var transportCertificates = certificates.get(CertType.TRANSPORT.name().toUpperCase(Locale.ROOT)); assertThat(prettyStringBody, transportCertificates.isArray()); assertThat(prettyStringBody, transportCertificates.size(), is(1)); 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..870b43f036 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().name(), 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..c7bbb74926 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 @@ -56,9 +56,11 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { - if (!Strings.isEmpty(certificateType) && !CertType.TYPES.contains(certificateType)) { + if (!Strings.isEmpty(certificateType) && !CertType.REGISTERED_CERT_TYPES.contains(certificateType)) { final var errorMessage = new ActionRequestValidationException(); - errorMessage.addValidationError("wrong certificate type " + certificateType + ". Please use one of " + CertType.TYPES); + errorMessage.addValidationError( + "wrong certificate type " + certificateType + ". Please use one of " + CertType.REGISTERED_CERT_TYPES + ); 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..0c09dc9506 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.REGISTERED_CERT_TYPES) { + if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.name())) { + 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/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index c4d89a3d74..7805b0eb6c 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -97,7 +97,6 @@ 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_DEFAULT; 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_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_FILEPATH; @@ -652,21 +651,23 @@ 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_TYPE, - SECURITY_SSL_AUX_KEYSTORE_ALIAS, - SECURITY_SSL_AUX_KEYSTORE_FILEPATH, - SECURITY_SSL_AUX_PEMKEY_FILEPATH, - SECURITY_SSL_AUX_PEMCERT_FILEPATH, - SECURITY_SSL_AUX_CLIENTAUTH_MODE, - SECURITY_SSL_AUX_TRUSTSTORE_TYPE, - SECURITY_SSL_AUX_TRUSTSTORE_ALIAS, - SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH, - SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH - )); + settings.addAll( + List.of( + SECURITY_SSL_AUX_ENABLED, + SECURITY_SSL_AUX_ENABLED_CIPHERS, + SECURITY_SSL_AUX_ENABLED_PROTOCOLS, + SECURITY_SSL_AUX_KEYSTORE_TYPE, + SECURITY_SSL_AUX_KEYSTORE_ALIAS, + SECURITY_SSL_AUX_KEYSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMKEY_FILEPATH, + SECURITY_SSL_AUX_PEMCERT_FILEPATH, + SECURITY_SSL_AUX_CLIENTAUTH_MODE, + SECURITY_SSL_AUX_TRUSTSTORE_TYPE, + SECURITY_SSL_AUX_TRUSTSTORE_ALIAS, + SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH, + SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH + ) + ); return settings; } diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index b5f3c8f2f4..d90796dc7d 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -92,19 +92,31 @@ public Optional sslContextHandler(final CertType sslConfigPre } private Map buildSslContexts(final Environment environment) { - final var contexts = new ImmutableMap.Builder(); - final var configurations = loadConfigurations(environment); + final ImmutableMap.Builder contexts = new ImmutableMap.Builder<>(); + final Map configurations = loadConfigurations(environment); + + // for (String auxType : AUX_TRANSPORT_TYPES_SETTING.get(settings)) { + // Setting auxTypePortSettings = AUX_TRANSPORT_PORT.getConcreteSettingForNamespace(auxType); + // if (auxTypePortSettings.exists(settings)) { + // portsRanges.add(auxTypePortSettings.get(settings)); + // } else { + // portsRanges.add(new PortsRange(AUX_PORT_DEFAULTS)); + // } + // } + 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")); + return contexts.build(); } 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..5a74229b2d 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,54 @@ package org.opensearch.security.ssl.config; +import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; + +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; -public enum CertType { - HTTP(SSL_HTTP_PREFIX), - TRANSPORT(SSL_TRANSPORT_PREFIX), - TRANSPORT_CLIENT(SSL_TRANSPORT_CLIENT_PREFIX); +/** + * CertTypes have a 1-to-1 relationship with ssl context configurations and identify + * the setting prefix under which configuration settings are located. + */ +public class CertType implements Writeable { + private final String sslConfigPrefix; + private final String certTypeKey; - public static Set TYPES = Arrays.stream(CertType.values()) - .map(CertType::name) - .map(String::toLowerCase) - .collect(Collectors.toSet()); + public static CertType HTTP = new CertType(SSL_HTTP_PREFIX, "http"); + public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX, "transport"); + public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX, "transport_client"); - private final String sslConfigPrefix; + public static final Set REGISTERED_CERT_TYPES = new HashSet<>(Arrays.asList(HTTP, TRANSPORT, TRANSPORT_CLIENT)); - private CertType(String sslConfigPrefix) { + CertType(String sslConfigPrefix, String certTypeKey) { this.sslConfigPrefix = sslConfigPrefix; + this.certTypeKey = certTypeKey; + } + + public CertType(final StreamInput in) throws IOException { + this.sslConfigPrefix = in.readString(); + this.certTypeKey = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(sslConfigPrefix); + out.writeString(certTypeKey); } public String sslConfigPrefix() { return sslConfigPrefix; } + public String name() { + return certTypeKey; + } } 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 8a61e66895..54cec3634c 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -109,21 +109,21 @@ public final class SSLConfigConstants { public static final boolean SECURITY_SSL_AUX_ENABLED_DEFAULT = false; // aux transports are optional public static final Setting 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) + 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> 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) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.ENABLED_CIPHERS, + key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope) ); public static final Setting> 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) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.ENABLED_PROTOCOLS, + key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope) ); public static final Setting SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( @@ -151,39 +151,39 @@ public final class SSLConfigConstants { ); public static final Setting 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) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_CERT_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); public static final Setting 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) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.CLIENT_AUTH_MODE, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.TRUSTSTORE_TYPE, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_TYPE, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.TRUSTSTORE_ALIAS, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_ALIAS, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.TRUSTSTORE_FILEPATH, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.TRUSTSTORE_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); public static final Setting 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) + SSLConfigConstants.SSL_AUX_PREFIX, + SSLConfigConstants.PEM_TRUSTED_CAS_FILEPATH, + key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); /** From 0ddd583dba2e1b3d9e3826530ebf14fad07bcdbd Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 20 May 2025 15:21:29 -0700 Subject: [PATCH 03/29] Load aux certs in SslSettingsManager. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManager.java | 215 ++++++++++++------ .../security/ssl/config/CertType.java | 7 +- 2 files changed, 156 insertions(+), 66 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index d90796dc7d..5540c2fa4d 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -27,17 +27,23 @@ 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.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; +import static org.opensearch.security.ssl.config.CertType.REGISTERED_CERT_TYPES; 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 +52,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,6 +70,7 @@ 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; @@ -91,32 +94,29 @@ public Optional sslContextHandler(final CertType sslConfigPre return Optional.ofNullable(sslSettingsContexts.get(sslConfigPrefix)); } + /** + * Load and validate environment configuration for available CertTypes. + * For each valid CertType + * @param environment settings and JDK environment. + */ private Map buildSslContexts(final Environment environment) { final ImmutableMap.Builder contexts = new ImmutableMap.Builder<>(); final Map configurations = loadConfigurations(environment); - - // for (String auxType : AUX_TRANSPORT_TYPES_SETTING.get(settings)) { - // Setting auxTypePortSettings = AUX_TRANSPORT_PORT.getConcreteSettingForNamespace(auxType); - // if (auxTypePortSettings.exists(settings)) { - // portsRanges.add(auxTypePortSettings.get(settings)); - // } else { - // portsRanges.add(new PortsRange(AUX_PORT_DEFAULTS)); - // } - // } - - 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")); - + configurations.forEach((cert, sslConfig) -> { + // Handle TRANSPORT_CLIENT + // SslContextHandler configured as client and falls back to CertType.TRANSPORT sslConfiguration + if (cert == CertType.TRANSPORT_CLIENT) { + final var transportClientConfiguration = Optional.ofNullable(configurations.get(CertType.TRANSPORT_CLIENT)) + .orElse(sslConfig); + contexts.put(CertType.TRANSPORT_CLIENT, new SslContextHandler(transportClientConfiguration, true)); + } else { + Optional.ofNullable(configurations.get(cert)) + .ifPresentOrElse( + sslConfiguration -> contexts.put(cert, new SslContextHandler(sslConfiguration)), + () -> LOGGER.warn("SSL Configuration for " + cert.name() + " Layer hasn't been set") + ); + } + }); return contexts.build(); } @@ -133,20 +133,45 @@ public synchronized void reloadSslContext(final CertType certType) { } 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()); + final Settings settings = environment.settings(); + final ImmutableMap.Builder configurationBuilder = ImmutableMap.builder(); + final Settings httpSettings = settings.getByPrefix(CertType.HTTP.sslConfigPrefix()); + final Settings transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()); if (httpSettings.isEmpty() && transportSettings.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, auxType); + final Settings auxTransportSettings = settings.getByPrefix(auxCert.sslConfigPrefix()); + final Setting auxEnabled = SECURITY_SSL_AUX_ENABLED.getConcreteSetting(auxType); + REGISTERED_CERT_TYPES.add(auxCert); + if (auxEnabled.get(settings) && !clientNode(settings)) { + validateSettings(auxCert, settings, false); + final SslParameters auxSslParameters = SslParameters.loader(auxTransportSettings).load(true); + final Tuple auxTrustAndKeyStore = new SslCertificatesLoader( + auxCert.sslConfigPrefix() + ).loadConfiguration(environment); + configurationBuilder.put( + auxCert, + new SslConfiguration(auxSslParameters, auxTrustAndKeyStore.v1(), auxTrustAndKeyStore.v2()) + ); + LOGGER.info("TLS {} Provider : {}", auxCert.name(), auxSslParameters.provider()); + LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.name(), auxSslParameters.allowedProtocols()); + } + } - final var configurationBuilder = ImmutableMap.builder(); + /* + * Load HTTP SslConfiguration. + */ + final var httpEnabled = httpSettings.getAsBoolean(ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); if (httpEnabled && !clientNode(settings)) { - validateHttpSettings(httpSettings); + validateSettings(CertType.HTTP, settings, SECURITY_SSL_HTTP_ENABLED_DEFAULT); final var httpSslParameters = SslParameters.loader(httpSettings).load(true); final var httpTrustAndKeyStore = new SslCertificatesLoader(CertType.HTTP.sslConfigPrefix()).loadConfiguration(environment); configurationBuilder.put( @@ -156,6 +181,11 @@ private Map loadConfigurations(final Environment env LOGGER.info("TLS HTTP Provider : {}", httpSslParameters.provider()); LOGGER.info("Enabled TLS protocols for HTTP layer : {}", httpSslParameters.allowedProtocols()); } + + /* + * Load transport layer SslConfigurations. + */ + final var transportEnabled = transportSettings.getAsBoolean(ENABLED, SECURITY_SSL_TRANSPORT_ENABLED_DEFAULT); final var transportSslParameters = SslParameters.loader(transportSettings).load(false); if (transportEnabled) { if (hasExtendedKeyUsageEnabled(transportSettings)) { @@ -241,38 +271,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.sslConfigPrefix()); + 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.name() + + " SSL configuration. One of Keystore and Truststore files or X.509 PEM certificates and " + + "PKCS#8 keys groups should be set to configure " + + certType.name() + + " 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.sslConfigPrefix()); + 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.name().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.name().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.sslConfigPrefix()); + 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.name().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.name().toLowerCase(Locale.ROOT) + + " SSL configuration. " + + TRUSTSTORE_FILEPATH + + " must be set if client auth is required" ); } } @@ -398,5 +484,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 5a74229b2d..460a4df77c 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -36,9 +36,14 @@ public class CertType implements Writeable { public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX, "transport"); public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX, "transport_client"); + /* + * REGISTERED_CERT_TYPES provides visibility of known configured certificates to certificates api. + * {@link org.opensearch.security.dlic.rest.api.ssl.CertificatesInfoNodesRequest}. + * Disabled or invalid cert configurations are still registered here. + */ public static final Set REGISTERED_CERT_TYPES = new HashSet<>(Arrays.asList(HTTP, TRANSPORT, TRANSPORT_CLIENT)); - CertType(String sslConfigPrefix, String certTypeKey) { + public CertType(String sslConfigPrefix, String certTypeKey) { this.sslConfigPrefix = sslConfigPrefix; this.certTypeKey = certTypeKey; } From 810784489acfdc77ad12fb1701bc745b803b2c7c Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 20 May 2025 17:29:34 -0700 Subject: [PATCH 04/29] Fix handling of TRANSPORT/TRANSPORT_CLIENT certs. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManager.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 5540c2fa4d..15f64cee86 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -96,26 +96,31 @@ public Optional sslContextHandler(final CertType sslConfigPre /** * Load and validate environment configuration for available CertTypes. - * For each valid CertType * @param environment settings and JDK environment. */ private Map buildSslContexts(final Environment environment) { final ImmutableMap.Builder contexts = new ImmutableMap.Builder<>(); final Map configurations = loadConfigurations(environment); configurations.forEach((cert, sslConfig) -> { - // Handle TRANSPORT_CLIENT - // SslContextHandler configured as client and falls back to CertType.TRANSPORT sslConfiguration - if (cert == CertType.TRANSPORT_CLIENT) { - final var transportClientConfiguration = Optional.ofNullable(configurations.get(CertType.TRANSPORT_CLIENT)) - .orElse(sslConfig); - contexts.put(CertType.TRANSPORT_CLIENT, new SslContextHandler(transportClientConfiguration, true)); - } else { - Optional.ofNullable(configurations.get(cert)) - .ifPresentOrElse( - sslConfiguration -> contexts.put(cert, new SslContextHandler(sslConfiguration)), - () -> LOGGER.warn("SSL Configuration for " + cert.name() + " Layer hasn't been set") - ); + // 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.name()) + ); }); return contexts.build(); } From 07b5ce4c17e3a9bde2879d3889e7150b75d62196 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 21 May 2025 09:14:08 -0700 Subject: [PATCH 05/29] Formatting. Signed-off-by: Finn Carroll --- .../security/ssl/util/SSLConfigConstants.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) 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 54cec3634c..626d786461 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -107,79 +107,72 @@ public final class SSLConfigConstants { 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 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> 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> 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) ); + // aux keystore settings public static final Setting SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.KEYSTORE_TYPE, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_KEYSTORE_ALIAS = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.KEYSTORE_ALIAS, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting 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 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 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 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 SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.TRUSTSTORE_TYPE, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.TRUSTSTORE_ALIAS, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting 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 SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.PEM_TRUSTED_CAS_FILEPATH, From 6216bb6388bc55425668dd3986a9512996ed195b Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 21 May 2025 09:40:32 -0700 Subject: [PATCH 06/29] getSecureSSLCiphers and getSecureSSLProtocols to handle generic CertTypes. Signed-off-by: Finn Carroll --- .../security/ssl/DefaultSecurityKeyStore.java | 9 ++-- .../ssl/ExternalSecurityKeyStore.java | 19 ++++++-- .../security/ssl/util/SSLConfigConstants.java | 45 +++++-------------- .../org/opensearch/security/ssl/SSLTest.java | 9 ++-- .../ssl/util/SSLConfigConstantsTest.java | 9 ++-- 5 files changed, 41 insertions(+), 50 deletions(-) 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/util/SSLConfigConstants.java b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java index 626d786461..3683b9d215 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -24,6 +24,7 @@ import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.config.CertType; public final class SSLConfigConstants { /** @@ -245,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.sslConfigPrefix() + 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.sslConfigPrefix() + ENABLED_PROTOCOLS, Collections.emptyList()); + if (configuredProtocols != null && !configuredProtocols.isEmpty()) { return configuredProtocols.toArray(new String[0]); } - return ALLOWED_SSL_PROTOCOLS.clone(); } @@ -377,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/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); } From badc64d6c00c062e63b2cb1e2bd52f1537024e22 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 21 May 2025 10:26:23 -0700 Subject: [PATCH 07/29] SslParameters to handle generic CertType. Additionally move CertType specification to constructor and filter settings within loader constructor. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManager.java | 18 ++++---- .../security/ssl/config/SslParameters.java | 43 +++++++++++-------- .../security/ssl/util/SSLConfigConstants.java | 8 ++++ .../security/ssl/SslContextHandlerTest.java | 3 +- .../ssl/config/SslParametersTest.java | 10 ++--- 5 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 15f64cee86..a51cb5d102 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -140,9 +140,8 @@ public synchronized void reloadSslContext(final CertType certType) { private Map loadConfigurations(final Environment environment) { final Settings settings = environment.settings(); final ImmutableMap.Builder configurationBuilder = ImmutableMap.builder(); - final Settings httpSettings = settings.getByPrefix(CertType.HTTP.sslConfigPrefix()); - final Settings transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()); - if (httpSettings.isEmpty() && transportSettings.isEmpty()) { + if (settings.getByPrefix(CertType.HTTP.sslConfigPrefix()).isEmpty() + && settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()).isEmpty()) { throw new OpenSearchException("No SSL configuration found"); } jceWarnings(); @@ -153,12 +152,11 @@ private Map loadConfigurations(final Environment env */ for (String auxType : AUX_TRANSPORT_TYPES_SETTING.get(environment.settings())) { final CertType auxCert = new CertType(SSL_AUX_PREFIX + auxType, auxType); - final Settings auxTransportSettings = settings.getByPrefix(auxCert.sslConfigPrefix()); final Setting auxEnabled = SECURITY_SSL_AUX_ENABLED.getConcreteSetting(auxType); REGISTERED_CERT_TYPES.add(auxCert); if (auxEnabled.get(settings) && !clientNode(settings)) { validateSettings(auxCert, settings, false); - final SslParameters auxSslParameters = SslParameters.loader(auxTransportSettings).load(true); + final SslParameters auxSslParameters = SslParameters.loader(auxCert, settings).load(); final Tuple auxTrustAndKeyStore = new SslCertificatesLoader( auxCert.sslConfigPrefix() ).loadConfiguration(environment); @@ -174,10 +172,10 @@ private Map loadConfigurations(final Environment env /* * Load HTTP SslConfiguration. */ - final var httpEnabled = httpSettings.getAsBoolean(ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); + final boolean httpEnabled = settings.getAsBoolean(CertType.HTTP.sslConfigPrefix() + ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); if (httpEnabled && !clientNode(settings)) { validateSettings(CertType.HTTP, settings, SECURITY_SSL_HTTP_ENABLED_DEFAULT); - final var httpSslParameters = SslParameters.loader(httpSettings).load(true); + final var httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); final var httpTrustAndKeyStore = new SslCertificatesLoader(CertType.HTTP.sslConfigPrefix()).loadConfiguration(environment); configurationBuilder.put( CertType.HTTP, @@ -190,9 +188,9 @@ private Map loadConfigurations(final Environment env /* * Load transport layer SslConfigurations. */ - final var transportEnabled = transportSettings.getAsBoolean(ENABLED, SECURITY_SSL_TRANSPORT_ENABLED_DEFAULT); - final var transportSslParameters = SslParameters.loader(transportSettings).load(false); - if (transportEnabled) { + final Settings transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()); + 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( 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..63b2742c66 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.sslConfigPrefix()); } - 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.name() + " 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.name() + " 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 3683b9d215..bbe77e7993 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -26,6 +26,8 @@ import org.opensearch.common.settings.Settings; import org.opensearch.security.ssl.config.CertType; +import io.netty.handler.ssl.ClientAuth; + public final class SSLConfigConstants { /** * Global configurations @@ -217,6 +219,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; 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/config/SslParametersTest.java b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java index d95c336e15..f40079ee22 100644 --- a/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java @@ -32,16 +32,14 @@ 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; 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); + final var httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); + final var transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); final var defaultCiphers = List.of(ALLOWED_SSL_CIPHERS); final var finalDefaultCiphers = Stream.of(SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites()) @@ -71,8 +69,8 @@ public void testCustomSSlParameters() { .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 var httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); + final var transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); assertThat(httpSslParameters.provider(), is(SslProvider.JDK)); assertThat(transportSslParameters.provider(), is(SslProvider.JDK)); From 84f3237ad369a6b4b72ca76eb9c70103de0c02fe Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 21 May 2025 13:49:41 -0700 Subject: [PATCH 08/29] SslParameters tests. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManager.java | 26 +-- .../security/ssl/config/CertType.java | 23 +-- .../security/ssl/config/SslParameters.java | 2 +- .../security/ssl/util/SSLConfigConstants.java | 4 +- .../ssl/config/SslParametersTest.java | 161 +++++++++++++++--- 5 files changed, 162 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index a51cb5d102..107a26ea1b 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -140,8 +140,8 @@ public synchronized void reloadSslContext(final CertType certType) { private Map loadConfigurations(final Environment environment) { final Settings settings = environment.settings(); final ImmutableMap.Builder configurationBuilder = ImmutableMap.builder(); - if (settings.getByPrefix(CertType.HTTP.sslConfigPrefix()).isEmpty() - && settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()).isEmpty()) { + if (settings.getByPrefix(CertType.HTTP.sslSettingPrefix()).isEmpty() + && settings.getByPrefix(CertType.TRANSPORT.sslSettingPrefix()).isEmpty()) { throw new OpenSearchException("No SSL configuration found"); } jceWarnings(); @@ -151,14 +151,14 @@ private Map loadConfigurations(final Environment env * 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, auxType); + final CertType auxCert = new CertType(SSL_AUX_PREFIX + auxType + "."); final Setting auxEnabled = SECURITY_SSL_AUX_ENABLED.getConcreteSetting(auxType); REGISTERED_CERT_TYPES.add(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.sslConfigPrefix() + auxCert.sslSettingPrefix() ).loadConfiguration(environment); configurationBuilder.put( auxCert, @@ -172,11 +172,11 @@ private Map loadConfigurations(final Environment env /* * Load HTTP SslConfiguration. */ - final boolean httpEnabled = settings.getAsBoolean(CertType.HTTP.sslConfigPrefix() + ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); + final boolean httpEnabled = settings.getAsBoolean(CertType.HTTP.sslSettingPrefix() + ENABLED, SECURITY_SSL_HTTP_ENABLED_DEFAULT); if (httpEnabled && !clientNode(settings)) { 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.sslConfigPrefix()).loadConfiguration(environment); + final var httpTrustAndKeyStore = new SslCertificatesLoader(CertType.HTTP.sslSettingPrefix()).loadConfiguration(environment); configurationBuilder.put( CertType.HTTP, new SslConfiguration(httpSslParameters, httpTrustAndKeyStore.v1(), httpTrustAndKeyStore.v2()) @@ -188,13 +188,13 @@ private Map loadConfigurations(final Environment env /* * Load transport layer SslConfigurations. */ - final Settings transportSettings = settings.getByPrefix(CertType.TRANSPORT.sslConfigPrefix()); + 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( @@ -202,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( @@ -211,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( @@ -281,7 +281,7 @@ private boolean clientNode(final Settings settings) { * @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.sslConfigPrefix()); + final Settings certSettings = settings.getByPrefix(certType.sslSettingPrefix()); if (certSettings.isEmpty()) return; if (!certSettings.getAsBoolean(ENABLED, enabled_default)) return; if (hasPemStoreSettings(certSettings)) { @@ -309,7 +309,7 @@ private void validateSettings(final CertType certType, final Settings settings, * @param settings {@link org.opensearch.env.Environment} settings. */ private void validatePemStoreSettings(CertType transportType, final Settings settings) throws OpenSearchException { - final var transportSettings = settings.getByPrefix(transportType.sslConfigPrefix()); + final var transportSettings = settings.getByPrefix(transportType.sslSettingPrefix()); final var clientAuth = ClientAuth.valueOf( transportSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT) ); @@ -342,7 +342,7 @@ private void validatePemStoreSettings(CertType transportType, final Settings set * @param settings {@link org.opensearch.env.Environment} settings. */ private void validateKeyStoreSettings(CertType transportType, final Settings settings) throws OpenSearchException { - final var transportSettings = settings.getByPrefix(transportType.sslConfigPrefix()); + final var transportSettings = settings.getByPrefix(transportType.sslSettingPrefix()); final var clientAuth = ClientAuth.valueOf( transportSettings.get(CLIENT_AUTH_MODE, ClientAuth.OPTIONAL.name()).toUpperCase(Locale.ROOT) ); 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 460a4df77c..17d89d350a 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -29,12 +29,12 @@ * the setting prefix under which configuration settings are located. */ public class CertType implements Writeable { - private final String sslConfigPrefix; + private final String sslConfigSettingPrefix; private final String certTypeKey; - public static CertType HTTP = new CertType(SSL_HTTP_PREFIX, "http"); - public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX, "transport"); - public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX, "transport_client"); + 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); /* * REGISTERED_CERT_TYPES provides visibility of known configured certificates to certificates api. @@ -43,24 +43,25 @@ public class CertType implements Writeable { */ public static final Set REGISTERED_CERT_TYPES = new HashSet<>(Arrays.asList(HTTP, TRANSPORT, TRANSPORT_CLIENT)); - public CertType(String sslConfigPrefix, String certTypeKey) { - this.sslConfigPrefix = sslConfigPrefix; - this.certTypeKey = certTypeKey; + public CertType(String sslConfigSettingPrefix) { + this.sslConfigSettingPrefix = sslConfigSettingPrefix; + String[] parts = sslConfigSettingPrefix.split("\\."); + this.certTypeKey = parts[parts.length - 1]; } public CertType(final StreamInput in) throws IOException { - this.sslConfigPrefix = in.readString(); + this.sslConfigSettingPrefix = in.readString(); this.certTypeKey = in.readString(); } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeString(sslConfigPrefix); + out.writeString(sslConfigSettingPrefix); out.writeString(certTypeKey); } - public String sslConfigPrefix() { - return sslConfigPrefix; + public String sslSettingPrefix() { + return sslConfigSettingPrefix; } public String name() { 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 63b2742c66..1bd64ac03b 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslParameters.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslParameters.java @@ -108,7 +108,7 @@ public static final class Loader { public Loader(final CertType certType, final Settings settings) { this.certType = certType; - this.sslConfigSettings = settings.getByPrefix(certType.sslConfigPrefix()); + this.sslConfigSettings = settings.getByPrefix(certType.sslSettingPrefix()); } private SslProvider provider() { 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 bbe77e7993..d32e23cea3 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -255,7 +255,7 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_CLIENT_EXTERNAL_CONTEXT_ID = SSL_PREFIX + "client.external_context_id"; public static List getSecureSSLCiphers(Settings settings, CertType certType) { - List configuredCiphers = settings.getAsList(certType.sslConfigPrefix() + ENABLED_CIPHERS, Collections.emptyList()); + List configuredCiphers = settings.getAsList(certType.sslSettingPrefix() + ENABLED_CIPHERS, Collections.emptyList()); if (configuredCiphers != null && !configuredCiphers.isEmpty()) { return configuredCiphers; } @@ -263,7 +263,7 @@ public static List getSecureSSLCiphers(Settings settings, CertType certT } public static String[] getSecureSSLProtocols(Settings settings, CertType certType) { - List configuredProtocols = settings.getAsList(certType.sslConfigPrefix() + ENABLED_PROTOCOLS, Collections.emptyList()); + List configuredProtocols = settings.getAsList(certType.sslSettingPrefix() + ENABLED_PROTOCOLS, Collections.emptyList()); if (configuredProtocols != null && !configuredProtocols.isEmpty()) { return configuredProtocols.toArray(new String[0]); } 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 f40079ee22..08bf4998b1 100644 --- a/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java @@ -11,78 +11,185 @@ package org.opensearch.security.ssl.config; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; 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.OpenSearchException; +import org.opensearch.OpenSearchSecurityException; import org.opensearch.common.settings.Settings; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslProvider; +import org.opensearch.env.TestEnvironment; +import org.opensearch.security.ssl.SslSettingsManager; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertThrows; 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_AUX_PREFIX; public class SslParametersTest { - @Test - public void testDefaultSslParameters() throws Exception { - final var settings = Settings.EMPTY; - final var httpSslParameters = SslParameters.loader(CertType.HTTP, settings).load(); - final var transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); + 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()) - .filter(defaultCiphers::contains) - .sorted(String::compareTo) - .collect(Collectors.toList()); + 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(httpSslParameters.clientAuth(), is(ClientAuth.OPTIONAL)); assertThat(transportSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); } @Test - public void testCustomSSlParameters() { - 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")) - .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(CertType.HTTP, settings).load(); - final var transportSslParameters = SslParameters.loader(CertType.TRANSPORT, settings).load(); - + 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(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)); + } + @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 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 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(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(transportSslParameters.clientAuth(), is(ClientAuth.REQUIRE)); } - } From bde96e18b27870c2e9f35c7ffa9fdd52a958b5e9 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 21 May 2025 16:34:46 -0700 Subject: [PATCH 09/29] Add SslSettingsManagerReloadListener tests for aux. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManager.java | 6 +- .../security/ssl/config/CertType.java | 14 ++ .../security/ssl/util/SSLConfigConstants.java | 26 +- .../SslSettingsManagerReloadListenerTest.java | 226 +++++++++--------- .../ssl/config/SslParametersTest.java | 74 +++--- 5 files changed, 183 insertions(+), 163 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 107a26ea1b..9098d38092 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -90,8 +90,8 @@ 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)); } /** @@ -152,7 +152,7 @@ private Map loadConfigurations(final Environment env */ 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.getConcreteSetting(auxType); + final Setting auxEnabled = SECURITY_SSL_AUX_ENABLED.getConcreteSettingForNamespace(auxType); REGISTERED_CERT_TYPES.add(auxCert); if (auxEnabled.get(settings) && !clientNode(settings)) { validateSettings(auxCert, settings, false); 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 17d89d350a..873b0d3980 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import org.opensearch.core.common.io.stream.StreamInput; @@ -67,4 +68,17 @@ public String sslSettingPrefix() { public String name() { return certTypeKey; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CertType certType = (CertType) o; + return sslConfigSettingPrefix.equals(certType.sslConfigSettingPrefix); + } + + @Override + public int hashCode() { + return Objects.hash(sslConfigSettingPrefix); + } } 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 d32e23cea3..a9fd548ec8 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -112,71 +112,71 @@ public final class SSLConfigConstants { // aux enable settings public static final boolean SECURITY_SSL_AUX_ENABLED_DEFAULT = false; // aux transports are optional - public static final Setting SECURITY_SSL_AUX_ENABLED = Setting.affixKeySetting( + 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> SECURITY_SSL_AUX_ENABLED_CIPHERS = Setting.affixKeySetting( + 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> SECURITY_SSL_AUX_ENABLED_PROTOCOLS = Setting.affixKeySetting( + 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) ); // aux keystore settings - public static final Setting SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( + public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.KEYSTORE_TYPE, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_KEYSTORE_ALIAS = Setting.affixKeySetting( + public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_ALIAS = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.KEYSTORE_ALIAS, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_KEYSTORE_FILEPATH = Setting.affixKeySetting( + 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 SECURITY_SSL_AUX_PEMKEY_FILEPATH = Setting.affixKeySetting( + 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 SECURITY_SSL_AUX_PEMCERT_FILEPATH = Setting.affixKeySetting( + 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 SECURITY_SSL_AUX_CLIENTAUTH_MODE = Setting.affixKeySetting( + 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 SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( + public static final Setting.AffixSetting SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.TRUSTSTORE_TYPE, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( + public static final Setting.AffixSetting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.TRUSTSTORE_ALIAS, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH = Setting.affixKeySetting( + 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 SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH = Setting.affixKeySetting( + 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) diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java index 64308d0abb..6e04d36309 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; @@ -38,26 +40,19 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; +import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; 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; 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.name()) + ).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.name()) + ).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.name().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.name().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.name().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/config/SslParametersTest.java b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java index 08bf4998b1..fac8c9c255 100644 --- a/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/SslParametersTest.java @@ -12,7 +12,6 @@ package org.opensearch.security.ssl.config; import java.security.NoSuchAlgorithmException; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; @@ -22,18 +21,14 @@ import org.junit.Before; import org.junit.Test; -import org.opensearch.OpenSearchException; import org.opensearch.OpenSearchSecurityException; import org.opensearch.common.settings.Settings; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslProvider; -import org.opensearch.env.TestEnvironment; -import org.opensearch.security.ssl.SslSettingsManager; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertThrows; 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; @@ -44,6 +39,7 @@ 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_AUX_PREFIX; +import static org.junit.Assert.assertThrows; public class SslParametersTest { @@ -58,9 +54,9 @@ public class SslParametersTest { public void setup() throws NoSuchAlgorithmException { final var defaultCiphers = List.of(ALLOWED_SSL_CIPHERS); finalDefaultCiphers = Stream.of(SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites()) - .filter(defaultCiphers::contains) - .sorted(String::compareTo) - .collect(Collectors.toList()); + .filter(defaultCiphers::contains) + .sorted(String::compareTo) + .collect(Collectors.toList()); } @Test @@ -93,10 +89,10 @@ public void testDefaultSslParametersForTransport() { @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(); + .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"))); @@ -107,10 +103,10 @@ public void testCustomSSlParametersForHttp() { @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(); + .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"))); @@ -121,13 +117,13 @@ public void testCustomSSlParametersForAux() { @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(); + .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"))); @@ -143,10 +139,10 @@ public void testCustomSSlParametersForMultiAux() { @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(); + .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()); } @@ -154,9 +150,9 @@ public void testSSlParametersEmptyProtocolsFails() { @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(); + .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"))); @@ -167,15 +163,15 @@ public void testCustomSSlParametersForTransport() { @Test 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(); + .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 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(); From 2e53b73e5afe10895a24410b3f8d17d42e36632c Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 27 May 2025 10:15:10 -0700 Subject: [PATCH 10/29] Helpers for aux affix settings. Signed-off-by: Finn Carroll --- .../opensearch/security/ssl/util/SSLConfigConstants.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 a9fd548ec8..f3d71ff228 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -182,6 +182,14 @@ public final class SSLConfigConstants { 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.name()).getKey(); + } + public static String getBoolAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { + return affix.getConcreteSettingForNamespace(certType.name()).getKey(); + } + /** * Transport layer (node-to-node) settings. * Transport layer acts both as client and server within the cluster. From aa4eec53065a6d7395136e15912ebdf94e02510c Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 27 May 2025 16:59:45 -0700 Subject: [PATCH 11/29] Remove unused settings keystore/truststore type/alias. Signed-off-by: Finn Carroll --- .../ssl/OpenSearchSecuritySSLPlugin.java | 8 ------- .../security/ssl/util/SSLConfigConstants.java | 22 ------------------- 2 files changed, 30 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 7805b0eb6c..5a3549e16d 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -98,15 +98,11 @@ 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_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_KEYSTORE_TYPE; 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_AUX_TRUSTSTORE_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH; -import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_AUX_TRUSTSTORE_TYPE; //For ES5 this class has only effect when SSL only plugin is installed public class OpenSearchSecuritySSLPlugin extends Plugin implements SystemIndexPlugin, NetworkPlugin { @@ -656,14 +652,10 @@ public List> getSettings() { SECURITY_SSL_AUX_ENABLED, SECURITY_SSL_AUX_ENABLED_CIPHERS, SECURITY_SSL_AUX_ENABLED_PROTOCOLS, - SECURITY_SSL_AUX_KEYSTORE_TYPE, - SECURITY_SSL_AUX_KEYSTORE_ALIAS, SECURITY_SSL_AUX_KEYSTORE_FILEPATH, SECURITY_SSL_AUX_PEMKEY_FILEPATH, SECURITY_SSL_AUX_PEMCERT_FILEPATH, SECURITY_SSL_AUX_CLIENTAUTH_MODE, - SECURITY_SSL_AUX_TRUSTSTORE_TYPE, - SECURITY_SSL_AUX_TRUSTSTORE_ALIAS, SECURITY_SSL_AUX_TRUSTSTORE_FILEPATH, SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH ) 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 f3d71ff228..42d04a8031 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -127,18 +127,6 @@ public final class SSLConfigConstants { SSLConfigConstants.ENABLED_PROTOCOLS, key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope) ); - - // aux keystore settings - public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_TYPE = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.KEYSTORE_TYPE, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) - ); - public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_ALIAS = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.KEYSTORE_ALIAS, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) - ); public static final Setting.AffixSetting SECURITY_SSL_AUX_KEYSTORE_FILEPATH = Setting.affixKeySetting( SSLConfigConstants.SSL_AUX_PREFIX, SSLConfigConstants.KEYSTORE_FILEPATH, @@ -161,16 +149,6 @@ public final class SSLConfigConstants { SSLConfigConstants.CLIENT_AUTH_MODE, key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) ); - public static final Setting.AffixSetting SECURITY_SSL_AUX_TRUSTSTORE_TYPE = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.TRUSTSTORE_TYPE, - key -> Setting.simpleString(key, Setting.Property.NodeScope, Setting.Property.Filtered) - ); - public static final Setting.AffixSetting SECURITY_SSL_AUX_TRUSTSTORE_ALIAS = Setting.affixKeySetting( - SSLConfigConstants.SSL_AUX_PREFIX, - SSLConfigConstants.TRUSTSTORE_ALIAS, - 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, From b36bed1de7fcc80358c9c94e8d4d0d8dbb8015b3 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Thu, 29 May 2025 16:38:04 -0700 Subject: [PATCH 12/29] Additional aux tests for SettingsManager suite. Signed-off-by: Finn Carroll --- .../security/ssl/SslSettingsManagerTest.java | 361 +++++++++++++----- 1 file changed, 258 insertions(+), 103 deletions(-) diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index 1aa2c47eb3..f9306dadf3 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -31,8 +31,15 @@ import io.netty.handler.ssl.SslContext; import static org.hamcrest.MatcherAssert.assertThat; +import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; 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,10 +59,13 @@ 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.junit.Assert.assertThrows; @@ -64,10 +74,83 @@ 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.name())) + .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.name() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_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.name())) + .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.name() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_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.name() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate_pk.pem"); + writeCertificates("ca_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate_pk.pem"); } static void writeCertificates(final String trustedFileName, final String accessFileName, final String accessPkFileName) @@ -85,28 +168,21 @@ 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))); + public void testFailsIfNoConfigDefine() { + assertThrows(OpenSearchException.class, () -> 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 transportFailsIfConfigEnabledButNotDefined() throws Exception { - final var noTransportSettingsButItEnabled = defaultSettingsBuilder().put(SECURITY_SSL_TRANSPORT_ENABLED, true).build(); - assertThrows( - OpenSearchException.class, - () -> new SslSettingsManager(TestEnvironment.newEnvironment(noTransportSettingsButItEnabled)) - ); - } - - @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 +193,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 +206,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 +219,119 @@ 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()); + securitySettings.setString(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "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); + withAuxFooSslSettings(settingsBuilder); + final var transportEnabled = randomBoolean(); final var sslSettingsManager = new SslSettingsManager( TestEnvironment.newEnvironment( @@ -209,6 +340,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 +351,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 +360,26 @@ 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 +389,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 +410,84 @@ 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()); + 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, - "ca_transport_certificate.pem", - "access_transport_certificate.pem", - "access_transport_certificate_pk.pem" - ); + 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.name(), MOCK_AUX_CERT_TYPE_FOO.name())) + .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 +506,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 +525,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 +569,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 +586,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 +596,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 +616,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"); - } - } From d18872cb969d95ee0ea2248fff35c5fbe84b5d9f Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Fri, 30 May 2025 11:16:14 -0700 Subject: [PATCH 13/29] Move CertType to uniquely identify cert types by id. Where id is the last element of the setting prefix. i.e. http, transport-client, transport, grpc... Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 6 ++-- .../dlic/rest/api/ssl/CertificatesInfo.java | 2 +- .../api/ssl/CertificatesInfoNodesRequest.java | 20 +++++------ .../TransportCertificatesInfoNodesAction.java | 2 +- .../security/ssl/SslSettingsManager.java | 22 ++++++------- .../security/ssl/config/CertType.java | 33 ++++++++++++++----- .../security/ssl/config/SslParameters.java | 4 +-- .../security/ssl/util/SSLConfigConstants.java | 4 +-- .../SslSettingsManagerReloadListenerTest.java | 10 +++--- .../security/ssl/SslSettingsManagerTest.java | 30 ++++++++--------- 10 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index b5514906d2..edf901adf6 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -120,7 +120,6 @@ private void verifySSLCertsInfo(final TestRestClient client) throws Exception { Set.of(randomCertType), ok(() -> client.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType))) ); - } private void assertSSLCertsInfo( @@ -146,19 +145,18 @@ private void assertSSLCertsInfo( assertThat(prettyStringBody, node.has("certificates")); final var certificates = node.get("certificates"); if (expectedCertTypes.contains(CertType.HTTP)) { - final var httpCertificates = certificates.get(CertType.HTTP.name().toUpperCase(Locale.ROOT)); + final var httpCertificates = certificates.get(CertType.HTTP.certID()); assertThat(prettyStringBody, httpCertificates.isArray()); assertThat(prettyStringBody, httpCertificates.size(), is(1)); verifyCertsJson(n.nodeNumber(), httpCertificates.get(0)); } if (expectedCertTypes.contains(CertType.TRANSPORT_CLIENT)) { - final var transportCertificates = certificates.get(CertType.TRANSPORT.name().toUpperCase(Locale.ROOT)); + final var transportCertificates = certificates.get(CertType.TRANSPORT.certID()); assertThat(prettyStringBody, transportCertificates.isArray()); assertThat(prettyStringBody, transportCertificates.size(), is(1)); verifyCertsJson(n.nodeNumber(), transportCertificates.get(0)); } } - } private void verifyCertsJson(final int nodeNumber, final JsonNode jsonNode) { 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 870b43f036..91a65de845 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 @@ -43,7 +43,7 @@ public void writeTo(StreamOutput out) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject("certificates"); for (Map.Entry> entry : certificates.entrySet()) { - builder.field(entry.getKey().name(), certificates.get(entry.getKey())); + builder.field(entry.getKey().certID(), 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 c7bbb74926..f78245ec6b 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 @@ -21,26 +21,26 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.security.ssl.config.CertType; -public class CertificatesInfoNodesRequest extends BaseNodesRequest { - - private final String certificateType; +import static org.opensearch.security.ssl.config.CertType.certRegistered; +public class CertificatesInfoNodesRequest extends BaseNodesRequest { + 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,16 +50,16 @@ 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.REGISTERED_CERT_TYPES.contains(certificateType)) { + if (!Strings.isEmpty(certTypeID) && !certRegistered(certTypeID)) { final var errorMessage = new ActionRequestValidationException(); errorMessage.addValidationError( - "wrong certificate type " + certificateType + ". Please use one of " + CertType.REGISTERED_CERT_TYPES + "wrong certificate type " + certTypeID + ". Please use one of " + CertType.REGISTERED_CERT_TYPES ); return errorMessage; } 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 0c09dc9506..65ae2afd6f 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 @@ -100,7 +100,7 @@ protected CertificatesNodesResponse.CertificatesNodeResponse nodeOperation(final protected CertificatesInfo loadCertificates(final Optional loadCertType) { Map> certInfos = new HashMap<>(); for (CertType certType : CertType.REGISTERED_CERT_TYPES) { - if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.name())) { + if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.certID())) { List certs = sslSettingsManager.sslContextHandler(certType) .map(SslContextHandler::certificates) .map(this::certificatesDetails) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 9098d38092..27050c9d45 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -119,7 +119,7 @@ private Map buildSslContexts(final Environment envi Optional.ofNullable(configurations.get(cert)) .ifPresentOrElse( sslConfiguration -> contexts.put(cert, new SslContextHandler(sslConfiguration)), - () -> LOGGER.warn("SSL Configuration for {} Layer hasn't been set", cert.name()) + () -> LOGGER.warn("SSL Configuration for {} Layer hasn't been set", cert.certID()) ); }); return contexts.build(); @@ -129,12 +129,12 @@ 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.certID()); } } catch (CertificateException e) { throw new OpenSearchException(e); } - }, () -> LOGGER.error("Missing SSL Context for {}", certType.name())); + }, () -> LOGGER.error("Missing SSL Context for {}", certType.certID())); } private Map loadConfigurations(final Environment environment) { @@ -164,8 +164,8 @@ private Map loadConfigurations(final Environment env auxCert, new SslConfiguration(auxSslParameters, auxTrustAndKeyStore.v1(), auxTrustAndKeyStore.v2()) ); - LOGGER.info("TLS {} Provider : {}", auxCert.name(), auxSslParameters.provider()); - LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.name(), auxSslParameters.allowedProtocols()); + LOGGER.info("TLS {} Provider : {}", auxCert.certID(), auxSslParameters.provider()); + LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.certID(), auxSslParameters.allowedProtocols()); } } @@ -291,10 +291,10 @@ private void validateSettings(final CertType certType, final Settings settings, } else { throw new OpenSearchException( "Wrong " - + certType.name() + + certType.certID() + " SSL configuration. One of Keystore and Truststore files or X.509 PEM certificates and " + "PKCS#8 keys groups should be set to configure " - + certType.name() + + certType.certID() + " layer" ); } @@ -316,7 +316,7 @@ private void validatePemStoreSettings(CertType transportType, final Settings set if (!transportSettings.hasValue(PEM_CERT_FILEPATH) || !transportSettings.hasValue(PEM_KEY_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.name().toLowerCase(Locale.ROOT) + + transportType.certID().toLowerCase(Locale.ROOT) + " SSL configuration. " + String.join(", ", transportSettings.get(PEM_CERT_FILEPATH), transportSettings.get(PEM_KEY_FILEPATH)) + " must be set" @@ -325,7 +325,7 @@ private void validatePemStoreSettings(CertType transportType, final Settings set if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(PEM_TRUSTED_CAS_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.name().toLowerCase(Locale.ROOT) + + transportType.certID().toLowerCase(Locale.ROOT) + " SSL configuration. " + PEM_TRUSTED_CAS_FILEPATH + " must be set if client auth is required" @@ -349,7 +349,7 @@ private void validateKeyStoreSettings(CertType transportType, final Settings set if (!transportSettings.hasValue(KEYSTORE_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.name().toLowerCase(Locale.ROOT) + + transportType.certID().toLowerCase(Locale.ROOT) + " SSL configuration. " + transportSettings.get(KEYSTORE_FILEPATH) + " must be set" @@ -358,7 +358,7 @@ private void validateKeyStoreSettings(CertType transportType, final Settings set if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(TRUSTSTORE_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.name().toLowerCase(Locale.ROOT) + + transportType.certID().toLowerCase(Locale.ROOT) + " SSL configuration. " + TRUSTSTORE_FILEPATH + " must be set if client auth is required" 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 873b0d3980..b082cd344a 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashSet; +import java.util.Locale; import java.util.Objects; import java.util.Set; @@ -28,10 +29,12 @@ /** * CertTypes have a 1-to-1 relationship with ssl context configurations and identify * the setting prefix under which configuration settings are located. + * + * CertTypes are uniquely identified by their `certID` (the last element of their setting prefix) + * as this is how users identify certs in the certificates info API. */ public class CertType implements Writeable { private final String sslConfigSettingPrefix; - private final String certTypeKey; public static CertType HTTP = new CertType(SSL_HTTP_PREFIX); public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX); @@ -43,30 +46,42 @@ public class CertType implements Writeable { * Disabled or invalid cert configurations are still registered here. */ public static final Set REGISTERED_CERT_TYPES = new HashSet<>(Arrays.asList(HTTP, TRANSPORT, TRANSPORT_CLIENT)); + public static boolean certRegistered(String certID){ + for (CertType certType : REGISTERED_CERT_TYPES) { + if (Objects.equals(certType.certID(), certID)) { + return true; + } + } + return false; + } public CertType(String sslConfigSettingPrefix) { this.sslConfigSettingPrefix = sslConfigSettingPrefix; - String[] parts = sslConfigSettingPrefix.split("\\."); - this.certTypeKey = parts[parts.length - 1]; + } public CertType(final StreamInput in) throws IOException { this.sslConfigSettingPrefix = in.readString(); - this.certTypeKey = in.readString(); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(sslConfigSettingPrefix); - out.writeString(certTypeKey); } public String sslSettingPrefix() { return sslConfigSettingPrefix; } - public String name() { - return certTypeKey; + public String certID() { + String[] parts = sslConfigSettingPrefix.split("\\."); + String id = parts[parts.length - 1]; + return id.toLowerCase(Locale.ROOT); + } + + @Override + public String toString() { + return this.certID(); } @Override @@ -74,11 +89,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CertType certType = (CertType) o; - return sslConfigSettingPrefix.equals(certType.sslConfigSettingPrefix); + return this.certID().equals(certType.certID()); } @Override public int hashCode() { - return Objects.hash(sslConfigSettingPrefix); + return Objects.hash(this.certID()); } } 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 1bd64ac03b..85acc8c07f 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslParameters.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslParameters.java @@ -166,10 +166,10 @@ public SslParameters load() { validateCertDNsOnReload(sslConfigSettings) ); if (sslParameters.allowedProtocols().isEmpty()) { - throw new OpenSearchSecurityException("No ssl protocols for " + certType.name() + " layer"); + throw new OpenSearchSecurityException("No ssl protocols for " + certType.certID() + " layer"); } if (sslParameters.allowedCiphers().isEmpty()) { - throw new OpenSearchSecurityException("No valid cipher suites for " + certType.name() + " layer"); + throw new OpenSearchSecurityException("No valid cipher suites for " + certType.certID() + " 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 42d04a8031..4c9b3b65e6 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -162,10 +162,10 @@ public final class SSLConfigConstants { // helper to resolve setting key for CertType namespace public static String getStringAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { - return affix.getConcreteSettingForNamespace(certType.name()).getKey(); + return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); } public static String getBoolAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { - return affix.getConcreteSettingForNamespace(certType.name()).getKey(); + return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); } /** diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java index 6e04d36309..220cb3f115 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java @@ -107,7 +107,7 @@ public void testReloadsSslContextOnPemStoreFilesChangedForHttp() throws Exceptio public void testReloadsSslContextOnPemStoreFilesChangedForAux() throws Exception { Settings.Builder settings = defaultSettingsBuilder().putList( AUX_TRANSPORT_TYPES_SETTING.getKey(), - List.of(MOCK_AUX_CERT_TYPE_FOO.name()) + List.of(MOCK_AUX_CERT_TYPE_FOO.certID()) ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); reloadSslContextOnPemFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); } @@ -126,7 +126,7 @@ public void testReloadsSslContextOnJdkStoreFilesChangedForHttp() throws Exceptio public void testReloadsSslContextOnJdkStoreFilesChangedForAux() throws Exception { Settings.Builder settings = defaultSettingsBuilder().putList( AUX_TRANSPORT_TYPES_SETTING.getKey(), - List.of(MOCK_AUX_CERT_TYPE_FOO.name()) + List.of(MOCK_AUX_CERT_TYPE_FOO.certID()) ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); reloadSslContextOnJdkStoreFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); } @@ -143,7 +143,7 @@ private void reloadSslContextOnJdkStoreFilesChangedForTransportType(CertType cer final String trustStoreTypeSetting = settingPrefix + TRUSTSTORE_TYPE; final String keyStorePathSetting = settingPrefix + KEYSTORE_FILEPATH; final String keyStoreTypeSetting = settingPrefix + KEYSTORE_TYPE; - final String certTypeFilePrefix = certType.name().toLowerCase(Locale.ROOT); + final String certTypeFilePrefix = certType.certID().toLowerCase(Locale.ROOT); final var keyStorePassword = randomAsciiAlphanumOfLength(10); final var secureSettings = new MockSecureSettings(); secureSettings.setString(settingPrefix + "truststore_password_secure", keyStorePassword); @@ -186,7 +186,7 @@ private void reloadSslContextOnPemFilesChangedForTransportType(CertType certType 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.name().toLowerCase(Locale.ROOT); + final String certTypeFilePrefix = certType.certID().toLowerCase(Locale.ROOT); MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(settingPrefix + "pemkey_password_secure", certificatesRule.privateKeyPassword()); reloadSslContextOnFilesChanged( @@ -214,7 +214,7 @@ private void reloadSslContextOnPemFilesChangedForTransportType(CertType certType private void reloadSslContextOnFilesChanged(CertType certType, final Settings settings, final CertificatesWriter certificatesWriter) throws Exception { - final String certNamePrefix = certType.name().toLowerCase(Locale.ROOT); + final String certNamePrefix = certType.certID().toLowerCase(Locale.ROOT); final var defaultCertificates = generateCertificates(); var defaultKeyPair = defaultCertificates.v1(); var caCertificate = defaultCertificates.v2().v1(); diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index f9306dadf3..fc3d4a4ab6 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -80,15 +80,15 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.name())) + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_FOO.certID())) .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.name() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate_pk.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMTRUSTEDCAS_NAME = "ca_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem"; /* Settings for a mock aux transport - bar @@ -96,15 +96,15 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.name())) + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.certID())) .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.name() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate_pk.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMTRUSTEDCAS_NAME = "ca_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem"; + private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem"; static Settings.Builder defaultSettingsBuilder() { return Settings.builder() @@ -145,12 +145,12 @@ private void withAuxBarSslSettings(final Settings.Builder settingsBuilder) { 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.name() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.name() + "_certificate_pk.pem"); - writeCertificates("ca_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.name() + "_certificate_pk.pem"); + writeCertificates("ca_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem"); + writeCertificates("ca_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem"); } static void writeCertificates(final String trustedFileName, final String accessFileName, final String accessPkFileName) @@ -423,7 +423,7 @@ public void loadConfigurationAndBuildSslContextsMultipleAuxTransports() { withAuxBarSslSettings(settingsBuilder); withAuxFooSslSettings(settingsBuilder); settingsBuilder.put(Settings.builder() - .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.name(), MOCK_AUX_CERT_TYPE_FOO.name())) + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.certID(), MOCK_AUX_CERT_TYPE_FOO.certID())) .build()); final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settingsBuilder.build())); From 25094f86125ec7ab51b8315c3db87f57a3bec621 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Fri, 30 May 2025 12:40:18 -0700 Subject: [PATCH 14/29] Make CertTypes registry write only. Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 15 ++-- .../api/ssl/CertificatesInfoNodesRequest.java | 6 +- .../TransportCertificatesInfoNodesAction.java | 2 +- .../security/ssl/SslSettingsManager.java | 4 +- .../security/ssl/config/CertType.java | 79 ++++++++++++++----- 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index edf901adf6..57dd05a366 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -14,7 +14,6 @@ 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; @@ -108,23 +107,27 @@ private void verifyTimeoutRequest(final TestRestClient client) throws Exception } private void verifySSLCertsInfo(final TestRestClient client) throws Exception { - assertSSLCertsInfo(localCluster.nodes(), CertType.REGISTERED_CERT_TYPES, ok(() -> client.get(sslCertsPath()))); + List certTypes = new ArrayList<>(); + for (CertType cert : CertType.CERT_TYPE_REGISTRY) { + certTypes.add(cert); + } + assertSSLCertsInfo(localCluster.nodes(), certTypes, 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.REGISTERED_CERT_TYPES, ok(() -> client.get(sslCertsPath(nodeIds)))); + assertSSLCertsInfo(randomNodes, certTypes, ok(() -> client.get(sslCertsPath(nodeIds)))); } - final var randomCertType = randomFrom(List.copyOf(CertType.REGISTERED_CERT_TYPES)); + final var randomCertType = randomFrom(certTypes); assertSSLCertsInfo( localCluster.nodes(), - Set.of(randomCertType), + List.of(randomCertType), ok(() -> client.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType))) ); } private void assertSSLCertsInfo( final List expectedNode, - final Set expectedCertTypes, + final List expectedCertTypes, final TestRestClient.HttpResponse response ) { final var body = response.bodyAsJsonNode(); 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 f78245ec6b..0773e34f44 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 @@ -21,8 +21,6 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.security.ssl.config.CertType; -import static org.opensearch.security.ssl.config.CertType.certRegistered; - public class CertificatesInfoNodesRequest extends BaseNodesRequest { private final String certTypeID; private final boolean inMemory; @@ -56,10 +54,10 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { - if (!Strings.isEmpty(certTypeID) && !certRegistered(certTypeID)) { + if (!Strings.isEmpty(certTypeID) && !CertType.CERT_TYPE_REGISTRY.contains(certTypeID)) { final var errorMessage = new ActionRequestValidationException(); errorMessage.addValidationError( - "wrong certificate type " + certTypeID + ". Please use one of " + CertType.REGISTERED_CERT_TYPES + "wrong certificate type " + certTypeID + ". Please use one of " + CertType.CERT_TYPE_REGISTRY ); return errorMessage; } 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 65ae2afd6f..a8f58daf6a 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 @@ -99,7 +99,7 @@ protected CertificatesNodesResponse.CertificatesNodeResponse nodeOperation(final protected CertificatesInfo loadCertificates(final Optional loadCertType) { Map> certInfos = new HashMap<>(); - for (CertType certType : CertType.REGISTERED_CERT_TYPES) { + for (CertType certType : CertType.CERT_TYPE_REGISTRY) { if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.certID())) { List certs = sslSettingsManager.sslContextHandler(certType) .map(SslContextHandler::certificates) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 27050c9d45..ffc244adeb 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -43,7 +43,7 @@ import io.netty.handler.ssl.ClientAuth; import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; -import static org.opensearch.security.ssl.config.CertType.REGISTERED_CERT_TYPES; +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; @@ -153,7 +153,7 @@ private Map loadConfigurations(final Environment env 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); - REGISTERED_CERT_TYPES.add(auxCert); + CERT_TYPE_REGISTRY.register(auxCert); if (auxEnabled.get(settings) && !clientNode(settings)) { validateSettings(auxCert, settings, false); final SslParameters auxSslParameters = SslParameters.loader(auxCert, settings).load(); 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 b082cd344a..8700671060 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -12,8 +12,9 @@ package org.opensearch.security.ssl.config; import java.io.IOException; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.Locale; import java.util.Objects; import java.util.Set; @@ -22,42 +23,82 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.common.io.stream.Writeable; +import javax.annotation.Nonnull; + 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_CLIENT_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_SERVER_PREFIX; /** - * CertTypes have a 1-to-1 relationship with ssl context configurations and identify + * CertTypes have a 1-to-1 relationship with ssl contexts and identify * the setting prefix under which configuration settings are located. - * - * CertTypes are uniquely identified by their `certID` (the last element of their setting prefix) - * as this is how users identify certs in the certificates info API. + * CertTypes are uniquely identified by a `certID` which is used as the key for registering CertTypes on a node + * and fetching certificate info through a CertificatesInfoNodesRequest. */ public class CertType implements Writeable { private final String sslConfigSettingPrefix; 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); + public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX) { + @Override + public String certID() { return "transport_client"; } + }; + public static CertType TRANSPORT_SERVER = new CertType(SSL_TRANSPORT_SERVER_PREFIX) { + @Override + public String certID() { return "transport_server"; } + }; + + public static class NodeCertTypeRegistry implements Iterable { + private final Set RegisteredCertType = new HashSet<>(); + + public NodeCertTypeRegistry(CertType... initialCertTypes) { + for (CertType certType : initialCertTypes){ + register(certType); + } + } - /* - * REGISTERED_CERT_TYPES provides visibility of known configured certificates to certificates api. - * {@link org.opensearch.security.dlic.rest.api.ssl.CertificatesInfoNodesRequest}. - * Disabled or invalid cert configurations are still registered here. - */ - public static final Set REGISTERED_CERT_TYPES = new HashSet<>(Arrays.asList(HTTP, TRANSPORT, TRANSPORT_CLIENT)); - public static boolean certRegistered(String certID){ - for (CertType certType : REGISTERED_CERT_TYPES) { - if (Objects.equals(certType.certID(), certID)) { - return true; + 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.certID(), certID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(RegisteredCertType).iterator(); } - return false; } + /* + Write only map for tracking certificates type 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 final NodeCertTypeRegistry CERT_TYPE_REGISTRY = new NodeCertTypeRegistry( + HTTP, + TRANSPORT, + TRANSPORT_CLIENT, + TRANSPORT_SERVER + ); + public CertType(String sslConfigSettingPrefix) { this.sslConfigSettingPrefix = sslConfigSettingPrefix; - } public CertType(final StreamInput in) throws IOException { From be71f9e713a16ac1662a52c84d9822571097c319 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Fri, 30 May 2025 12:41:02 -0700 Subject: [PATCH 15/29] Spotless apply Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 1 - .../api/ssl/CertificatesInfoNodesRequest.java | 4 +- .../security/ssl/config/CertType.java | 25 ++- .../security/ssl/util/SSLConfigConstants.java | 1 + .../security/ssl/SslSettingsManagerTest.java | 194 ++++++++++++------ 5 files changed, 146 insertions(+), 79 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index 57dd05a366..cac8fd8db3 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -15,7 +15,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; 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 0773e34f44..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 @@ -56,9 +56,7 @@ public void writeTo(final StreamOutput out) throws IOException { public ActionRequestValidationException validate() { if (!Strings.isEmpty(certTypeID) && !CertType.CERT_TYPE_REGISTRY.contains(certTypeID)) { final var errorMessage = new ActionRequestValidationException(); - errorMessage.addValidationError( - "wrong certificate type " + certTypeID + ". Please use one of " + CertType.CERT_TYPE_REGISTRY - ); + 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/ssl/config/CertType.java b/src/main/java/org/opensearch/security/ssl/config/CertType.java index 8700671060..2ea8226373 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -18,16 +18,15 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; +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 javax.annotation.Nonnull; - 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_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; /** @@ -43,24 +42,28 @@ public class CertType implements Writeable { public static CertType TRANSPORT = new CertType(SSL_TRANSPORT_PREFIX); public static CertType TRANSPORT_CLIENT = new CertType(SSL_TRANSPORT_CLIENT_PREFIX) { @Override - public String certID() { return "transport_client"; } + public String certID() { + return "transport_client"; + } }; public static CertType TRANSPORT_SERVER = new CertType(SSL_TRANSPORT_SERVER_PREFIX) { @Override - public String certID() { return "transport_server"; } + public String certID() { + return "transport_server"; + } }; public static class NodeCertTypeRegistry implements Iterable { private final Set RegisteredCertType = new HashSet<>(); public NodeCertTypeRegistry(CertType... initialCertTypes) { - for (CertType certType : initialCertTypes){ + for (CertType certType : initialCertTypes) { register(certType); } } public void register(CertType certType) { - if(RegisteredCertType.contains(certType)){ + if (RegisteredCertType.contains(certType)) { throw new IllegalArgumentException("Cert type " + certType + " is already registered in CertType registry"); } RegisteredCertType.add(certType); @@ -91,10 +94,10 @@ public Iterator iterator() { Not all ssl context configurations are known at compile time, so we track newly discovered CertTypes here. */ public static final NodeCertTypeRegistry CERT_TYPE_REGISTRY = new NodeCertTypeRegistry( - HTTP, - TRANSPORT, - TRANSPORT_CLIENT, - TRANSPORT_SERVER + HTTP, + TRANSPORT, + TRANSPORT_CLIENT, + TRANSPORT_SERVER ); public CertType(String sslConfigSettingPrefix) { 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 4c9b3b65e6..d17da0f228 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -164,6 +164,7 @@ public final class SSLConfigConstants { public static String getStringAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); } + public static String getBoolAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); } diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index fc3d4a4ab6..1c3c78116a 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -80,12 +80,21 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID())) - .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); + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_FOO.certID())) + .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.certID() + "_certificate.pem"; private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem"; private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem"; @@ -96,61 +105,74 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID())) - .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); + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.certID())) + .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.certID() + "_certificate.pem"; private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem"; private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem"; static Settings.Builder defaultSettingsBuilder() { return Settings.builder() - .put(Environment.PATH_HOME_SETTING.getKey(), certificatesRule.configRootFolder().toString()) - .put("client.type", "node"); + .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")); + .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")); + .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)); + .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)); + .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.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem"); - writeCertificates("ca_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem"); + writeCertificates( + "ca_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem" + ); + writeCertificates( + "ca_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", + "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem" + ); } static void writeCertificates(final String trustedFileName, final String accessFileName, final String accessPkFileName) @@ -175,10 +197,26 @@ public void failsIfNoSslSet() { @Test public void testFailsIfNoConfigDefine() { - assertThrows(OpenSearchException.class, () -> 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()))); + assertThrows( + OpenSearchException.class, + () -> 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 @@ -258,11 +296,13 @@ public void configFailsIfBothPemAndJDKSettingsWereSet() { ) ); configFailsIfBothPemAndJDKSettingsWereSet( - defaultSettingsBuilder().put(ENABLE_FOO_SETTINGS_BUILDER), + 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), + 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)) + getStringAffixKeyForCertType(SECURITY_SSL_AUX_PEMTRUSTEDCAS_FILEPATH, MOCK_AUX_CERT_TYPE_FOO) + ) ); } @@ -325,7 +365,10 @@ 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()); - securitySettings.setString(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "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); @@ -366,7 +409,10 @@ public void loadConfigurationAndBuildSslContextForSslOnlyMode() { ); assertThat( "Built Server SSL context for AUX FOO", - sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO).map(SslContextHandler::sslContext).map(SslContext::isServer).orElse(false) + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) ); } @@ -414,17 +460,25 @@ 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()); - 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()); + 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() + settingsBuilder.put( + Settings.builder() .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.certID(), MOCK_AUX_CERT_TYPE_FOO.certID())) - .build()); + .build() + ); final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settingsBuilder.build())); @@ -434,30 +488,36 @@ public void loadConfigurationAndBuildSslContextsMultipleAuxTransports() { 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) + "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) + "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) + "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) + "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) + "Built Client SSL context for Transport Client", + sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) + .map(SslContextHandler::sslContext) + .map(SslContext::isClient) + .orElse(false) ); } @@ -467,7 +527,10 @@ 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()); + securitySettings.setString( + MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + "pemkey_password_secure", + certificatesRule.privateKeyPassword() + ); final var settingsBuilder = defaultSettingsBuilder().setSecureSettings(securitySettings); withTransportSslSettings(settingsBuilder); @@ -486,7 +549,10 @@ public void loadConfigurationAndBuildSslContexts() { ); assertThat( "Built Server SSL context for AUX", - sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO).map(SslContextHandler::sslContext).map(SslContext::isServer).orElse(false) + sslSettingsManager.sslContextHandler(MOCK_AUX_CERT_TYPE_FOO) + .map(SslContextHandler::sslContext) + .map(SslContext::isServer) + .orElse(false) ); assertThat( "Built Server SSL context for Transport", From ac4674ba5b23544819262790f84133f600606a68 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 3 Jun 2025 11:43:59 -0700 Subject: [PATCH 16/29] Fix CertificatesRestApiIntegrationTests. Certificate structure was never checked in this test suite due to the usage of CertType.HTTP.name().toUpperCase. Which does not match previous convention of representing certificate names in lowercase. Additionally integ tests configure a subject AND root certificate on each node. Fixing certificate validation here to handle root cert case. Additionally on board these tests to the new CertType class. Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index cac8fd8db3..fb353dbc1c 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -16,12 +16,14 @@ import java.util.List; import java.util.Map; import java.util.StringJoiner; +import java.util.function.Consumer; import java.util.stream.Collectors; import com.carrotsearch.randomizedtesting.RandomizedContext; 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; @@ -29,6 +31,7 @@ import org.opensearch.test.framework.cluster.LocalOpenSearchCluster; import org.opensearch.test.framework.cluster.TestRestClient; +import static junit.framework.TestCase.fail; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,10 +40,9 @@ import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; 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( @@ -87,13 +89,13 @@ 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 @@ -105,23 +107,25 @@ private void verifyTimeoutRequest(final TestRestClient client) throws Exception ok(() -> client.get(sslCertsPath() + "?timeout=0")); } - private void verifySSLCertsInfo(final TestRestClient client) throws Exception { - List certTypes = new ArrayList<>(); - for (CertType cert : CertType.CERT_TYPE_REGISTRY) { - certTypes.add(cert); - } - assertSSLCertsInfo(localCluster.nodes(), certTypes, 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, certTypes, ok(() -> client.get(sslCertsPath(nodeIds)))); - } - final var randomCertType = randomFrom(certTypes); - assertSSLCertsInfo( - localCluster.nodes(), - List.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( @@ -131,48 +135,52 @@ private void assertSSLCertsInfo( ) { 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)) { - final var httpCertificates = certificates.get(CertType.HTTP.certID()); - assertThat(prettyStringBody, httpCertificates.isArray()); - assertThat(prettyStringBody, httpCertificates.size(), is(1)); - verifyCertsJson(n.nodeNumber(), httpCertificates.get(0)); - } - if (expectedCertTypes.contains(CertType.TRANSPORT_CLIENT)) { - final var transportCertificates = certificates.get(CertType.TRANSPORT.certID()); + /* + Expect each node transport configured with root and issued cert. + */ + for (CertType expectCert : expectedCertTypes) { + final JsonNode transportCertificates = certificates.get(expectCert.certID()); 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 +201,4 @@ public List randomSubsetOf(int size, Collection collection) { Collections.shuffle(tempList, RandomizedContext.current().getRandom()); return tempList.subList(0, size); } - } From 8555ebbe6ba159e06fa0c3ca0a25ab73a899fdb7 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 3 Jun 2025 13:34:03 -0700 Subject: [PATCH 17/29] CertType to store user cert id. CertType needs to discretely store the unique identifier for a set of certs which the user will use in CertificatesInfo API. Previously inferred from the setting prefix, but transport client and transport server are problematic for this model and cannot be changed for bwc. Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 3 +- .../dlic/rest/api/ssl/CertificatesInfo.java | 2 +- .../TransportCertificatesInfoNodesAction.java | 2 +- .../security/ssl/SslSettingsManager.java | 22 +-- .../security/ssl/config/CertType.java | 143 ++++++++++-------- .../security/ssl/config/SslParameters.java | 4 +- .../security/ssl/util/SSLConfigConstants.java | 4 +- .../SslSettingsManagerReloadListenerTest.java | 10 +- .../security/ssl/SslSettingsManagerTest.java | 30 ++-- 9 files changed, 114 insertions(+), 106 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index fb353dbc1c..194559753a 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Map; import java.util.StringJoiner; -import java.util.function.Consumer; import java.util.stream.Collectors; import com.carrotsearch.randomizedtesting.RandomizedContext; @@ -151,7 +150,7 @@ private void assertSSLCertsInfo( Expect each node transport configured with root and issued cert. */ for (CertType expectCert : expectedCertTypes) { - final JsonNode transportCertificates = certificates.get(expectCert.certID()); + final JsonNode transportCertificates = certificates.get(expectCert.id()); assertThat(prettyStringBody, transportCertificates.isArray()); assertThat(prettyStringBody, transportCertificates.size(), is(2)); verifyCertsJson(n.nodeNumber(), transportCertificates.get(0)); 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 91a65de845..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 @@ -43,7 +43,7 @@ public void writeTo(StreamOutput out) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject("certificates"); for (Map.Entry> entry : certificates.entrySet()) { - builder.field(entry.getKey().certID(), certificates.get(entry.getKey())); + 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/TransportCertificatesInfoNodesAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ssl/TransportCertificatesInfoNodesAction.java index a8f58daf6a..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 @@ -100,7 +100,7 @@ protected CertificatesNodesResponse.CertificatesNodeResponse nodeOperation(final protected CertificatesInfo loadCertificates(final Optional loadCertType) { Map> certInfos = new HashMap<>(); for (CertType certType : CertType.CERT_TYPE_REGISTRY) { - if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.certID())) { + if (loadCertType.isEmpty() || loadCertType.get().equalsIgnoreCase(certType.id())) { List certs = sslSettingsManager.sslContextHandler(certType) .map(SslContextHandler::certificates) .map(this::certificatesDetails) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index ffc244adeb..39d48a2f35 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -119,7 +119,7 @@ private Map buildSslContexts(final Environment envi Optional.ofNullable(configurations.get(cert)) .ifPresentOrElse( sslConfiguration -> contexts.put(cert, new SslContextHandler(sslConfiguration)), - () -> LOGGER.warn("SSL Configuration for {} Layer hasn't been set", cert.certID()) + () -> LOGGER.warn("SSL Configuration for {} Layer hasn't been set", cert.id()) ); }); return contexts.build(); @@ -129,12 +129,12 @@ public synchronized void reloadSslContext(final CertType certType) { sslContextHandler(certType).ifPresentOrElse(sscContextHandler -> { try { if (sscContextHandler.reloadSslContext()) { - LOGGER.info("{} SSL context reloaded", certType.certID()); + LOGGER.info("{} SSL context reloaded", certType.id()); } } catch (CertificateException e) { throw new OpenSearchException(e); } - }, () -> LOGGER.error("Missing SSL Context for {}", certType.certID())); + }, () -> LOGGER.error("Missing SSL Context for {}", certType.id())); } private Map loadConfigurations(final Environment environment) { @@ -164,8 +164,8 @@ private Map loadConfigurations(final Environment env auxCert, new SslConfiguration(auxSslParameters, auxTrustAndKeyStore.v1(), auxTrustAndKeyStore.v2()) ); - LOGGER.info("TLS {} Provider : {}", auxCert.certID(), auxSslParameters.provider()); - LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.certID(), auxSslParameters.allowedProtocols()); + LOGGER.info("TLS {} Provider : {}", auxCert.id(), auxSslParameters.provider()); + LOGGER.info("Enabled TLS protocols for {} layer : {}", auxCert.id(), auxSslParameters.allowedProtocols()); } } @@ -291,10 +291,10 @@ private void validateSettings(final CertType certType, final Settings settings, } else { throw new OpenSearchException( "Wrong " - + certType.certID() + + 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.certID() + + certType.id() + " layer" ); } @@ -316,7 +316,7 @@ private void validatePemStoreSettings(CertType transportType, final Settings set if (!transportSettings.hasValue(PEM_CERT_FILEPATH) || !transportSettings.hasValue(PEM_KEY_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.certID().toLowerCase(Locale.ROOT) + + transportType.id().toLowerCase(Locale.ROOT) + " SSL configuration. " + String.join(", ", transportSettings.get(PEM_CERT_FILEPATH), transportSettings.get(PEM_KEY_FILEPATH)) + " must be set" @@ -325,7 +325,7 @@ private void validatePemStoreSettings(CertType transportType, final Settings set if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(PEM_TRUSTED_CAS_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.certID().toLowerCase(Locale.ROOT) + + transportType.id().toLowerCase(Locale.ROOT) + " SSL configuration. " + PEM_TRUSTED_CAS_FILEPATH + " must be set if client auth is required" @@ -349,7 +349,7 @@ private void validateKeyStoreSettings(CertType transportType, final Settings set if (!transportSettings.hasValue(KEYSTORE_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.certID().toLowerCase(Locale.ROOT) + + transportType.id().toLowerCase(Locale.ROOT) + " SSL configuration. " + transportSettings.get(KEYSTORE_FILEPATH) + " must be set" @@ -358,7 +358,7 @@ private void validateKeyStoreSettings(CertType transportType, final Settings set if (clientAuth == ClientAuth.REQUIRE && !transportSettings.hasValue(TRUSTSTORE_FILEPATH)) { throw new OpenSearchException( "Wrong " - + transportType.certID().toLowerCase(Locale.ROOT) + + transportType.id().toLowerCase(Locale.ROOT) + " SSL configuration. " + TRUSTSTORE_FILEPATH + " must be set if client auth is required" 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 2ea8226373..fd2ed29063 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -30,29 +30,76 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_SERVER_PREFIX; /** - * CertTypes have a 1-to-1 relationship with ssl contexts and identify - * the setting prefix under which configuration settings are located. - * CertTypes are uniquely identified by a `certID` which is used as the key for registering CertTypes on a node - * and fetching certificate info through a CertificatesInfoNodesRequest. + * 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 sslConfigSettingPrefix; + private final String certSettingPrefix; + private final String certID; + + /** + * 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 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) { - @Override - public String certID() { - return "transport_client"; - } - }; - public static CertType TRANSPORT_SERVER = new CertType(SSL_TRANSPORT_SERVER_PREFIX) { - @Override - public String certID() { - return "transport_server"; - } - }; + public CertType(String certSettingPrefix, String certID) { + this.certSettingPrefix = certSettingPrefix; + this.certID = certID; + } + + public CertType(final StreamInput in) throws IOException { + this.certSettingPrefix = in.readString(); + this.certID = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(this.certSettingPrefix); + out.writeString(this.certID); + } + + 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<>(); @@ -75,7 +122,7 @@ public boolean contains(CertType certType) { public boolean contains(String certID) { for (CertType certType : RegisteredCertType) { - if (Objects.equals(certType.certID(), certID)) { + if (Objects.equals(certType.id(), certID)) { return true; } } @@ -90,54 +137,16 @@ public Iterator iterator() { } /* - Write only map for tracking certificates type discovered and registered on a node. - Not all ssl context configurations are known at compile time, so we track newly discovered CertTypes here. + 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 + HTTP, + TRANSPORT, + TRANSPORT_CLIENT, + TRANSPORT_SERVER ); - - public CertType(String sslConfigSettingPrefix) { - this.sslConfigSettingPrefix = sslConfigSettingPrefix; - } - - public CertType(final StreamInput in) throws IOException { - this.sslConfigSettingPrefix = in.readString(); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(sslConfigSettingPrefix); - } - - public String sslSettingPrefix() { - return sslConfigSettingPrefix; - } - - public String certID() { - String[] parts = sslConfigSettingPrefix.split("\\."); - String id = parts[parts.length - 1]; - return id.toLowerCase(Locale.ROOT); - } - - @Override - public String toString() { - return this.certID(); - } - - @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.certID().equals(certType.certID()); - } - - @Override - public int hashCode() { - return Objects.hash(this.certID()); - } } 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 85acc8c07f..169de00ae3 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslParameters.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslParameters.java @@ -166,10 +166,10 @@ public SslParameters load() { validateCertDNsOnReload(sslConfigSettings) ); if (sslParameters.allowedProtocols().isEmpty()) { - throw new OpenSearchSecurityException("No ssl protocols for " + certType.certID() + " layer"); + throw new OpenSearchSecurityException("No ssl protocols for " + certType.id() + " layer"); } if (sslParameters.allowedCiphers().isEmpty()) { - throw new OpenSearchSecurityException("No valid cipher suites for " + certType.certID() + " 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 d17da0f228..d2c58e9796 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -162,11 +162,11 @@ public final class SSLConfigConstants { // helper to resolve setting key for CertType namespace public static String getStringAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { - return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); + return affix.getConcreteSettingForNamespace(certType.id()).getKey(); } public static String getBoolAffixKeyForCertType(Setting.AffixSetting affix, CertType certType) { - return affix.getConcreteSettingForNamespace(certType.certID()).getKey(); + return affix.getConcreteSettingForNamespace(certType.id()).getKey(); } /** diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java index 220cb3f115..a6d34b2bf5 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java @@ -107,7 +107,7 @@ public void testReloadsSslContextOnPemStoreFilesChangedForHttp() throws Exceptio public void testReloadsSslContextOnPemStoreFilesChangedForAux() throws Exception { Settings.Builder settings = defaultSettingsBuilder().putList( AUX_TRANSPORT_TYPES_SETTING.getKey(), - List.of(MOCK_AUX_CERT_TYPE_FOO.certID()) + List.of(MOCK_AUX_CERT_TYPE_FOO.id()) ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); reloadSslContextOnPemFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); } @@ -126,7 +126,7 @@ public void testReloadsSslContextOnJdkStoreFilesChangedForHttp() throws Exceptio public void testReloadsSslContextOnJdkStoreFilesChangedForAux() throws Exception { Settings.Builder settings = defaultSettingsBuilder().putList( AUX_TRANSPORT_TYPES_SETTING.getKey(), - List.of(MOCK_AUX_CERT_TYPE_FOO.certID()) + List.of(MOCK_AUX_CERT_TYPE_FOO.id()) ).put(MOCK_AUX_CERT_TYPE_FOO.sslSettingPrefix() + ENABLED, true); reloadSslContextOnJdkStoreFilesChangedForTransportType(MOCK_AUX_CERT_TYPE_FOO, settings); } @@ -143,7 +143,7 @@ private void reloadSslContextOnJdkStoreFilesChangedForTransportType(CertType cer final String trustStoreTypeSetting = settingPrefix + TRUSTSTORE_TYPE; final String keyStorePathSetting = settingPrefix + KEYSTORE_FILEPATH; final String keyStoreTypeSetting = settingPrefix + KEYSTORE_TYPE; - final String certTypeFilePrefix = certType.certID().toLowerCase(Locale.ROOT); + final String certTypeFilePrefix = certType.id().toLowerCase(Locale.ROOT); final var keyStorePassword = randomAsciiAlphanumOfLength(10); final var secureSettings = new MockSecureSettings(); secureSettings.setString(settingPrefix + "truststore_password_secure", keyStorePassword); @@ -186,7 +186,7 @@ private void reloadSslContextOnPemFilesChangedForTransportType(CertType certType 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.certID().toLowerCase(Locale.ROOT); + final String certTypeFilePrefix = certType.id().toLowerCase(Locale.ROOT); MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(settingPrefix + "pemkey_password_secure", certificatesRule.privateKeyPassword()); reloadSslContextOnFilesChanged( @@ -214,7 +214,7 @@ private void reloadSslContextOnPemFilesChangedForTransportType(CertType certType private void reloadSslContextOnFilesChanged(CertType certType, final Settings settings, final CertificatesWriter certificatesWriter) throws Exception { - final String certNamePrefix = certType.certID().toLowerCase(Locale.ROOT); + final String certNamePrefix = certType.id().toLowerCase(Locale.ROOT); final var defaultCertificates = generateCertificates(); var defaultKeyPair = defaultCertificates.v1(); var caCertificate = defaultCertificates.v2().v1(); diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index 1c3c78116a..1844b55cdb 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -80,7 +80,7 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID())) + .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( @@ -95,9 +95,9 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_FOO_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_FOO_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem"; + 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 @@ -105,7 +105,7 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID())) + .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( @@ -120,9 +120,9 @@ public class SslSettingsManagerTest extends RandomizedTest { 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.certID() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_BAR_PEMCERT_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem"; - private static final String MOCK_AUX_CERT_TYPE_BAR_PEMKEY_NAME = "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem"; + 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() @@ -164,14 +164,14 @@ 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.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_FOO.certID() + "_certificate_pk.pem" + "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.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate.pem", - "access_" + MOCK_AUX_CERT_TYPE_BAR.certID() + "_certificate_pk.pem" + "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" ); } @@ -476,7 +476,7 @@ public void loadConfigurationAndBuildSslContextsMultipleAuxTransports() { withAuxFooSslSettings(settingsBuilder); settingsBuilder.put( Settings.builder() - .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.certID(), MOCK_AUX_CERT_TYPE_FOO.certID())) + .putList(AUX_TRANSPORT_TYPES_SETTING.getKey(), List.of(MOCK_AUX_CERT_TYPE_BAR.id(), MOCK_AUX_CERT_TYPE_FOO.id())) .build() ); From b00554c582e3e32cc66725824f2e651c0041a2ad Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 10 Jun 2025 11:51:23 -0700 Subject: [PATCH 18/29] Update AuxTransport import after core changes. Signed-off-by: Finn Carroll --- .../java/org/opensearch/security/ssl/SslSettingsManager.java | 2 +- .../security/ssl/SslSettingsManagerReloadListenerTest.java | 2 +- .../org/opensearch/security/ssl/SslSettingsManagerTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 39d48a2f35..2f68011050 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -42,7 +42,6 @@ import io.netty.handler.ssl.ClientAuth; -import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; 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; @@ -75,6 +74,7 @@ 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 { diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java index a6d34b2bf5..8f02f16613 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java @@ -40,7 +40,6 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; -import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; import static org.opensearch.security.ssl.CertificatesUtils.privateKeyToPemObject; import static org.opensearch.security.ssl.CertificatesUtils.writePemContent; import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED; @@ -53,6 +52,7 @@ 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 { diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index 1844b55cdb..1366f16da9 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -31,7 +31,6 @@ import io.netty.handler.ssl.SslContext; import static org.hamcrest.MatcherAssert.assertThat; -import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; 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; @@ -68,6 +67,7 @@ import static org.opensearch.security.ssl.util.SSLConfigConstants.getStringAffixKeyForCertType; import static org.opensearch.security.support.ConfigConstants.SECURITY_SSL_ONLY; import static org.junit.Assert.assertThrows; +import static org.opensearch.transport.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; public class SslSettingsManagerTest extends RandomizedTest { From ea243b6f879c12daa5b5702af52f4b8d17e758b7 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 17 Jun 2025 14:42:55 -0700 Subject: [PATCH 19/29] Spotless apply Signed-off-by: Finn Carroll --- .../CertificatesRestApiIntegrationTest.java | 36 ++++++++++--------- .../security/ssl/config/CertType.java | 8 ++--- .../security/ssl/SslSettingsManagerTest.java | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index 194559753a..748b036d16 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -30,13 +30,13 @@ import org.opensearch.test.framework.cluster.LocalOpenSearchCluster; import org.opensearch.test.framework.cluster.TestRestClient; -import static junit.framework.TestCase.fail; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; 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"; @@ -88,7 +88,11 @@ public void forbiddenForAdminUser() throws Exception { @Test public void availableForTlsAdmin() throws Exception { - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT))); + withUser( + ADMIN_USER_NAME, + localCluster.getAdminCertificate(), + verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)) + ); } @Test @@ -112,14 +116,16 @@ private CheckedConsumer verifySSLCertsInfo(List 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(",")); + 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))) + 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()); @@ -161,22 +167,18 @@ private void assertSSLCertsInfo( 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("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)) + 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)) + 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)); 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 fd2ed29063..3eaf529e39 100644 --- a/src/main/java/org/opensearch/security/ssl/config/CertType.java +++ b/src/main/java/org/opensearch/security/ssl/config/CertType.java @@ -144,9 +144,9 @@ public Iterator iterator() { 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 + HTTP, + TRANSPORT, + TRANSPORT_CLIENT, + TRANSPORT_SERVER ); } diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java index 1366f16da9..a5d9c8002a 100644 --- a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerTest.java @@ -66,8 +66,8 @@ 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.junit.Assert.assertThrows; import static org.opensearch.transport.AuxTransport.AUX_TRANSPORT_TYPES_SETTING; +import static org.junit.Assert.assertThrows; public class SslSettingsManagerTest extends RandomizedTest { From ca3ddf9ad8273e899241645623e28827db9196bb Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Tue, 24 Jun 2025 13:30:39 -0700 Subject: [PATCH 20/29] Extract javax SSLContext as a more generic type for aux transports. Signed-off-by: Finn Carroll --- .../ssl/OpenSearchSecureSettingsFactory.java | 17 ++++++++++++++++- .../security/ssl/SslContextHandler.java | 14 ++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java index c228c27470..d547145c87 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java @@ -16,10 +16,12 @@ 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; +import org.opensearch.common.annotation.ExperimentalApi; import org.opensearch.common.settings.Settings; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.netty4.ssl.SecureNetty4HttpServerTransport; @@ -35,6 +37,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 +235,18 @@ 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::sslContext); + } + + @Override + public Optional parameters() { + return SecureAuxTransportSettingsProvider.super.parameters(); + } + }); } } diff --git a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java index 1595ae1539..31b56ce9bb 100644 --- a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java @@ -16,11 +16,14 @@ import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; +import java.util.Optional; 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 io.netty.handler.ssl.JdkSslContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -63,8 +66,15 @@ public SslConfiguration sslConfiguration() { return sslConfiguration; } - SslContext sslContext() { - return sslContext; + /** + * Only JDK provider is supported, so we expect only JdkSslContext. + * @return null if context cannot be fetched from io.netty.handler.ssl.SslContext child type. + */ + public SSLContext sslContext() { + if (sslContext instanceof JdkSslContext) { + return ((JdkSslContext) sslContext).context(); + } + return null; } public Stream certificates() { From e09e6c89f17f494a6c2eae91f9efb3ecdba457ce Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 25 Jun 2025 11:35:44 -0700 Subject: [PATCH 21/29] Add client auth + cipher suites to aux secure settings. Signed-off-by: Finn Carroll --- .../ssl/OpenSearchSecureSettingsFactory.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java index d547145c87..8d1e301ce4 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java @@ -244,8 +244,23 @@ public Optional buildSecureAuxServerTransportContext(Settings settin } @Override - public Optional parameters() { - return SecureAuxTransportSettingsProvider.super.parameters(); + 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()); + } + }); } }); } From 9614f9e92a74eb01cb20dd5737042953fffd6ea3 Mon Sep 17 00:00:00 2001 From: Finn Carroll Date: Wed, 25 Jun 2025 11:51:53 -0700 Subject: [PATCH 22/29] Add back sslContext() to SslContextHandler. Used in tests. Signed-off-by: Finn Carroll --- .../ssl/OpenSearchSecureSettingsFactory.java | 2 +- .../opensearch/security/ssl/SslContextHandler.java | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java index 8d1e301ce4..7c777aff30 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java @@ -240,7 +240,7 @@ public Optional getSecureAuxTransportSetting @Override public Optional buildSecureAuxServerTransportContext(Settings settings, AuxTransport transport) { CertType auxCertType = new CertType(transport.settingKey()); - return sslSettingsManager.sslContextHandler(auxCertType).map(SslContextHandler::sslContext); + return sslSettingsManager.sslContextHandler(auxCertType).map(SslContextHandler::tryFetchSSLContext); } @Override diff --git a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java index 31b56ce9bb..df2459e2b0 100644 --- a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java @@ -67,16 +67,24 @@ public SslConfiguration sslConfiguration() { } /** - * Only JDK provider is supported, so we expect only JdkSslContext. - * @return null if context cannot be fetched from io.netty.handler.ssl.SslContext child type. + * 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 sslContext() { + public SSLContext tryFetchSSLContext() { if (sslContext instanceof JdkSslContext) { return ((JdkSslContext) sslContext).context(); } return null; } + // public for testing + public SslContext sslContext() { + return sslContext; + } + public Stream certificates() { return Stream.concat(authorityCertificates(), keyMaterialCertificates()) .sorted((c1, c2) -> Boolean.compare(c1.hasPrivateKey(), c2.hasPrivateKey())); From 266af217278fc3569226d501df208a7ca17ffa05 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 25 Jun 2025 19:47:05 -0400 Subject: [PATCH 23/29] WIP on integ test Signed-off-by: Craig Perkins --- build.gradle | 1 + .../framework/cluster/TestGrpcClient.java | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java diff --git a/build.gradle b/build.gradle index e9a4bba83f..509a76f904 100644 --- a/build.gradle +++ b/build.gradle @@ -533,6 +533,7 @@ allprojects { integrationTestImplementation("org.opensearch.plugin:reindex-client:${opensearch_version}"){ exclude(group: 'org.slf4j', module: 'slf4j-api') } + integrationTestImplementation "io.grpc:grpc-netty-shaded:${versions.grpc}" 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/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..5df30fc1b3 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -0,0 +1,86 @@ +package org.opensearch.test.framework.cluster; + +import io.grpc.ManagedChannelBuilder; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class TestGrpcClient { + private static final Logger log = LogManager.getLogger(TestRestClient.class); + + private boolean enableHTTPClientSSL; + private boolean sendHTTPClientCertificate; + private InetSocketAddress nodeHttpAddress; + private RequestConfig requestConfig; + private List
headers = new ArrayList<>(); + private SSLContext sslContext; + + private final InetAddress sourceInetAddress; + + public TestGrpcClient( + InetSocketAddress nodeHttpAddress, + List
headers, + SSLContext sslContext, + InetAddress sourceInetAddress, + boolean enableHTTPClientSSL, + boolean sendHTTPClientCertificate + ) { + this.nodeHttpAddress = nodeHttpAddress; + this.headers.addAll(headers); + this.sslContext = sslContext; + this.sourceInetAddress = sourceInetAddress; + this.enableHTTPClientSSL = enableHTTPClientSSL; + this.sendHTTPClientCertificate = sendHTTPClientCertificate; + } + + public TestRestClient.HttpResponse get(String path, Header... headers) { + return executeRequest(new HttpGet(getHttpServerUri() + "/" + path), headers); + } + + public TestRestClient.HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { + try (CloseableHttpClient httpClient = getHTTPClient()) { + + if (requestSpecificHeaders != null && requestSpecificHeaders.length > 0) { + for (int i = 0; i < requestSpecificHeaders.length; i++) { + Header h = requestSpecificHeaders[i]; + uriRequest.addHeader(h); + } + } + + for (Header header : headers) { + uriRequest.addHeader(header); + } + + TestRestClient.HttpResponse res = new TestRestClient.HttpResponse(httpClient.execute(uriRequest)); + log.debug(res.getBody()); + return res; + } catch (IOException e) { + throw new RestClientException("Error occured during HTTP request execution", e); + } + } + + public final String getHttpServerUri() { + return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); + } + + protected final CloseableHttpClient getHTTPClient() { + // ManagedChannelBuilder.forAddress(host, port); + HttpRoutePlanner routePlanner = Optional.ofNullable(sourceInetAddress).map(LocalAddressRoutePlanner::new).orElse(null); + var factory = new CloseableHttpClientFactory(sslContext, requestConfig, routePlanner, null); + return factory.getHTTPClient(); + } +} From 9ce4390a3e97ca5dbea2571965e0619c9e8cb5cb Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 25 Jun 2025 20:17:05 -0400 Subject: [PATCH 24/29] WIP on integ test with grpc + encryption Signed-off-by: Craig Perkins --- build.gradle | 1 + .../framework/cluster/TestGrpcClient.java | 87 ++++++------------- .../SystemIndexAccessEvaluator.java | 3 + .../ssl/OpenSearchSecureSettingsFactory.java | 5 +- .../security/ssl/SslContextHandler.java | 3 +- 5 files changed, 34 insertions(+), 65 deletions(-) diff --git a/build.gradle b/build.gradle index 509a76f904..1bfa9e9460 100644 --- a/build.gradle +++ b/build.gradle @@ -533,6 +533,7 @@ 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-netty-shaded:${versions.grpc}" integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" integrationTestImplementation 'commons-io:commons-io:2.19.0' diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java index 5df30fc1b3..153cf6f19d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -1,86 +1,53 @@ package org.opensearch.test.framework.cluster; -import io.grpc.ManagedChannelBuilder; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.routing.HttpRoutePlanner; -import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.message.BasicHeader; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import javax.net.ssl.SSLContext; -import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import javax.net.ssl.SSLContext; + +import org.apache.hc.core5.http.Header; +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 io.grpc.ChannelCredentials; +import io.grpc.Grpc; +import io.grpc.ManagedChannel; +import io.grpc.TlsChannelCredentials; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; public class TestGrpcClient { private static final Logger log = LogManager.getLogger(TestRestClient.class); - private boolean enableHTTPClientSSL; - private boolean sendHTTPClientCertificate; private InetSocketAddress nodeHttpAddress; - private RequestConfig requestConfig; private List
headers = new ArrayList<>(); private SSLContext sslContext; private final InetAddress sourceInetAddress; - public TestGrpcClient( - InetSocketAddress nodeHttpAddress, - List
headers, - SSLContext sslContext, - InetAddress sourceInetAddress, - boolean enableHTTPClientSSL, - boolean sendHTTPClientCertificate - ) { + public TestGrpcClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext, InetAddress sourceInetAddress) { this.nodeHttpAddress = nodeHttpAddress; this.headers.addAll(headers); this.sslContext = sslContext; this.sourceInetAddress = sourceInetAddress; - this.enableHTTPClientSSL = enableHTTPClientSSL; - this.sendHTTPClientCertificate = sendHTTPClientCertificate; - } - - public TestRestClient.HttpResponse get(String path, Header... headers) { - return executeRequest(new HttpGet(getHttpServerUri() + "/" + path), headers); - } - - public TestRestClient.HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { - try (CloseableHttpClient httpClient = getHTTPClient()) { - - if (requestSpecificHeaders != null && requestSpecificHeaders.length > 0) { - for (int i = 0; i < requestSpecificHeaders.length; i++) { - Header h = requestSpecificHeaders[i]; - uriRequest.addHeader(h); - } - } - - for (Header header : headers) { - uriRequest.addHeader(header); - } - - TestRestClient.HttpResponse res = new TestRestClient.HttpResponse(httpClient.execute(uriRequest)); - log.debug(res.getBody()); - return res; - } catch (IOException e) { - throw new RestClientException("Error occured during HTTP request execution", e); - } } - public final String getHttpServerUri() { - return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); + public SearchResponse search(SearchRequest request) { + SearchServiceGrpc.SearchServiceBlockingStub client = client(); + return client.search(request); } - protected final CloseableHttpClient getHTTPClient() { - // ManagedChannelBuilder.forAddress(host, port); - HttpRoutePlanner routePlanner = Optional.ofNullable(sourceInetAddress).map(LocalAddressRoutePlanner::new).orElse(null); - var factory = new CloseableHttpClientFactory(sslContext, requestConfig, routePlanner, null); - return factory.getHTTPClient(); + private SearchServiceGrpc.SearchServiceBlockingStub client() { + ChannelCredentials credentials = TlsChannelCredentials.newBuilder() + // You can use your own certificate here .trustManager(new File("cert.pem")) + .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) + .build(); + ManagedChannel channel = Grpc.newChannelBuilderForAddress(nodeHttpAddress.getHostString(), nodeHttpAddress.getPort(), credentials) + .build(); + return SearchServiceGrpc.newBlockingStub(channel); } } 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/OpenSearchSecureSettingsFactory.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java index 7c777aff30..5a01cc553c 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecureSettingsFactory.java @@ -21,7 +21,6 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; -import org.opensearch.common.annotation.ExperimentalApi; import org.opensearch.common.settings.Settings; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.netty4.ssl.SecureNetty4HttpServerTransport; @@ -257,8 +256,8 @@ public Optional clientAuth() { public Collection cipherSuites() { CertType auxCertType = new CertType(transport.settingKey()); return sslSettingsManager.sslConfiguration(auxCertType) - .map(config -> config.sslParameters().allowedCiphers()) - .orElse(Collections.emptyList()); + .map(config -> config.sslParameters().allowedCiphers()) + .orElse(Collections.emptyList()); } }); } diff --git a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java index df2459e2b0..bec40e2c8f 100644 --- a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java @@ -16,20 +16,19 @@ import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; -import java.util.Optional; 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 io.netty.handler.ssl.JdkSslContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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; From c1452557669801288a300f4ae71e44ecbfefa21d Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 25 Jun 2025 20:53:21 -0400 Subject: [PATCH 25/29] WIP Signed-off-by: Craig Perkins --- .../cluster/OpenSearchClientProvider.java | 19 +++++++++++++++++++ .../framework/cluster/TestGrpcClient.java | 17 +++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) 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..e206822b10 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -108,6 +108,10 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade return getRestClient(user.getName(), user.getPassword(), null, headers); } + default TestGrpcClient getGrpcClient(UserCredentialsHolder user) { + return getGrpcClient(user.getName(), user.getPassword()); + } + default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { return getRestHighLevelClient(new UserCredentialsHolder() { @Override @@ -211,6 +215,11 @@ default TestRestClient getRestClient(String user, String password, CertificateDa return getRestClient(useCertificateData, basicAuthHeader); } + default TestGrpcClient getGrpcClient(String user, String password) { + Header basicAuthHeader = getBasicAuthHeader(user, password); + return getGrpcClient(basicAuthHeader.getValue()); + } + /** * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this * method to test non-basic authentication, such as JWT bearer authentication. @@ -232,6 +241,10 @@ default TestRestClient getRestClient(List
headers, CertificateData useCe return createGenericClientRestClient(headers, useCertificateData, null); } + default TestGrpcClient getGrpcClient(String authorizationHeader) { + return createGenericClientGrpcClient(authorizationHeader); + } + default TestRestClient getSecurityDisabledRestClient() { return new TestRestClient(getHttpAddress(), List.of(), getSSLContext(null), null, false, false); } @@ -244,6 +257,12 @@ default TestRestClient createGenericClientRestClient( return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress, true, false); } + default TestGrpcClient createGenericClientGrpcClient( + String authorizationHeader + ) { + return new TestGrpcClient(getHttpAddress(), authorizationHeader, getSSLContext()); + } + 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 index 153cf6f19d..6bf17bfad7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -6,6 +6,7 @@ import java.util.List; import javax.net.ssl.SSLContext; +import io.grpc.Metadata; import org.apache.hc.core5.http.Header; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -23,21 +24,25 @@ 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 List
headers = new ArrayList<>(); + private String authorizationHeader; private SSLContext sslContext; - private final InetAddress sourceInetAddress; - - public TestGrpcClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext, InetAddress sourceInetAddress) { + public TestGrpcClient(InetSocketAddress nodeHttpAddress, String authorizationHeader, SSLContext sslContext) { this.nodeHttpAddress = nodeHttpAddress; - this.headers.addAll(headers); + this.authorizationHeader = authorizationHeader; this.sslContext = sslContext; - this.sourceInetAddress = sourceInetAddress; } 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); } From 8c921171c351372b0c7b0c15263ea81b9d302f86 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 26 Jun 2025 12:10:42 -0400 Subject: [PATCH 26/29] WIP on Secure GRPC integ test Signed-off-by: Craig Perkins --- build.gradle | 6 ++ sample-resource-plugin/build.gradle | 2 + .../opensearch/security/grpc/GrpcTests.java | 70 +++++++++++++++++++ .../test/framework/cluster/LocalCluster.java | 17 +++++ .../cluster/OpenSearchClientProvider.java | 4 +- .../framework/cluster/TestGrpcClient.java | 9 +-- 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java diff --git a/build.gradle b/build.gradle index 1bfa9e9460..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" } } @@ -534,7 +537,10 @@ allprojects { 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/grpc/GrpcTests.java b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java new file mode 100644 index 0000000000..0c92fdd528 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java @@ -0,0 +1,70 @@ +/* + * 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 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.test.framework.TestSecurityConfig.User; +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.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +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); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .plugin(GrpcPlugin.class) + .grpc() + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); + + @Test + public void testRolloverWithLimitedUser() throws IOException { + try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.put("test-index"); + client.postJson("test-index/_doc/1", "{\"field\": \"value\"}"); + } + + TestGrpcClient client = cluster.getGrpcClient(ADMIN_USER); + // 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..bc88ee5209 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,23 @@ public Builder audit(AuditConfiguration auditConfiguration) { return this; } + public Builder grpc() { + 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", "esnode.pem"); + nodeOverrideSettingsBuilder.put( + "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemkey_filepath", + "esnode-key.pem" + ); + nodeOverrideSettingsBuilder.put("plugins.security.ssl.aux.experimental-secure-transport-grpc.clientauth_mode", "REQUIRE"); + nodeOverrideSettingsBuilder.put( + "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemtrustedcas_filepath", + "root-ca.pem" + ); + 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 e206822b10..b17ac17572 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -257,9 +257,7 @@ default TestRestClient createGenericClientRestClient( return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress, true, false); } - default TestGrpcClient createGenericClientGrpcClient( - String authorizationHeader - ) { + default TestGrpcClient createGenericClientGrpcClient(String authorizationHeader) { return new TestGrpcClient(getHttpAddress(), authorizationHeader, getSSLContext()); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java index 6bf17bfad7..368b69284b 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -1,13 +1,8 @@ package org.opensearch.test.framework.cluster; -import java.net.InetAddress; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; import javax.net.ssl.SSLContext; -import io.grpc.Metadata; -import org.apache.hc.core5.http.Header; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,14 +13,14 @@ import io.grpc.ChannelCredentials; import io.grpc.Grpc; import io.grpc.ManagedChannel; +import io.grpc.Metadata; import io.grpc.TlsChannelCredentials; import io.grpc.netty.shaded.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); + Metadata.Key AUTHORIZATION_KEY = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); private InetSocketAddress nodeHttpAddress; private String authorizationHeader; From b18eeb84d5521d9dbfd2f8b348ee1b48a353c959 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 26 Jun 2025 12:32:18 -0400 Subject: [PATCH 27/29] WIP on secure GRPC test Signed-off-by: Craig Perkins --- .../org/opensearch/security/grpc/GrpcTests.java | 10 +++++++--- .../test/framework/cluster/LocalCluster.java | 16 ++++++++++++---- .../test/framework/cluster/TestGrpcClient.java | 4 ++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java index 0c92fdd528..77dacf7dde 100644 --- a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java +++ b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java @@ -23,6 +23,7 @@ import org.opensearch.protobufs.SearchRequestBody; import org.opensearch.protobufs.SearchResponse; 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; @@ -39,17 +40,20 @@ public class GrpcTests { 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.THREE_CLUSTER_MANAGERS) + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .testCertificates(TEST_CERTIFICATES) .anonymousAuth(false) .plugin(GrpcPlugin.class) - .grpc() + .grpc(TEST_CERTIFICATES) .authc(AUTHC_HTTPBASIC_INTERNAL) .users(ADMIN_USER) .build(); @Test - public void testRolloverWithLimitedUser() throws IOException { + public void testSearch() throws IOException { try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { client.put("test-index"); client.postJson("test-index/_doc/1", "{\"field\": \"value\"}"); 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 bc88ee5209..85693256de 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -495,19 +495,27 @@ public Builder audit(AuditConfiguration auditConfiguration) { return this; } - public Builder grpc() { + 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", "esnode.pem"); + 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", - "esnode-key.pem" + testCertificates.getNodeKey(0, "").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", "REQUIRE"); nodeOverrideSettingsBuilder.put( "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemtrustedcas_filepath", - "root-ca.pem" + testCertificates.getRootCertificate().getAbsolutePath() ); return this; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java index 368b69284b..4477484fac 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -46,8 +46,8 @@ private SearchServiceGrpc.SearchServiceBlockingStub client() { // You can use your own certificate here .trustManager(new File("cert.pem")) .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) .build(); - ManagedChannel channel = Grpc.newChannelBuilderForAddress(nodeHttpAddress.getHostString(), nodeHttpAddress.getPort(), credentials) - .build(); + System.out.println("nodeHttpAddress.getHostString():" + nodeHttpAddress); + ManagedChannel channel = Grpc.newChannelBuilderForAddress("127.0.0.1", 9400, credentials).build(); return SearchServiceGrpc.newBlockingStub(channel); } } From 47cb7f2d6a76e2afe6ed3e40299b21291e53a796 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 26 Jun 2025 13:09:25 -0400 Subject: [PATCH 28/29] WIP on grpc test Signed-off-by: Craig Perkins --- .../opensearch/security/grpc/GrpcTests.java | 10 +++---- .../cluster/OpenSearchClientProvider.java | 17 +++-------- .../framework/cluster/TestGrpcClient.java | 28 +++++++++++++------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java index 77dacf7dde..67c918e8e4 100644 --- a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java +++ b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java @@ -29,7 +29,6 @@ import org.opensearch.test.framework.cluster.TestGrpcClient; import org.opensearch.test.framework.cluster.TestRestClient; -import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @@ -45,21 +44,20 @@ public class GrpcTests { @ClassRule public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) .testCertificates(TEST_CERTIFICATES) - .anonymousAuth(false) .plugin(GrpcPlugin.class) .grpc(TEST_CERTIFICATES) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER) + .sslOnly(true) .build(); @Test public void testSearch() throws IOException { - try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + try (TestRestClient client = cluster.getRestClient()) { client.put("test-index"); client.postJson("test-index/_doc/1", "{\"field\": \"value\"}"); } - TestGrpcClient client = cluster.getGrpcClient(ADMIN_USER); + // TODO this is SSO 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(); 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 b17ac17572..0bda3bd513 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -108,10 +108,6 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade return getRestClient(user.getName(), user.getPassword(), null, headers); } - default TestGrpcClient getGrpcClient(UserCredentialsHolder user) { - return getGrpcClient(user.getName(), user.getPassword()); - } - default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { return getRestHighLevelClient(new UserCredentialsHolder() { @Override @@ -215,11 +211,6 @@ default TestRestClient getRestClient(String user, String password, CertificateDa return getRestClient(useCertificateData, basicAuthHeader); } - default TestGrpcClient getGrpcClient(String user, String password) { - Header basicAuthHeader = getBasicAuthHeader(user, password); - return getGrpcClient(basicAuthHeader.getValue()); - } - /** * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this * method to test non-basic authentication, such as JWT bearer authentication. @@ -241,8 +232,8 @@ default TestRestClient getRestClient(List
headers, CertificateData useCe return createGenericClientRestClient(headers, useCertificateData, null); } - default TestGrpcClient getGrpcClient(String authorizationHeader) { - return createGenericClientGrpcClient(authorizationHeader); + default TestGrpcClient getGrpcClient(TestCertificates testCertificates) { + return createGenericClientGrpcClient(testCertificates); } default TestRestClient getSecurityDisabledRestClient() { @@ -257,8 +248,8 @@ default TestRestClient createGenericClientRestClient( return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress, true, false); } - default TestGrpcClient createGenericClientGrpcClient(String authorizationHeader) { - return new TestGrpcClient(getHttpAddress(), authorizationHeader, getSSLContext()); + default TestGrpcClient createGenericClientGrpcClient(TestCertificates testCertificates) { + return new TestGrpcClient(getHttpAddress(), "", getSSLContext(), testCertificates); } default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java index 4477484fac..a438afb3ee 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -1,5 +1,6 @@ package org.opensearch.test.framework.cluster; +import java.io.IOException; import java.net.InetSocketAddress; import javax.net.ssl.SSLContext; @@ -9,13 +10,13 @@ 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.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; public class TestGrpcClient { private static final Logger log = LogManager.getLogger(TestRestClient.class); @@ -25,11 +26,18 @@ public class TestGrpcClient { private InetSocketAddress nodeHttpAddress; private String authorizationHeader; private SSLContext sslContext; + private TestCertificates testCertificates; - public TestGrpcClient(InetSocketAddress nodeHttpAddress, String authorizationHeader, SSLContext sslContext) { + 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) { @@ -42,12 +50,16 @@ public SearchResponse search(SearchRequest request) { } private SearchServiceGrpc.SearchServiceBlockingStub client() { - ChannelCredentials credentials = TlsChannelCredentials.newBuilder() - // You can use your own certificate here .trustManager(new File("cert.pem")) - .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) - .build(); - System.out.println("nodeHttpAddress.getHostString():" + nodeHttpAddress); - ManagedChannel channel = Grpc.newChannelBuilderForAddress("127.0.0.1", 9400, credentials).build(); + ChannelCredentials credentials = null; + try { + credentials = TlsChannelCredentials.newBuilder() + // You can use your own certificate here .trustManager(new File("cert.pem")) + .trustManager(testCertificates.getRootCertificate()) + .build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + ManagedChannel channel = Grpc.newChannelBuilderForAddress("localhost", 9400, credentials).build(); return SearchServiceGrpc.newBlockingStub(channel); } } From 264ad0cd1bb40a1931de3ffc9023a036476342e9 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 26 Jun 2025 15:25:39 -0400 Subject: [PATCH 29/29] Set clientmode to optional and show working test Signed-off-by: Craig Perkins --- .../org/opensearch/security/grpc/GrpcTests.java | 11 ++++++++--- .../test/framework/cluster/LocalCluster.java | 4 ++-- .../test/framework/cluster/TestGrpcClient.java | 14 +++++--------- .../security/ssl/OpenSearchSecuritySSLPlugin.java | 2 ++ .../security/ssl/util/SSLConfigConstants.java | 5 +++++ 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java index 67c918e8e4..6e60b9a2a8 100644 --- a/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java +++ b/src/integrationTest/java/org/opensearch/security/grpc/GrpcTests.java @@ -10,6 +10,7 @@ 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; @@ -22,6 +23,7 @@ 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; @@ -43,20 +45,23 @@ public class GrpcTests { @ClassRule public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) - .testCertificates(TEST_CERTIFICATES) + .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()) { - client.put("test-index"); + TestRestClient.HttpResponse response = client.put("test-index"); + response.assertStatusCode(200); client.postJson("test-index/_doc/1", "{\"field\": \"value\"}"); } - // TODO this is SSO Only test, but eventually a test with authc should be added as well + // 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(); 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 85693256de..c37d59baaf 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -506,13 +506,13 @@ public Builder grpc(TestCertificates testCertificates) { ); nodeOverrideSettingsBuilder.put( "plugins.security.ssl.aux.experimental-secure-transport-grpc.pemkey_filepath", - testCertificates.getNodeKey(0, "").getAbsolutePath() + 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", "REQUIRE"); + 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() diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java index a438afb3ee..f496b20e58 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestGrpcClient.java @@ -1,6 +1,5 @@ package org.opensearch.test.framework.cluster; -import java.io.IOException; import java.net.InetSocketAddress; import javax.net.ssl.SSLContext; @@ -17,6 +16,7 @@ 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); @@ -51,14 +51,10 @@ public SearchResponse search(SearchRequest request) { private SearchServiceGrpc.SearchServiceBlockingStub client() { ChannelCredentials credentials = null; - try { - credentials = TlsChannelCredentials.newBuilder() - // You can use your own certificate here .trustManager(new File("cert.pem")) - .trustManager(testCertificates.getRootCertificate()) - .build(); - } catch (IOException e) { - throw new RuntimeException(e); - } + 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/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 5a3549e16d..6094cb5cb7 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -101,6 +101,7 @@ 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; @@ -654,6 +655,7 @@ public List> getSettings() { 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, 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 d2c58e9796..dd9b6de292 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -137,6 +137,11 @@ public final class SSLConfigConstants { 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,