Provide more control over when the health endpoint shows details

Closes gh-11869
pull/11886/merge
Andy Wilkinson 7 years ago
parent 1975d51106
commit 8605499a64

@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;
/**
* Reactive {@link EndpointExtension} for the {@link HealthEndpoint} that always exposes
@ -45,7 +46,7 @@ public class CloudFoundryReactiveHealthEndpointWebExtension {
@ReadOperation
public Mono<WebEndpointResponse<Health>> health() {
return this.delegate.health(true);
return this.delegate.health(null, ShowDetails.ALWAYS);
}
}

@ -23,6 +23,7 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;
/**
* {@link EndpointExtension} for the {@link HealthEndpoint} that always exposes full
@ -42,7 +43,7 @@ public class CloudFoundryHealthEndpointWebExtension {
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
return this.delegate.getHealth(true);
return this.delegate.getHealth(null, ShowDetails.ALWAYS);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ShowDetails;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@ -28,16 +29,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class HealthEndpointProperties {
/**
* Whether to show full health details instead of just the status when exposed over a
* potentially insecure connection.
* Whether to show full health details.
*/
private boolean showDetails;
private ShowDetails showDetails = ShowDetails.WHEN_AUTHENTICATED;
public boolean isShowDetails() {
public ShowDetails getShowDetails() {
return this.showDetails;
}
public void setShowDetails(boolean showDetails) {
public void setShowDetails(ShowDetails showDetails) {
this.showDetails = showDetails;
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -84,7 +84,7 @@ class HealthEndpointWebExtensionConfiguration {
HealthStatusHttpMapper healthStatusHttpMapper,
HealthEndpointProperties properties) {
return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
healthStatusHttpMapper, properties.isShowDetails());
healthStatusHttpMapper, properties.getShowDetails());
}
}
@ -103,7 +103,7 @@ class HealthEndpointWebExtensionConfiguration {
HealthEndpointProperties properties) {
return new HealthEndpointWebExtension(
HealthIndicatorBeansComposite.get(applicationContext),
healthStatusHttpMapper, properties.isShowDetails());
healthStatusHttpMapper, properties.getShowDetails());
}
}

@ -56,9 +56,9 @@ public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentati
responseFields(
fieldWithPath("status").description(
"Overall status of the application."),
fieldWithPath("details")
.description("Details of the health of the application "
+ "(only included when `management.endpoint.health.show-details` is `true`)."),
fieldWithPath("details").description(
"Details of the health of the application. Presence is controlled by "
+ "`management.endpoint.health.show-details`)."),
fieldWithPath("details.*.status").description(
"Status of a specific part of the application."),
subsectionWithPath("details.*.details").description(

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -65,7 +65,7 @@ public class HealthEndpointAutoConfigurationTests {
@Test
public void healthEndpointAdaptReactiveHealthIndicator() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=true")
.withPropertyValues("management.endpoint.health.show-details=always")
.withUserConfiguration(ReactiveHealthIndicatorConfiguration.class)
.run((context) -> {
ReactiveHealthIndicator indicator = context.getBean(
@ -81,7 +81,7 @@ public class HealthEndpointAutoConfigurationTests {
@Test
public void healthEndpointMergeRegularAndReactive() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=true")
.withPropertyValues("management.endpoint.health.show-details=always")
.withUserConfiguration(HealthIndicatorConfiguration.class,
ReactiveHealthIndicatorConfiguration.class)
.run((context) -> {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.security.Principal;
import java.util.Map;
import org.junit.Test;
@ -27,6 +28,7 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HealthEndpointAutoConfiguration} in a servlet environment.
@ -70,4 +72,47 @@ public class HealthEndpointWebExtensionTests {
});
}
@Test
public void unauthenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context
.getBean(HealthEndpointWebExtension.class);
assertThat(extension.getHealth(null).getBody().getDetails()).isEmpty();
});
}
@Test
public void authenticatedUsersAreShownDetailsByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context
.getBean(HealthEndpointWebExtension.class);
assertThat(extension.getHealth(mock(Principal.class)).getBody().getDetails())
.isNotEmpty();
});
}
@Test
public void unauthenticatedUsersCanBeShownDetails() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=always")
.run((context) -> {
HealthEndpointWebExtension extension = context
.getBean(HealthEndpointWebExtension.class);
assertThat(extension.getHealth(null).getBody().getDetails())
.isNotEmpty();
});
}
@Test
public void detailsCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=never")
.run((context) -> {
HealthEndpointWebExtension extension = context
.getBean(HealthEndpointWebExtension.class);
assertThat(extension.getHealth(mock(Principal.class)).getBody()
.getDetails()).isEmpty();
});
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.security.Principal;
import java.util.Map;
import org.junit.Test;
@ -33,6 +34,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HealthEndpointAutoConfiguration} in a reactive environment.
@ -84,7 +86,8 @@ public class ReactiveHealthEndpointWebExtensionTests {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
Health endpointHealth = endpoint.health();
Health extensionHealth = extension.health(true).block().getBody();
Health extensionHealth = extension.health(mock(Principal.class))
.block().getBody();
assertThat(endpointHealth.getDetails())
.containsOnlyKeys("application", "first", "second");
assertThat(extensionHealth.getDetails())
@ -92,6 +95,49 @@ public class ReactiveHealthEndpointWebExtensionTests {
});
}
@Test
public void unauthenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(null).block().getBody().getDetails()).isEmpty();
});
}
@Test
public void authenticatedUsersAreShownDetailsByDefault() {
this.contextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(mock(Principal.class)).block().getBody()
.getDetails()).isNotEmpty();
});
}
@Test
public void unauthenticatedUsersCanBeShownDetails() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=always")
.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(null).block().getBody().getDetails())
.isNotEmpty();
});
}
@Test
public void detailsCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner
.withPropertyValues("management.endpoint.health.show-details=never")
.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(mock(Principal.class)).block().getBody()
.getDetails()).isEmpty();
});
}
@Configuration
static class HealthIndicatorsConfiguration {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -16,9 +16,12 @@
package org.springframework.boot.actuate.health;
import java.security.Principal;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.lang.Nullable;
/**
* {@link EndpointWebExtension} for the {@link HealthEndpoint}.
@ -38,24 +41,27 @@ public class HealthEndpointWebExtension {
private final HealthStatusHttpMapper statusHttpMapper;
private final boolean showDetails;
private final ShowDetails showDetails;
public HealthEndpointWebExtension(HealthIndicator delegate,
HealthStatusHttpMapper statusHttpMapper, boolean showDetails) {
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
this.showDetails = showDetails;
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
return getHealth(this.showDetails);
public WebEndpointResponse<Health> getHealth(@Nullable Principal principal) {
return getHealth(principal, this.showDetails);
}
public WebEndpointResponse<Health> getHealth(boolean showDetails) {
public WebEndpointResponse<Health> getHealth(Principal principal,
ShowDetails showDetails) {
Health health = this.delegate.health();
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
if (!showDetails) {
if (this.showDetails == ShowDetails.NEVER
|| (this.showDetails == ShowDetails.WHEN_AUTHENTICATED
&& principal == null)) {
health = Health.status(health.getStatus()).build();
}
return new WebEndpointResponse<>(health, status);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -16,11 +16,14 @@
package org.springframework.boot.actuate.health;
import java.security.Principal;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.lang.Nullable;
/**
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}.
@ -35,24 +38,27 @@ public class ReactiveHealthEndpointWebExtension {
private final HealthStatusHttpMapper statusHttpMapper;
private final boolean showDetails;
private final ShowDetails showDetails;
public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
HealthStatusHttpMapper statusHttpMapper, boolean showDetails) {
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
this.showDetails = showDetails;
}
@ReadOperation
public Mono<WebEndpointResponse<Health>> health() {
return health(this.showDetails);
public Mono<WebEndpointResponse<Health>> health(@Nullable Principal principal) {
return health(principal, this.showDetails);
}
public Mono<WebEndpointResponse<Health>> health(boolean showDetails) {
public Mono<WebEndpointResponse<Health>> health(Principal principal,
ShowDetails showDetails) {
return this.delegate.health().map((health) -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
if (!showDetails) {
if (this.showDetails == ShowDetails.NEVER
|| (this.showDetails == ShowDetails.WHEN_AUTHENTICATED
&& principal == null)) {
health = Health.status(health.getStatus()).build();
}
return new WebEndpointResponse<>(health, status);

@ -0,0 +1,43 @@
/*
* Copyright 2012-2018 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;
/**
* Options for showing details in responses from the {@link HealthEndpoint} web
* extensions.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
public enum ShowDetails {
/**
* Never show details in the response.
*/
NEVER,
/**
* Show details in the response when accessed by an authenticated user.
*/
WHEN_AUTHENTICATED,
/**
* Always show details in the response.
*/
ALWAYS;
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -75,7 +75,7 @@ public class HealthEndpointWebIntegrationTests {
return new HealthEndpointWebExtension(
new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators),
new HealthStatusHttpMapper(), true);
new HealthStatusHttpMapper(), ShowDetails.ALWAYS);
}
@Bean

@ -513,9 +513,28 @@ moves to a child context with all the other web endpoints.
You can use health information to check the status of your running application. It is
often used by monitoring software to alert someone when a production system goes down.
The information exposed by the `health` endpoint depends on the
`management.endpoint.health.show-details` property. By default, the property's value is
`false` and a simple "`status`" message is returned. When the property's value is set to
`true`, additional details from the individual health indicators are also displayed.
`management.endpoint.health.show-details` property which can be configured with one of the
following values:
[cols="1, 3"]
|===
|Name |Description
|`never`
|Details are never shown.
|`when-authenticated`
|Details are only shown to authenticated users.
|`always`
|Details are shown to all users.
|===
The default value is `when-authenticated`.
NOTE: If you have secured your application and wish to use `always`, your security
configuration must permit access to the health endpoint for both authenticated and
unauthenticated users.
Health information is collected from all
{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] beans

@ -1,2 +1,2 @@
management.endpoints.web.expose=*
management.endpoint.health.show-details=true
management.endpoint.health.show-details=always

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,7 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"management.server.port=0", "management.endpoints.web.base-path=/admin" })
"management.server.port=0", "management.endpoints.web.base-path=/admin",
"management.endpoint.health.show-details=never" })
public class ManagementPortAndPathSampleActuatorApplicationTests {
@LocalServerPort

@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"management.server.port=0", "management.endpoint.health.show-details=true" })
"management.server.port=0", "management.endpoint.health.show-details=always" })
public class ManagementPortSampleActuatorApplicationTests {
@LocalServerPort

@ -1,3 +1,3 @@
server.error.path: /oops
management.endpoint.health.show-details: true
management.endpoint.health.show-details: always
management.endpoints.web.base-path: /admin

@ -34,7 +34,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
* @author Madhura Bhave
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "management.endpoint.health.show-details=never")
public class SampleSecureWebFluxApplicationTests {
@Autowired

Loading…
Cancel
Save