From b2250f4ad8143b5a7dd6ee20f878f0a636b63798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Sat, 7 Jan 2017 19:40:05 -0500 Subject: [PATCH] Add LDAP health actuator Provide specific health actuator endpoint to verify if LDAP connection is valid. See gh-7905 --- spring-boot-actuator/pom.xml | 5 + .../HealthIndicatorAutoConfiguration.java | 33 ++++++- .../actuate/health/LdapHealthIndicator.java | 52 ++++++++++ ...itional-spring-configuration-metadata.json | 6 ++ ...HealthIndicatorAutoConfigurationTests.java | 44 ++++++++- .../health/LdapHealthIndicatorTests.java | 96 +++++++++++++++++++ 6 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/LdapHealthIndicator.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/LdapHealthIndicatorTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index bcb23ce313..3204b8fc08 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -166,6 +166,11 @@ spring-data-couchbase true + + org.springframework.data + spring-data-ldap + true + org.springframework.data spring-data-mongodb diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index e838233479..de30335deb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorPropertie import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; +import org.springframework.boot.actuate.health.LdapHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; @@ -56,6 +57,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration; +import org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration; @@ -77,6 +79,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.ldap.core.LdapOperations; import org.springframework.mail.javamail.JavaMailSenderImpl; /** @@ -96,9 +99,10 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; CassandraDataAutoConfiguration.class, CouchbaseDataAutoConfiguration.class, DataSourceAutoConfiguration.class, ElasticsearchAutoConfiguration.class, JestAutoConfiguration.class, JmsAutoConfiguration.class, - MailSenderAutoConfiguration.class, MongoAutoConfiguration.class, - MongoDataAutoConfiguration.class, RabbitAutoConfiguration.class, - RedisAutoConfiguration.class, SolrAutoConfiguration.class }) + LdapDataAutoConfiguration.class, MailSenderAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + RabbitAutoConfiguration.class, RedisAutoConfiguration.class, + SolrAutoConfiguration.class }) @EnableConfigurationProperties({ HealthIndicatorProperties.class }) @Import({ ElasticsearchHealthIndicatorConfiguration.ElasticsearchClientHealthIndicatorConfiguration.class, @@ -231,6 +235,27 @@ public class HealthIndicatorAutoConfiguration { } + @Configuration + @ConditionalOnClass(LdapOperations.class) + @ConditionalOnBean(LdapOperations.class) + @ConditionalOnEnabledHealthIndicator("ldap") + public static class LdapHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map ldapOperations; + + public LdapHealthIndicatorConfiguration(Map ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Bean + @ConditionalOnMissingBean(name = "ldapHealthIndicator") + public HealthIndicator ldapHealthIndicator() { + return createHealthIndicator(this.ldapOperations); + } + + } + @Configuration @ConditionalOnBean(MongoTemplate.class) @ConditionalOnEnabledHealthIndicator("mongo") diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/LdapHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/LdapHealthIndicator.java new file mode 100644 index 0000000000..5e0befbd4e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/LdapHealthIndicator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.ContextExecutor; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.util.Assert; + +/** + * {@link HealthIndicator} for configured LDAP server(s). + * + * @author Eddú Meléndez + * @version 1.5.0 + */ +public class LdapHealthIndicator extends AbstractHealthIndicator { + + private final LdapOperations ldapOperations; + + public LdapHealthIndicator(LdapOperations ldapOperations) { + Assert.notNull(ldapOperations, "LdapOperations must not be null"); + this.ldapOperations = ldapOperations; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + String version = (String) this.ldapOperations.executeReadOnly(new ContextExecutor() { + @Override + public Object executeWithContext(DirContext ctx) throws NamingException { + return ctx.getEnvironment().get("java.naming.ldap.version"); + } + }); + builder.up().withDetail("version", version); + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json index bf7b902c6a..0ecc13c01e 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -139,6 +139,12 @@ "description": "Enable JMS health check.", "defaultValue": true }, + { + "name": "management.health.ldap.enabled", + "type": "java.lang.Boolean", + "description": "Enable LDAP health check.", + "defaultValue": true + }, { "name": "management.health.mongo.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index be0ab02b73..307d39c2d6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; +import org.springframework.boot.actuate.health.LdapHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.RabbitHealthIndicator; @@ -63,6 +64,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.ldap.core.LdapOperations; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -535,6 +537,35 @@ public class HealthIndicatorAutoConfigurationTests { .isEqualTo(ApplicationHealthIndicator.class); } + @Test + public void ldapHealthIndicator() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.diskspace.enabled:false"); + this.context.register(LdapConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertThat(beans.size()).isEqualTo(1); + assertThat(beans.values().iterator().next().getClass()) + .isEqualTo(LdapHealthIndicator.class); + } + + @Test + public void notLdapHealthIndicator() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.diskspace.enabled:false", + "management.health.ldap.enabled:false"); + this.context.register(LdapConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertThat(beans.size()).isEqualTo(1); + assertThat(beans.values().iterator().next().getClass()) + .isEqualTo(ApplicationHealthIndicator.class); + } + @Configuration @EnableConfigurationProperties protected static class DataSourceConfig { @@ -605,4 +636,15 @@ public class HealthIndicatorAutoConfigurationTests { } + @Configuration + protected static class LdapConfiguration { + + @Bean + public LdapOperations ldapOperations() { + LdapOperations operations = mock(LdapOperations.class); + return operations; + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/LdapHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/LdapHealthIndicatorTests.java new file mode 100644 index 0000000000..3ff7de4dd8 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/LdapHealthIndicatorTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.ldap.CommunicationException; +import org.springframework.ldap.core.ContextExecutor; +import org.springframework.ldap.core.LdapTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link LdapHealthIndicator} + * + * @author Eddú Meléndez + */ +public class LdapHealthIndicatorTests { + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void indicatorExist() { + this.context.register(LdapAutoConfiguration.class, LdapDataAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, EndpointAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + LdapTemplate ldapTemplate = this.context.getBean(LdapTemplate.class); + assertThat(ldapTemplate).isNotNull(); + LdapHealthIndicator healthIndicator = this.context.getBean(LdapHealthIndicator.class); + assertThat(healthIndicator).isNotNull(); + } + + @Test + public void ldapIsUp() { + LdapTemplate ldapTemplate = mock(LdapTemplate.class); + given(ldapTemplate.executeReadOnly(any(ContextExecutor.class))).willReturn("3"); + LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("version")).isEqualTo("3"); + verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class)); + } + + @Test + public void ldapIsDown() { + LdapTemplate ldapTemplate = mock(LdapTemplate.class); + given(ldapTemplate.executeReadOnly(any(ContextExecutor.class))) + .willThrow(new CommunicationException(new javax.naming.CommunicationException("Connection failed"))); + LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat((String) health.getDetails().get("error")) + .contains("Connection failed"); + verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class)); + } + +}