diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc index 496364b53a4..b9055e577b4 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -154,6 +154,7 @@ SSL is supported for the following service connections: * Cassandra * Elasticsearch +* LDAP * MongoDB * OpenTelemetry (OTLP logging, metrics, and tracing) * RabbitMQ diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc index c3e14b6dcba..a80cf5f01f4 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc @@ -161,6 +161,7 @@ Currently the following components are compatible: * Tomcat web server * Netty web server +* LDAP client To enable reloading, you need to opt-in via a configuration property as shown in this example: diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc index 398c697764a..853496a9078 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc @@ -243,6 +243,7 @@ The SSL annotations are supported for the following service connections: * Couchbase * Elasticsearch * Kafka +* LDAP * MongoDB * OpenTelemetry (OTLP logging, metrics, and tracing) * RabbitMQ diff --git a/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java index 44102463544..7171811d964 100644 --- a/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -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(); } } diff --git a/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java index 0d1eebe990e..bdb82576249 100644 --- a/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/module/spring-boot-ldap/src/dockerTest/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -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(); } } diff --git a/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ca.crt b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ca.crt new file mode 100644 index 00000000000..6b39d52ce64 --- /dev/null +++ b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ca.crt @@ -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----- diff --git a/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ldap-ssl-compose.yaml b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ldap-ssl-compose.yaml new file mode 100644 index 00000000000..df3cfd87e12 --- /dev/null +++ b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/ldap-ssl-compose.yaml @@ -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' diff --git a/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/lldap-ssl-compose.yaml b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/lldap-ssl-compose.yaml new file mode 100644 index 00000000000..126ceda0d9b --- /dev/null +++ b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/lldap-ssl-compose.yaml @@ -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' diff --git a/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.crt b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.crt new file mode 100644 index 00000000000..16146dfd17d --- /dev/null +++ b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.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----- diff --git a/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.key b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.key new file mode 100644 index 00000000000..8e958b351c6 --- /dev/null +++ b/module/spring-boot-ldap/src/dockerTest/resources/org/springframework/boot/ldap/docker/compose/server.key @@ -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----- diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactory.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactory.java index 6bec236ea1e..b75d0099d5b 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactory.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/LLdapDockerComposeConnectionDetailsFactory.java @@ -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 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; + } + } } diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactory.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactory.java index 86b362cd1e6..a745f64d80a 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactory.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/docker/compose/OpenLdapDockerComposeConnectionDetailsFactory.java @@ -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 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; + } + } }