From 8d5bde94e0597475c8a7d5f9bb56fd219bd5dafe Mon Sep 17 00:00:00 2001 From: CatiaCorreia Date: Thu, 27 Nov 2025 16:13:26 +0000 Subject: [PATCH 1/3] Add support for LDAPS testing Add to EmbeddedLdapProperties: -boolean ldaps -String sslBundleName Create setLdapsListener method to create and set the LDAPS listener for the server. Add test for new embedded LDAP setup. Issue#48060 Signed-off-by: CatiaCorreia --- .../EmbeddedLdapAutoConfiguration.java | 33 ++++++++++++++++-- .../embedded/EmbeddedLdapProperties.java | 26 ++++++++++++++ .../EmbeddedLdapAutoConfigurationTests.java | 30 +++++++++++++++- .../ldap/autoconfigure/embedded/keystore.jks | Bin 0 -> 32 bytes .../autoconfigure/embedded/keystore.pkcs12 | Bin 0 -> 103 bytes .../ldap/autoconfigure/embedded/rsa-cert.pem | 23 ++++++++++++ .../ldap/autoconfigure/embedded/rsa-key.pem | 28 +++++++++++++++ 7 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks create mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 create mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/rsa-cert.pem create mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/rsa-key.pem diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java index 823ab1dd3e59..3fc2ea3f689b 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java @@ -22,10 +22,15 @@ import java.util.List; import java.util.Map; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocketFactory; + import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.schema.Schema; import com.unboundid.ldif.LDIFReader; import org.jspecify.annotations.Nullable; @@ -38,6 +43,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; @@ -47,6 +53,7 @@ import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; import org.springframework.boot.ldap.autoconfigure.LdapProperties; import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; @@ -100,9 +107,13 @@ InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) t config.addAdditionalBindCredentials(username, password); } setSchema(config); - InMemoryListenerConfig listenerConfig = InMemoryListenerConfig.createLDAPConfig("LDAP", - this.embeddedProperties.getPort()); - config.setListenerConfigs(listenerConfig); + if (this.embeddedProperties.isLdaps()) { + this.setLdapsListener(applicationContext, config); + } + else { + config + .setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort())); + } this.server = new InMemoryDirectoryServer(config); importLdif(this.server, applicationContext); this.server.startListening(); @@ -137,6 +148,22 @@ private void setSchema(InMemoryDirectoryServerConfig config, Resource resource) } } + @ConditionalOnBean(SslBundles.class) + private void setLdapsListener(ApplicationContext applicationContext, InMemoryDirectoryServerConfig config) + throws LDAPException { + if (StringUtils.hasText(this.embeddedProperties.getSslBundleName())) { + SslBundles sslBundles = applicationContext.getBean(SslBundles.class); + SSLContext sslContext = sslBundles.getBundle(this.embeddedProperties.getSslBundleName()).createSslContext(); + SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); + SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, + this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory)); + } + else { + throw new LDAPException(ResultCode.PARAM_ERROR, "SslBundleName property not specified"); + } + } + private void importLdif(InMemoryDirectoryServer server, ApplicationContext applicationContext) { String location = this.embeddedProperties.getLdif(); if (StringUtils.hasText(location)) { diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java index d348491af48a..e5b480a94edf 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java @@ -57,6 +57,16 @@ public class EmbeddedLdapProperties { */ private String ldif = "classpath:schema.ldif"; + /** + * Listener type. + */ + private boolean ldaps; + + /** + * Embedded LDAPS client SSL bundle name. + */ + @Nullable private String sslBundleName; + /** * Schema validation. */ @@ -94,6 +104,22 @@ public void setLdif(String ldif) { this.ldif = ldif; } + public boolean isLdaps() { + return this.ldaps; + } + + public void setLdaps(boolean bool) { + this.ldaps = bool; + } + + public @Nullable String getSslBundleName() { + return this.sslBundleName; + } + + public void setSslBundleName(@Nullable String sslBundleName) { + this.sslBundleName = sslBundleName; + } + public Validation getValidation() { return this.validation; } diff --git a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java index 14b4d020638c..00e0d23a6978 100644 --- a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java +++ b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java @@ -20,6 +20,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.sdk.BindResult; @@ -32,7 +34,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; +import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.util.TestPropertyValues; @@ -54,7 +58,7 @@ class EmbeddedLdapAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class, SslAutoConfiguration.class)); @Test void testSetDefaultPort() { @@ -66,6 +70,30 @@ void testSetDefaultPort() { }); } + @Test + void testLdapsVersion() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; + propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1"); + propertyValues.add("spring.ssl.bundle.pem.test.key.password=secret1"); + propertyValues.add("spring.ssl.bundle.pem.test.keystore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.test.keystore.keystore.private-key=" + location + "rsa-key.pem"); + propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ldap.embedded.port:1234"); + propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); + propertyValues.add("spring.ldap.embedded.ldaps:true"); + propertyValues.add("spring.ldap.embedded.sslBundleName:test"); + propertyValues.add("spring.ldap.embedded.credential.username:uid=root"); + propertyValues.add("spring.ldap.embedded.credential.password:boot"); + this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { + context.getBean(SslBundles.class); + InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + assertThat(server.getListenPort()).isEqualTo(1234); + BindResult result = server.bind("uid=root", "boot"); + assertThat(result).isNotNull(); + }); + } + @Test void testRandomPortWithEnvironment() { this.contextRunner.withPropertyValues("spring.ldap.embedded.base-dn:dc=spring,dc=org").run((context) -> { diff --git a/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks b/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..4e5e1399aee491f1765c8ff8a833c7c1103380ae GIT binary patch literal 32 mcmezO_TO6u1_mY|W@tFi`n=R{PU>QDACY&KEuvWkKVksZ3=H4^ literal 0 HcmV?d00001 diff --git a/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 b/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 new file mode 100644 index 0000000000000000000000000000000000000000..8c9a6ffa62f44dc739e0849bacce4b3bd6b2f2a6 GIT binary patch literal 103 zcmV-t0GR(UWdZ>MFcAg`Duzgg_YDCD0iXl~0x$qDO)xPq4F(BdhDZTr0|WvA1povf zvJZ7z`bbPmL!}G_MScP$r~?xi3ALqBRfwXz2zS1)1QaQ|@ehK>n61?5=jVJYDi7ZE JuxSDUClLO2A6) Date: Tue, 2 Dec 2025 20:45:17 +0000 Subject: [PATCH 2/3] Documenting changes and existing problem in code for demonstration purposes. Signed-off-by: CatiaCorreia --- .../EmbeddedLdapAutoConfiguration.java | 115 ++++++++-- .../embedded/EmbeddedLdapProperties.java | 204 +++++++++++++++--- .../EmbeddedLdapAutoConfigurationTests.java | 97 +++++++-- .../ldap/autoconfigure/embedded/keystore.jks | Bin 32 -> 0 bytes .../autoconfigure/embedded/keystore.pkcs12 | Bin 103 -> 0 bytes .../ldap/autoconfigure/embedded/rsa-cert.pem | 23 -- .../ldap/autoconfigure/embedded/rsa-key.pem | 28 --- .../boot/ldap/autoconfigure/embedded/test.jks | Bin 0 -> 1294 bytes 8 files changed, 356 insertions(+), 111 deletions(-) delete mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks delete mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 delete mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/rsa-cert.pem delete mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/rsa-key.pem create mode 100644 module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/test.jks diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java index 3fc2ea3f689b..27c83b0934bf 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java @@ -16,21 +16,32 @@ package org.springframework.boot.ldap.autoconfigure.embedded; +import java.io.IOException; import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.LDAPException; -import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.schema.Schema; import com.unboundid.ldif.LDIFReader; import org.jspecify.annotations.Nullable; @@ -38,12 +49,12 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; @@ -53,6 +64,7 @@ import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; import org.springframework.boot.ldap.autoconfigure.LdapProperties; import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints; +import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -67,9 +79,12 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -91,6 +106,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { private final EmbeddedLdapProperties embeddedProperties; + private final ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(); + private @Nullable InMemoryDirectoryServer server; EmbeddedLdapAutoConfiguration(EmbeddedLdapProperties embeddedProperties) { @@ -98,7 +115,9 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { } @Bean - InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) throws LDAPException { + InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext, ObjectProvider sslBundles) + throws LDAPException, KeyStoreException, IOException, + NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn()); InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn); String username = this.embeddedProperties.getCredential().getUsername(); @@ -107,8 +126,13 @@ InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) t config.addAdditionalBindCredentials(username, password); } setSchema(config); - if (this.embeddedProperties.isLdaps()) { - this.setLdapsListener(applicationContext, config); + if (this.embeddedProperties.getSsl().isEnabled()) { + EmbeddedLdapProperties.Ssl ssl = this.embeddedProperties.getSsl(); + SSLContext sslContext = getSslContext(ssl, sslBundles.getIfAvailable()); + SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); + SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, + this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory)); } else { config @@ -148,22 +172,6 @@ private void setSchema(InMemoryDirectoryServerConfig config, Resource resource) } } - @ConditionalOnBean(SslBundles.class) - private void setLdapsListener(ApplicationContext applicationContext, InMemoryDirectoryServerConfig config) - throws LDAPException { - if (StringUtils.hasText(this.embeddedProperties.getSslBundleName())) { - SslBundles sslBundles = applicationContext.getBean(SslBundles.class); - SSLContext sslContext = sslBundles.getBundle(this.embeddedProperties.getSslBundleName()).createSslContext(); - SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); - SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, - this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory)); - } - else { - throw new LDAPException(ResultCode.PARAM_ERROR, "SslBundleName property not specified"); - } - } - private void importLdif(InMemoryDirectoryServer server, ApplicationContext applicationContext) { String location = this.embeddedProperties.getLdif(); if (StringUtils.hasText(location)) { @@ -208,6 +216,71 @@ public void destroy() throws Exception { } } + private SSLContext getSslContext(EmbeddedLdapProperties.Ssl ssl, @Nullable SslBundles sslBundles) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, + UnrecoverableKeyException, KeyManagementException { + if (sslBundles != null && StringUtils.hasText(ssl.getBundle())) { + SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle()); + Assert.notNull(sslBundle, "SSL bundle name has been set but no SSL bundles found in context"); + return sslBundle.createSslContext(); + + } + else { + Assert.notNull(ssl.getAlgorithm(), "SSL algorithm must be specified"); + SSLContext sslContext = SSLContext.getInstance(ssl.getAlgorithm()); + KeyManager[] keyManagers = configureKeyManagers(ssl); + TrustManager[] trustManagers = configureTrustManagers(ssl); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + return sslContext; + } + } + + private KeyManager @Nullable [] configureKeyManagers(EmbeddedLdapProperties.Ssl ssl) throws KeyStoreException, + IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + String keyStoreName = ssl.getKeyStore(); + String keyStorePassword = ssl.getKeyStorePassword(); + String storeType = ssl.getKeyStoreType(); + char[] keyPassphrase = null; + if (keyStorePassword != null) { + keyPassphrase = keyStorePassword.toCharArray(); + } + KeyManager[] keyManagers = null; + if (StringUtils.hasText(keyStoreName)) { + Resource resource = this.resourceLoader.getResource(keyStoreName); + KeyStore ks = KeyStore.getInstance(storeType); + try (InputStream inputStream = resource.getInputStream()) { + ks.load(inputStream, keyPassphrase); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(ssl.getKeyStoreAlgorithm()); + kmf.init(ks, keyPassphrase); + keyManagers = kmf.getKeyManagers(); + } + return keyManagers; + } + + private TrustManager @Nullable [] configureTrustManagers(EmbeddedLdapProperties.Ssl ssl) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + String trustStoreName = ssl.getTrustStore(); + String trustStorePassword = ssl.getTrustStorePassword(); + String storeType = ssl.getTrustStoreType(); + char[] trustPassphrase = null; + if (trustStorePassword != null) { + trustPassphrase = trustStorePassword.toCharArray(); + } + TrustManager[] trustManagers = null; + if (StringUtils.hasText(trustStoreName)) { + Resource resource = this.resourceLoader.getResource(trustStoreName); + KeyStore tks = KeyStore.getInstance(storeType); + try (InputStream inputStream = resource.getInputStream()) { + tks.load(inputStream, trustPassphrase); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(ssl.getTrustStoreAlgorithm()); + tmf.init(tks); + trustManagers = tmf.getTrustManagers(); + } + return trustManagers; + } + /** * {@link SpringBootCondition} to determine when to apply embedded LDAP * auto-configuration. diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java index e5b480a94edf..55592be0b1ff 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java @@ -16,9 +16,12 @@ package org.springframework.boot.ldap.autoconfigure.embedded; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; +import javax.net.ssl.SSLContext; + import org.jspecify.annotations.Nullable; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -58,19 +61,14 @@ public class EmbeddedLdapProperties { private String ldif = "classpath:schema.ldif"; /** - * Listener type. + * Schema validation. */ - private boolean ldaps; + private final Validation validation = new Validation(); /** - * Embedded LDAPS client SSL bundle name. + * SSL configuration. */ - @Nullable private String sslBundleName; - - /** - * Schema validation. - */ - private final Validation validation = new Validation(); + private final Ssl ssl = new Ssl(); public int getPort() { return this.port; @@ -104,26 +102,14 @@ public void setLdif(String ldif) { this.ldif = ldif; } - public boolean isLdaps() { - return this.ldaps; - } - - public void setLdaps(boolean bool) { - this.ldaps = bool; - } - - public @Nullable String getSslBundleName() { - return this.sslBundleName; - } - - public void setSslBundleName(@Nullable String sslBundleName) { - this.sslBundleName = sslBundleName; - } - public Validation getValidation() { return this.validation; } + public Ssl getSsl() { + return this.ssl; + } + public static class Credential { /** @@ -158,6 +144,174 @@ boolean isAvailable() { } + public static class Ssl { + + private static final String SUN_X509 = "SunX509"; + + private static final String DEFAULT_PROTOCOL; + + static { + String protocol = "TLSv1.1"; + try { + String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); + for (String prot : protocols) { + if ("TLSv1.2".equals(prot)) { + protocol = "TLSv1.2"; + break; + } + } + } + catch (NoSuchAlgorithmException e) { + // nothing + } + DEFAULT_PROTOCOL = protocol; + } + + /** + * Whether to enable SSL support. + */ + private Boolean enabled = false; + + /** + * SSL bundle name. + */ + private @Nullable String bundle; + + /** + * Path to the key store that holds the SSL certificate. + */ + private @Nullable String keyStore; + + /** + * Key store type. + */ + private String keyStoreType = "PKCS12"; + + /** + * Password used to access the key store. + */ + private @Nullable String keyStorePassword; + + /** + * Key store algorithm. + */ + private String keyStoreAlgorithm = SUN_X509; + + /** + * Trust store that holds SSL certificates. + */ + private @Nullable String trustStore; + + /** + * Trust store type. + */ + private String trustStoreType = "JKS"; + + /** + * Password used to access the trust store. + */ + private @Nullable String trustStorePassword; + + /** + * Trust store algorithm. + */ + private String trustStoreAlgorithm = SUN_X509; + + /** + * SSL algorithm to use. + */ + private String algorithm = DEFAULT_PROTOCOL; + + public Boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public @Nullable String getBundle() { + return this.bundle; + } + + public void setBundle(@Nullable String bundle) { + this.bundle = bundle; + } + + public @Nullable String getKeyStore() { + return this.keyStore; + } + + public void setKeyStore(@Nullable String keyStore) { + this.keyStore = keyStore; + } + + public String getKeyStoreType() { + return this.keyStoreType; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public @Nullable String getKeyStorePassword() { + return this.keyStorePassword; + } + + public void setKeyStorePassword(@Nullable String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getKeyStoreAlgorithm() { + return this.keyStoreAlgorithm; + } + + public void setKeyStoreAlgorithm(String keyStoreAlgorithm) { + this.keyStoreAlgorithm = keyStoreAlgorithm; + } + + public @Nullable String getTrustStore() { + return this.trustStore; + } + + public void setTrustStore(@Nullable String trustStore) { + this.trustStore = trustStore; + } + + public String getTrustStoreType() { + return this.trustStoreType; + } + + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public @Nullable String getTrustStorePassword() { + return this.trustStorePassword; + } + + public void setTrustStorePassword(@Nullable String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public String getTrustStoreAlgorithm() { + return this.trustStoreAlgorithm; + } + + public void setTrustStoreAlgorithm(String trustStoreAlgorithm) { + this.trustStoreAlgorithm = trustStoreAlgorithm; + } + + public String getAlgorithm() { + return this.algorithm; + } + + public void setAlgorithm( String sslAlgorithm) { + this.algorithm = sslAlgorithm; + } + + } + public static class Validation { /** diff --git a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java index 00e0d23a6978..d7724305191d 100644 --- a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java +++ b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java @@ -24,6 +24,7 @@ import java.util.List; import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.BindResult; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.LDAPConnection; @@ -36,7 +37,6 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; -import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.util.TestPropertyValues; @@ -71,26 +71,95 @@ void testSetDefaultPort() { } @Test - void testLdapsVersion() { + void testServerDefaultNoSsl() { + this.contextRunner + .withPropertyValues("spring.ldap.embedded.port:1234", "spring.ldap.embedded.base-dn:dc=spring,dc=org") + .run((context) -> { + InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1); + InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0); + assertThat(config.getListenerName()).isEqualTo("LDAP"); + }); + } + + @Test + void testServerWithSslBundle() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; + propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret"); + propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks"); + propertyValues.add("spring.ssl.bundle.jks.test.truststore.location=" + location + "test.jks"); + propertyValues.add("spring.ldap.embedded.port:1234"); + propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); + propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); + propertyValues.add("spring.ldap.embedded.ssl.bundle:test"); + this.contextRunner + .withPropertyValues(propertyValues.toArray(String[]::new)) + .run((context) -> { + InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1); + InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0); + assertThat(config.getListenerName()).isEqualTo("LDAPS"); + assertThat(config.getListenPort()).isEqualTo(1234); + assertThat(server.getListenPort()).isEqualTo(1234); + assertThat(server.getConnection("LDAPS").getSSLSession()).isNotNull(); + }); + } + + @Test + void testServerWithInvalidSslBundleShouldFail() { List propertyValues = new ArrayList<>(); String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; - propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1"); - propertyValues.add("spring.ssl.bundle.pem.test.key.password=secret1"); - propertyValues.add("spring.ssl.bundle.pem.test.keystore.certificate=" + location + "rsa-cert.pem"); - propertyValues.add("spring.ssl.bundle.pem.test.keystore.keystore.private-key=" + location + "rsa-key.pem"); - propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret"); + propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks"); propertyValues.add("spring.ldap.embedded.port:1234"); propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); - propertyValues.add("spring.ldap.embedded.ldaps:true"); - propertyValues.add("spring.ldap.embedded.sslBundleName:test"); - propertyValues.add("spring.ldap.embedded.credential.username:uid=root"); - propertyValues.add("spring.ldap.embedded.credential.password:boot"); + propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); + propertyValues.add("spring.ldap.embedded.ssl.bundle:foo"); + this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure().hasMessageContaining("foo"); + assertThat(context).getFailure().hasMessageContaining("cannot be found"); + }); + } + + @Test + void testServerWithSsl() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; + propertyValues.add("spring.ldap.embedded.port:1234"); + propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); + propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); + propertyValues.add("spring.ldap.embedded.ssl.keyStorePassword=secret"); + propertyValues.add("spring.ldap.embedded.ssl.keyStore=" + location + "test.jks"); + propertyValues.add("spring.ldap.embedded.ssl.trustStorePassword=secret"); + propertyValues.add("spring.ldap.embedded.ssl.trustStore=" + location + "test.jks"); this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { - context.getBean(SslBundles.class); InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1); + InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0); + assertThat(config.getListenerName()).isEqualTo("LDAPS"); + assertThat(config.getListenPort()).isEqualTo(1234); assertThat(server.getListenPort()).isEqualTo(1234); - BindResult result = server.bind("uid=root", "boot"); - assertThat(result).isNotNull(); + assertThat(server.getConnection("LDAPS").getSSLSession()).isNotNull(); + }); + } + + @Test + void testServerWithInvalidSslShouldFail() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; + propertyValues.add("spring.ldap.embedded.port:1234"); + propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); + propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); + propertyValues.add("spring.ldap.embedded.ssl.keyStorePassword=secret"); + propertyValues.add("spring.ldap.embedded.ssl.keyStore=" + location + "foo"); + propertyValues.add("spring.ldap.embedded.ssl.trustStorePassword=secret"); + propertyValues.add("spring.ldap.embedded.ssl.trustStore=" + location + "foo"); + this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure().hasMessageContaining("foo"); + assertThat(context).getFailure().hasMessageContaining("does not exist"); }); } diff --git a/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks b/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.jks deleted file mode 100644 index 4e5e1399aee491f1765c8ff8a833c7c1103380ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 mcmezO_TO6u1_mY|W@tFi`n=R{PU>QDACY&KEuvWkKVksZ3=H4^ diff --git a/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 b/module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/keystore.pkcs12 deleted file mode 100644 index 8c9a6ffa62f44dc739e0849bacce4b3bd6b2f2a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103 zcmV-t0GR(UWdZ>MFcAg`Duzgg_YDCD0iXl~0x$qDO)xPq4F(BdhDZTr0|WvA1povf zvJZ7z`bbPmL!}G_MScP$r~?xi3ALqBRfwXz2zS1)1QaQ|@ehK>n61?5=jVJYDi7ZE JuxSDUClLO2A6)m2`#U#kc$jZRd z#8^J5!~Wmp7|YE9c_o202CEfvPJAt&F-z+E+Y5Je-P@SM3x!M{?<`L$d7kPy>HgY< z0rl+7ddFPf9_^UC?)~PM8U_ciTzNlZu6ENGso!d;PtM$lt<~ROC~|P;oYPIU`ifCm z3xrl>nF}qFRax}#!Gf-mKc9ryWTqXD5RA>>WDIzayKC#wV=9~F(x>fBQsZQ6{U|+U z!WW$>x*Dgfn4=QHB86`zaB53NZvW1sG()7e{`TIV! z&3V4Ogyo;agqT?@rcVEe)EOO%0lu zJ}+QqVq{`s(UY*NH{fPt*J|@PXTieE%3zRVC;*H-=1>+kVfN6x?7aN)JeUXvh6p=` z2p5J3H--o^T*N?5oY&CYz!C_}4NXl=qCi|jBLfR4m(H$fV%z~ty*jYqU<7jHnHn1z z{?-RK%9aXg2Hp3Xqxa^?^KXLNgKXZrFdUY%Z`c;7mgySnpcME!(zZ9^*Z!klcNu&; zJv%h#dZTrNtV#Z-w1n;1GXs_`nLqypOSOQ~A^+Vgum9VAO!|j)`14~cmWlGS{BHhs z`Ka7sCesk%D6xq~-%3BP(@wmu_5PC*v*g^19*oDCL>ZQ_-Kd;Z$Hmz)N^9lLmRQG?vR>ZjR%X@$!Tw1Ryr|naXt4hMbo5@zmuvM zRjkh}`M7UI;DpW<&G#QhJ)O@ssXHyMp*mDGh0(|Q-+50h=EhbAkUKiu|LR|O{i|~8 zG#9?b`_jG2zRtb(d;J9c=WfRw1SLMc;#t4^)bdH$I%1_8mU!~AelPGZ&F0$q@R`9O zTlJYe_wAPLtUcO4Q%WhRoN3F19Qh9Ow1TBSOuwH0IP>TZsa20}Jy&_$o4sju^m418 zzVifG8;&#`T)}fG(e3OcJ!7?pmWGQJ4de}Ef$3J3k420{WPN8vSnXA Date: Mon, 15 Dec 2025 12:36:43 +0000 Subject: [PATCH 3/3] Add ssl nested class in EmbeddedLdapProperties. Setup autoconfiguration of LDAP listener with ssl. Add tests using ssl. Issue#48060 Signed-off-by: CatiaCorreia catia.correia97@gmail.com Signed-off-by: CatiaCorreia --- .../EmbeddedLdapAutoConfiguration.java | 5 +-- .../embedded/EmbeddedLdapProperties.java | 4 +- .../EmbeddedLdapAutoConfigurationTests.java | 45 +++++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java index 27c83b0934bf..982cce9726c3 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java @@ -115,8 +115,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { } @Bean - InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext, ObjectProvider sslBundles) - throws LDAPException, KeyStoreException, IOException, + InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext, + ObjectProvider sslBundles) throws LDAPException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn()); InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn); @@ -226,7 +226,6 @@ private SSLContext getSslContext(EmbeddedLdapProperties.Ssl ssl, @Nullable SslBu } else { - Assert.notNull(ssl.getAlgorithm(), "SSL algorithm must be specified"); SSLContext sslContext = SSLContext.getInstance(ssl.getAlgorithm()); KeyManager[] keyManagers = configureKeyManagers(ssl); TrustManager[] trustManagers = configureTrustManagers(ssl); diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java index 55592be0b1ff..3e85ec2ee13e 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java @@ -161,7 +161,7 @@ public static class Ssl { } } } - catch (NoSuchAlgorithmException e) { + catch (NoSuchAlgorithmException ex) { // nothing } DEFAULT_PROTOCOL = protocol; @@ -306,7 +306,7 @@ public String getAlgorithm() { return this.algorithm; } - public void setAlgorithm( String sslAlgorithm) { + public void setAlgorithm(String sslAlgorithm) { this.algorithm = sslAlgorithm; } diff --git a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java index d7724305191d..c28fd29e4ed2 100644 --- a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java +++ b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java @@ -82,29 +82,28 @@ void testServerDefaultNoSsl() { }); } - @Test - void testServerWithSslBundle() { - List propertyValues = new ArrayList<>(); - String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; - propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret"); - propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks"); - propertyValues.add("spring.ssl.bundle.jks.test.truststore.location=" + location + "test.jks"); - propertyValues.add("spring.ldap.embedded.port:1234"); - propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); - propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); - propertyValues.add("spring.ldap.embedded.ssl.bundle:test"); - this.contextRunner - .withPropertyValues(propertyValues.toArray(String[]::new)) - .run((context) -> { - InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); - assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1); - InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0); - assertThat(config.getListenerName()).isEqualTo("LDAPS"); - assertThat(config.getListenPort()).isEqualTo(1234); - assertThat(server.getListenPort()).isEqualTo(1234); - assertThat(server.getConnection("LDAPS").getSSLSession()).isNotNull(); - }); - } + @Test + void testServerWithSslBundle() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; + propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret"); + propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks"); + propertyValues.add("spring.ssl.bundle.jks.test.truststore.location=" + location + "test.jks"); + propertyValues.add("spring.ssl.bundle.jks.test.protocol=TLSv1.2"); + propertyValues.add("spring.ldap.embedded.port:1234"); + propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org"); + propertyValues.add("spring.ldap.embedded.ssl.enabled:true"); + propertyValues.add("spring.ldap.embedded.ssl.bundle:test"); + this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { + InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1); + InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0); + assertThat(config.getListenerName()).isEqualTo("LDAPS"); + assertThat(config.getListenPort()).isEqualTo(1234); + assertThat(server.getListenPort()).isEqualTo(1234); + assertThat(server.getConnection("LDAPS").getSSLSession()).isNotNull(); + }); + } @Test void testServerWithInvalidSslBundleShouldFail() {