Add SSL support for LDAP in Docker Compose

Also fixes missing LDAP entries in the documentation.

See gh-51382
This commit is contained in:
Moritz Halbritter
2026-08-26 11:53:08 +02:00
parent f92cf3155e
commit 4a3cad7983
12 changed files with 148 additions and 0 deletions
@@ -18,6 +18,7 @@ package org.springframework.boot.ldap.docker.compose;
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
import org.springframework.boot.ldap.autoconfigure.LdapConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.testsupport.container.TestImage;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,6 +37,17 @@ class LLdapDockerComposeConnectionDetailsFactoryIntegrationTests {
assertThat(connectionDetails.getBase()).isEqualTo("dc=springframework,dc=org");
assertThat(connectionDetails.getUrls()).hasSize(1);
assertThat(connectionDetails.getUrls()[0]).startsWith("ldap://");
assertThat(connectionDetails.getSslBundle()).isNull();
}
@DockerComposeTest(composeFile = "lldap-ssl-compose.yaml", image = TestImage.LLDAP,
additionalResources = { "ca.crt", "server.crt", "server.key" })
void runWithSslCreatesConnectionDetails(LdapConnectionDetails connectionDetails) {
assertThat(connectionDetails.getUrls()).hasSize(1);
assertThat(connectionDetails.getUrls()[0]).startsWith("ldaps://");
SslBundle sslBundle = connectionDetails.getSslBundle();
assertThat(sslBundle).isNotNull();
assertThat(sslBundle.createSslContext()).isNotNull();
}
}
@@ -18,6 +18,7 @@ package org.springframework.boot.ldap.docker.compose;
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
import org.springframework.boot.ldap.autoconfigure.LdapConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.testsupport.container.TestImage;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,6 +37,17 @@ class OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests {
assertThat(connectionDetails.getBase()).isEqualTo("dc=ldap,dc=example,dc=org");
assertThat(connectionDetails.getUrls()).hasSize(1);
assertThat(connectionDetails.getUrls()[0]).startsWith("ldaps://");
assertThat(connectionDetails.getSslBundle()).isNull();
}
@DockerComposeTest(composeFile = "ldap-ssl-compose.yaml", image = TestImage.OPEN_LDAP,
additionalResources = { "ca.crt", "server.crt", "server.key" })
void runWithSslCreatesConnectionDetails(LdapConnectionDetails connectionDetails) {
assertThat(connectionDetails.getUrls()).hasSize(1);
assertThat(connectionDetails.getUrls()[0]).startsWith("ldaps://");
SslBundle sslBundle = connectionDetails.getSslBundle();
assertThat(sslBundle).isNotNull();
assertThat(sslBundle.createSslContext()).isNotNull();
}
}
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWTCCAkGgAwIBAgIURFIQf/P6N/1OEFidlxuqbi3DOp4wDQYJKoZIhvcNAQEL
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MCAXDTI2MDgyNTEyNDg0MFoYDzIxMjYwODAxMTI0ODQw
WjA7MRkwFwYDVQQKDBBTcHJpbmcgQm9vdCBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9
IJLbjjsaH7VWjlPcjHLOEptcdlWGIChS87Y2bmWgxsYw0W6HjulSFIRHUsI6/NsJ
WH5lodrBaKVm23zqejxnPwzijR7x6tMnNYp8Hm2uy700h5AwMS+1tDCEOPIQympZ
h7FlVCRLkNUjPNWLcVNwlZZlzSXb9Ql58zyp15/ytkO7W0Pv/VTXXhBMQfvwnN2Y
TJIY7Mxq61W4Jo0JnYpZslbD1sWno4cceVXrVoRmEWYd89J844ItlUmheJdy0GIP
xVBpM1Ihe6beAx8AG83kDOi6+KM0liUg5LXORDpfKdfhxLSghbCxkW0VDrYXdsGa
oYeOeTsM/oHHgjQ4blH9AgMBAAGjUzBRMB0GA1UdDgQWBBTK6aBvkBA3CttNNWPd
SA2zqC3J/DAfBgNVHSMEGDAWgBTK6aBvkBA3CttNNWPdSA2zqC3J/DAPBgNVHRMB
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBd4FBgLscxlGxQEe0TukF4yZts
c4e/waOjUJk0eNJTFeJTQrJCMqMKI5CKunjAJ1g4+XFKc3fxw9qNXieHjL0ekKLb
hXLzdcA75gRQ4NZwcw4GdZnOb7WaUWq9u5FCGU1NQpMu38Mv56o005iCrV8sEpij
ZsvwAJjFP2Fl//fafhMEsk06EnKbjpfxaB1KZ6DnBrMBeh/OokVnV9Mqom+bB48y
U38dxoNcacAmOCckaqALrCyC8pYFWQqTOGtJGezEdDIK7xmonADjdv5qvl1o3xDV
iRsjC7XaNAvvmzxm1E9JK66FhR22U1zsJp9c72otjjF6dpe8LNYTMm6KCB6/
-----END CERTIFICATE-----
@@ -0,0 +1,15 @@
services:
ldap:
image: '{imageName}'
environment:
- 'LDAP_DOMAIN=ldap.example.org'
- 'LDAP_ADMIN_PASSWORD=somepassword'
- 'LDAP_TLS=true'
hostname: ldap
ports:
- "389"
- "636"
labels:
- 'org.springframework.boot.sslbundle.pem.keystore.certificate=server.crt'
- 'org.springframework.boot.sslbundle.pem.keystore.private-key=server.key'
- 'org.springframework.boot.sslbundle.pem.truststore.certificate=ca.crt'
@@ -0,0 +1,19 @@
services:
ldap:
image: '{imageName}'
environment:
- 'LLDAP_LDAP_BASE_DN=dc=springframework,dc=org'
- 'LLDAP_LDAP_USER_PASS=somepassword'
- 'LLDAP_LDAPS_OPTIONS__ENABLED=true'
- 'LLDAP_LDAPS_OPTIONS__CERT_FILE=/certs/server.crt'
- 'LLDAP_LDAPS_OPTIONS__KEY_FILE=/certs/server.key'
ports:
- "3890"
- "6360"
volumes:
- './server.crt:/certs/server.crt'
- './server.key:/certs/server.key'
labels:
- 'org.springframework.boot.sslbundle.pem.keystore.certificate=server.crt'
- 'org.springframework.boot.sslbundle.pem.keystore.private-key=server.key'
- 'org.springframework.boot.sslbundle.pem.truststore.certificate=ca.crt'
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDYzCCAkugAwIBAgIUa/9qbWfUPcuXtVnmhGEIpzPRro4wDQYJKoZIhvcNAQEL
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MCAXDTI2MDgyNTEyNDg0MFoYDzIxMjYwODAxMTI0ODQw
WjAvMRkwFwYDVQQKDBBTcHJpbmcgQm9vdCBUZXN0MRIwEAYDVQQDDAlsb2NhbGhv
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMnHAogOOlrX3H8YCU
9U9ubEFhJIiABCMA7itTkXCxshsziqooNFPW037MVUCEvqfXtkHy0GJXjaYLyyHj
6NQJJs1NYaC05okEn5f0f6+lxVNSOk9TjHxEbXdOMTZAOiAmwsRXTReb185jMOxq
xUlLSY3ehot8geB4SjGNLQOKg+mCznt9f21SGi7o8TaNbTElDWqWIlCVDQ3AYsrm
RRRSXY9DIdpgHwRrnzYoS57+m7BjYUyx0kgamT8FehK4fa+BhIGoIvLAsWp1nd15
2YgQBrg82zDemT2zf5PwnOrRqTiJUubJEotjeOII5mqMdyKnrlpZ41FcK2oRFwvt
KDdtAgMBAAGjaTBnMAkGA1UdEwQCMAAwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/
AAABMB0GA1UdDgQWBBTIoMeEZxAks2Q5D319xxUMln2noDAfBgNVHSMEGDAWgBTK
6aBvkBA3CttNNWPdSA2zqC3J/DANBgkqhkiG9w0BAQsFAAOCAQEAostmB27rtPXi
0hX46UiQ84tcr0ViwK7TT4BHYr0ZntkOj1hKjpfjMORN1ANTYBLGpSsSM2xLBB2g
VXPLCo3BGU1rjxQ7rI3OKZ910pfwf2uvX6LDLRYjKjVnr6pElS3jSVw/nVvYRMf8
acjP7KBgGSLZyqizccSWYTsCYy+V56C4IJTanqf1sHRTqLsICcnwIpoSGwWBv9cG
kFsFoWE+tPwDGn7QuejJ+57U6m23E1vaDNwvi/xrsNy6tfVYS7OF+DzuDR8mHab/
Qvx0qzJkpeYziaObn43ocnd3vn5vhf54UYZkK5eieRnXubfsIYA1u2igIdIAVRk7
NPRHOlFk+g==
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMnHAogOOlrX3H
8YCU9U9ubEFhJIiABCMA7itTkXCxshsziqooNFPW037MVUCEvqfXtkHy0GJXjaYL
yyHj6NQJJs1NYaC05okEn5f0f6+lxVNSOk9TjHxEbXdOMTZAOiAmwsRXTReb185j
MOxqxUlLSY3ehot8geB4SjGNLQOKg+mCznt9f21SGi7o8TaNbTElDWqWIlCVDQ3A
YsrmRRRSXY9DIdpgHwRrnzYoS57+m7BjYUyx0kgamT8FehK4fa+BhIGoIvLAsWp1
nd152YgQBrg82zDemT2zf5PwnOrRqTiJUubJEotjeOII5mqMdyKnrlpZ41FcK2oR
FwvtKDdtAgMBAAECggEAJZnEBLV24C3rNPCdBAK2l6C+PQdVLE1WT2f1P1S1ZHpK
BGVBksTW+V4BabRq+Fn/ByWfpesEf3lw367zKepR+OKQDN3ZhhTtbhOT/aLvWOPT
xYMgq8Zf2cVSDOT/RrtHyEe3hCGPKpz0eQb3E0HxD6xTLWcFQBqv8PsVcw+oHG6A
CFFMWDYAYGbyIpwXpqPVT1YYxOc1taq+Zr2UAN4bK9hFmNv19bAqasCPWveStFiS
oNH4cdyW5Jbss6ZVXeDRZ7NyGpARGLxNJvAKCuQglgIsn7oV2Z3niBX/pGSs3yXI
ryP+pL3iESZPEjGkTrRy+HxWURI/fiOtLKUK/p0vMQKBgQD11Fx/KZC+k6ZSVvMD
nsppLZM5vqSEz4FJqmTHgSF0A2zX+gqM9w7ckhA/hi6PWFzTWBqGXI5FQ2tjKcZc
APQLcS1BrRTa2ZZK8bmVi0X1GJnWG1mqLcYolyv2X9BULhEXM5biEvM0o46pps9s
PVZGR0vlvcENm3JjUgL2zs3qaQKBgQDVE4XAI/iIc+vUzMhyaLt18hyxW3No7r3a
3MKYxlTH0G9MX9AQ2RzoPf6vIx6REenRUBdxJu/XaQBHd291oC8Hq5xIqefUJFY6
hCdCi4IJw9SvgCJshRsIJP+giH3pztj0br/RNoqqd4IPTisMdVoyKFiz/Mjm4vPQ
hoLIQKVcZQKBgQCfuamX3hP0H89TdLdVRNlTWY9tV5dhy8m/aX0tu5NW49rWwdoV
GXmIi0cPX0nlTY6Sq44gewbdrh6aQcxmfyASRykWoTUJtZLXgxQIPIPp6mAaI48w
6aj1OrQP0tVzvLLSFm0U2yc2robFaGMhewERjMWdsps7EiNSsTjH0Dsu8QKBgE/H
YXrRfQrKLHCexCZiJF5C6o9VaF7PlPJPWeUNzUyLNEDqVuMYB4TFQYido6I5jMw8
KqrJI2AKBnq7s1XdRf6fOILUauK5QDfkpzZ5OZUiZ43Wcg0jwT1JbRTURiC2u/C5
TsSsoTq2SFImuGOPxNem1598dFROgW+ADZOg8MkpAoGAVOEKtRblYtHBpBI1itvy
3AOIeaExQ49TLv9/fTJW9pY4j/47getq7RklU2uJ7FyV6ORxfaoQPJwSyTM47dNW
PPo0sbiw2iGlzcoG0A7Yf7/iffuvFj9qPZEzZNczBBmwE273zmgEN/royqKEeDSV
Czgn7Jeanqq0SBLGiJ9TPog=
-----END PRIVATE KEY-----
@@ -24,6 +24,7 @@ import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
import org.springframework.boot.ldap.autoconfigure.LdapConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
/**
* {@link DockerComposeConnectionDetailsFactory} to create {@link LdapConnectionDetails}
@@ -56,8 +57,11 @@ class LLdapDockerComposeConnectionDetailsFactory extends DockerComposeConnection
private final String password;
private final @Nullable SslBundle sslBundle;
LLdapDockerComposeConnectionDetails(RunningService service) {
super(service);
this.sslBundle = getSslBundle(service);
Map<String, @Nullable String> env = service.env();
boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LLDAP_LDAPS_OPTIONS__ENABLED", "false"));
String ldapPort = usesTls ? getFromEnv(env, "LLDAP_LDAPS_OPTIONS__PORT", "6360")
@@ -94,6 +98,11 @@ class LLdapDockerComposeConnectionDetailsFactory extends DockerComposeConnection
return this.password;
}
@Override
public @Nullable SslBundle getSslBundle() {
return this.sslBundle;
}
}
}
@@ -26,6 +26,7 @@ import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
import org.springframework.boot.ldap.autoconfigure.LdapConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
/**
* {@link DockerComposeConnectionDetailsFactory} to create {@link LdapConnectionDetails}
@@ -59,8 +60,11 @@ class OpenLdapDockerComposeConnectionDetailsFactory
private final String password;
private final @Nullable SslBundle sslBundle;
OpenLdapDockerComposeConnectionDetails(RunningService service) {
super(service);
this.sslBundle = getSslBundle(service);
Map<String, @Nullable String> env = service.env();
boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LDAP_TLS", "true"));
String ldapPort = usesTls ? getFromEnv(env, "LDAPS_PORT", "636") : getFromEnv(env, "LDAP_PORT", "389");
@@ -104,6 +108,11 @@ class OpenLdapDockerComposeConnectionDetailsFactory
return this.password;
}
@Override
public @Nullable SslBundle getSslBundle() {
return this.sslBundle;
}
}
}