Cátia 12 hours ago committed by GitHub
parent
commit
e662b2be6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 107
      module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java
  2. 180
      module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java
  3. 98
      module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java
  4. BIN
      module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/test.jks

107
module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java

@ -16,12 +16,28 @@ @@ -16,12 +16,28 @@
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;
@ -33,6 +49,7 @@ import org.jspecify.annotations.Nullable; @@ -33,6 +49,7 @@ import org.jspecify.annotations.Nullable;
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;
@ -47,6 +64,8 @@ import org.springframework.boot.context.properties.bind.Binder; @@ -47,6 +64,8 @@ import org.springframework.boot.context.properties.bind.Binder;
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;
import org.springframework.context.annotation.Bean;
@ -60,9 +79,12 @@ import org.springframework.core.env.MapPropertySource; @@ -60,9 +79,12 @@ import org.springframework.core.env.MapPropertySource;
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;
/**
@ -84,6 +106,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { @@ -84,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) {
@ -91,7 +115,9 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { @@ -91,7 +115,9 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
}
@Bean
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) throws LDAPException {
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext,
ObjectProvider<SslBundles> 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();
@ -100,9 +126,18 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { @@ -100,9 +126,18 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
config.addAdditionalBindCredentials(username, password);
}
setSchema(config);
InMemoryListenerConfig listenerConfig = InMemoryListenerConfig.createLDAPConfig("LDAP",
this.embeddedProperties.getPort());
config.setListenerConfigs(listenerConfig);
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
.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort()));
}
this.server = new InMemoryDirectoryServer(config);
importLdif(this.server, applicationContext);
this.server.startListening();
@ -181,6 +216,70 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { @@ -181,6 +216,70 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
}
}
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 {
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.

180
module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java

@ -16,9 +16,12 @@ @@ -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;
@ -62,6 +65,11 @@ public class EmbeddedLdapProperties { @@ -62,6 +65,11 @@ public class EmbeddedLdapProperties {
*/
private final Validation validation = new Validation();
/**
* SSL configuration.
*/
private final Ssl ssl = new Ssl();
public int getPort() {
return this.port;
}
@ -98,6 +106,10 @@ public class EmbeddedLdapProperties { @@ -98,6 +106,10 @@ public class EmbeddedLdapProperties {
return this.validation;
}
public Ssl getSsl() {
return this.ssl;
}
public static class Credential {
/**
@ -132,6 +144,174 @@ public class EmbeddedLdapProperties { @@ -132,6 +144,174 @@ public class EmbeddedLdapProperties {
}
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 ex) {
// 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 {
/**

98
module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java

@ -20,8 +20,11 @@ import java.lang.annotation.ElementType; @@ -20,8 +20,11 @@ import java.lang.annotation.ElementType;
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.listener.InMemoryListenerConfig;
import com.unboundid.ldap.sdk.BindResult;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.LDAPConnection;
@ -32,6 +35,7 @@ import org.junit.jupiter.api.Test; @@ -32,6 +35,7 @@ import org.junit.jupiter.api.Test;
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.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -54,7 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat;
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,98 @@ class EmbeddedLdapAutoConfigurationTests { @@ -66,6 +70,98 @@ class EmbeddedLdapAutoConfigurationTests {
});
}
@Test
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<String> 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() {
List<String> 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.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: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<String> 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) -> {
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 testServerWithInvalidSslShouldFail() {
List<String> 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");
});
}
@Test
void testRandomPortWithEnvironment() {
this.contextRunner.withPropertyValues("spring.ldap.embedded.base-dn:dc=spring,dc=org").run((context) -> {

BIN
module/spring-boot-ldap/src/test/resources/org/springframework/boot/ldap/autoconfigure/embedded/test.jks

Binary file not shown.
Loading…
Cancel
Save