Simplify HealthMvcEndpoint security

Expose full health details if management security is disabled or
management role is present.

Fixes gh-7604
Closes gh-7852
pull/7873/head
Madhura Bhave 8 years ago committed by Phillip Webb
parent f8a53cf775
commit 530c3cd3be

@ -54,7 +54,6 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -162,7 +161,7 @@ public class EndpointWebMvcManagementContextConfiguration {
@ConditionalOnEnabledEndpoint("health") @ConditionalOnEnabledEndpoint("health")
public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) { public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) {
HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate, HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate,
isHealthSecure()); this.managementServerProperties.getSecurity().isEnabled());
if (this.healthMvcEndpointProperties.getMapping() != null) { if (this.healthMvcEndpointProperties.getMapping() != null) {
healthMvcEndpoint healthMvcEndpoint
.addStatusMapping(this.healthMvcEndpointProperties.getMapping()); .addStatusMapping(this.healthMvcEndpointProperties.getMapping());
@ -206,17 +205,6 @@ public class EndpointWebMvcManagementContextConfiguration {
return new AuditEventsMvcEndpoint(auditEventRepository); return new AuditEventsMvcEndpoint(auditEventRepository);
} }
private boolean isHealthSecure() {
return isSpringSecurityAvailable()
&& this.managementServerProperties.getSecurity().isEnabled();
}
private boolean isSpringSecurityAvailable() {
return ClassUtils.isPresent(
"org.springframework.security.config.annotation.web.WebSecurityConfigurer",
getClass().getClassLoader());
}
private static class LogFileCondition extends SpringBootCondition { private static class LogFileCondition extends SpringBootCondition {
@Override @Override

@ -16,7 +16,7 @@
package org.springframework.boot.actuate.cloudfoundry; package org.springframework.boot.actuate.cloudfoundry;
import java.security.Principal; import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
@ -36,7 +36,7 @@ class CloudFoundryHealthMvcEndpoint extends HealthMvcEndpoint {
} }
@Override @Override
protected boolean exposeHealthDetails(Principal principal) { protected boolean exposeHealthDetails(HttpServletRequest request) {
return true; return true;
} }

@ -16,12 +16,11 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
@ -33,10 +32,7 @@ import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -49,6 +45,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.1.0 * @since 1.1.0
*/ */
@ConfigurationProperties(prefix = "endpoints.health") @ConfigurationProperties(prefix = "endpoints.health")
@ -59,11 +56,7 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
private Map<String, HttpStatus> statusMapping = new HashMap<String, HttpStatus>(); private Map<String, HttpStatus> statusMapping = new HashMap<String, HttpStatus>();
private RelaxedPropertyResolver healthPropertyResolver; private RelaxedPropertyResolver securityPropertyResolver;
private RelaxedPropertyResolver endpointPropertyResolver;
private RelaxedPropertyResolver roleResolver;
private long lastAccess = 0; private long lastAccess = 0;
@ -86,11 +79,7 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
this.healthPropertyResolver = new RelaxedPropertyResolver(environment, this.securityPropertyResolver = new RelaxedPropertyResolver(environment,
"endpoints.health.");
this.endpointPropertyResolver = new RelaxedPropertyResolver(environment,
"endpoints.");
this.roleResolver = new RelaxedPropertyResolver(environment,
"management.security."); "management.security.");
} }
@ -136,12 +125,12 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public Object invoke(Principal principal) { public Object invoke(HttpServletRequest request) {
if (!getDelegate().isEnabled()) { if (!getDelegate().isEnabled()) {
// Shouldn't happen because the request mapping should not be registered // Shouldn't happen because the request mapping should not be registered
return getDisabledResponse(); return getDisabledResponse();
} }
Health health = getHealth(principal); Health health = getHealth(request);
HttpStatus status = getStatus(health); HttpStatus status = getStatus(health);
if (status != null) { if (status != null) {
return new ResponseEntity<Health>(health, status); return new ResponseEntity<Health>(health, status);
@ -163,13 +152,13 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
return null; return null;
} }
private Health getHealth(Principal principal) { private Health getHealth(HttpServletRequest request) {
long accessTime = System.currentTimeMillis(); long accessTime = System.currentTimeMillis();
if (isCacheStale(accessTime)) { if (isCacheStale(accessTime)) {
this.lastAccess = accessTime; this.lastAccess = accessTime;
this.cached = getDelegate().invoke(); this.cached = getDelegate().invoke();
} }
if (exposeHealthDetails(principal)) { if (exposeHealthDetails(request)) {
return this.cached; return this.cached;
} }
return Health.status(this.cached.getStatus()).build(); return Health.status(this.cached.getStatus()).build();
@ -182,44 +171,19 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
return (accessTime - this.lastAccess) >= getDelegate().getTimeToLive(); return (accessTime - this.lastAccess) >= getDelegate().getTimeToLive();
} }
protected boolean exposeHealthDetails(Principal principal) { protected boolean exposeHealthDetails(HttpServletRequest request) {
return isSecure(principal) || isUnrestricted(); if (!this.secure) {
} return true;
private boolean isSecure(Principal principal) {
if (principal == null || principal.getClass().getName().contains("Anonymous")) {
return false;
} }
if (isSpringSecurityAuthentication(principal)) { String[] roles = StringUtils.commaDelimitedListToStringArray(
Authentication authentication = (Authentication) principal; this.securityPropertyResolver.getProperty("roles", "ROLE_ACTUATOR"));
List<String> roles = Arrays.asList(StringUtils roles = StringUtils.trimArrayElements(roles);
.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
this.roleResolver.getProperty("roles", "ROLE_ACTUATOR"))));
for (GrantedAuthority authority : authentication.getAuthorities()) {
String name = authority.getAuthority();
for (String role : roles) { for (String role : roles) {
if (role.equals(name) || ("ROLE_" + role).equals(name)) { if (request.isUserInRole(role) || request.isUserInRole("ROLE_" + role)) {
return true; return true;
} }
} }
}
}
return false; return false;
} }
private boolean isSpringSecurityAuthentication(Principal principal) {
return ClassUtils.isPresent("org.springframework.security.core.Authentication",
null) && (principal instanceof Authentication);
}
private boolean isUnrestricted() {
Boolean sensitive = this.healthPropertyResolver.getProperty("sensitive",
Boolean.class);
if (sensitive == null) {
sensitive = this.endpointPropertyResolver.getProperty("sensitive",
Boolean.class);
}
return !this.secure && !Boolean.TRUE.equals(sensitive);
}
} }

@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@ -60,8 +61,9 @@ public class HealthMvcEndpointAutoConfigurationTests {
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
MockHttpServletRequest request = new MockHttpServletRequest();
Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) Health health = (Health) this.context.getBean(HealthMvcEndpoint.class)
.invoke(null); .invoke(request);
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails().get("foo")).isNull(); assertThat(health.getDetails().get("foo")).isNull();
} }

@ -18,20 +18,21 @@ package org.springframework.boot.actuate.endpoint.mvc;
import java.util.Collections; import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.mock.web.MockServletContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -44,36 +45,36 @@ import static org.mockito.Mockito.mock;
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Madhura Bhave
*/ */
public class HealthMvcEndpointTests { public class HealthMvcEndpointTests {
private static final PropertySource<?> NON_SENSITIVE = new MapPropertySource("test",
Collections.<String, Object>singletonMap("endpoints.health.sensitive",
"false"));
private static final PropertySource<?> SECURITY_ROLES = new MapPropertySource("test", private static final PropertySource<?> SECURITY_ROLES = new MapPropertySource("test",
Collections.<String, Object>singletonMap("management.security.roles", Collections.<String, Object>singletonMap("management.security.roles",
"HERO, USER")); "HERO, USER"));
private HttpServletRequest request = new MockHttpServletRequest();
private HealthEndpoint endpoint = null; private HealthEndpoint endpoint = null;
private HealthMvcEndpoint mvc = null; private HealthMvcEndpoint mvc = null;
private MockEnvironment environment; private MockEnvironment environment;
private UsernamePasswordAuthenticationToken user = createAuthenticationToken( private HttpServletRequest user = createAuthenticationToken(
"ROLE_USER"); "ROLE_USER");
private UsernamePasswordAuthenticationToken actuator = createAuthenticationToken( private HttpServletRequest actuator = createAuthenticationToken(
"ROLE_ACTUATOR"); "ROLE_ACTUATOR");
private UsernamePasswordAuthenticationToken hero = createAuthenticationToken( private HttpServletRequest hero = createAuthenticationToken(
"ROLE_HERO"); "ROLE_HERO");
private UsernamePasswordAuthenticationToken createAuthenticationToken( private HttpServletRequest createAuthenticationToken(
String authority) { String role) {
return new UsernamePasswordAuthenticationToken("user", "password", MockServletContext servletContext = new MockServletContext();
AuthorityUtils.commaSeparatedStringToAuthorityList(authority)); servletContext.declareRoles(role);
return new MockHttpServletRequest(servletContext);
} }
@Before @Before
@ -88,7 +89,7 @@ public class HealthMvcEndpointTests {
@Test @Test
public void up() { public void up() {
given(this.endpoint.invoke()).willReturn(new Health.Builder().up().build()); given(this.endpoint.invoke()).willReturn(new Health.Builder().up().build());
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof Health).isTrue(); assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue(); assertThat(((Health) result).getStatus() == Status.UP).isTrue();
} }
@ -97,7 +98,7 @@ public class HealthMvcEndpointTests {
@Test @Test
public void down() { public void down() {
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof ResponseEntity).isTrue(); assertThat(result instanceof ResponseEntity).isTrue();
ResponseEntity<Health> response = (ResponseEntity<Health>) result; ResponseEntity<Health> response = (ResponseEntity<Health>) result;
assertThat(response.getBody().getStatus() == Status.DOWN).isTrue(); assertThat(response.getBody().getStatus() == Status.DOWN).isTrue();
@ -111,7 +112,7 @@ public class HealthMvcEndpointTests {
.willReturn(new Health.Builder().status("OK").build()); .willReturn(new Health.Builder().status("OK").build());
this.mvc.setStatusMapping( this.mvc.setStatusMapping(
Collections.singletonMap("OK", HttpStatus.INTERNAL_SERVER_ERROR)); Collections.singletonMap("OK", HttpStatus.INTERNAL_SERVER_ERROR));
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof ResponseEntity).isTrue(); assertThat(result instanceof ResponseEntity).isTrue();
ResponseEntity<Health> response = (ResponseEntity<Health>) result; ResponseEntity<Health> response = (ResponseEntity<Health>) result;
assertThat(response.getBody().getStatus().equals(new Status("OK"))).isTrue(); assertThat(response.getBody().getStatus().equals(new Status("OK"))).isTrue();
@ -125,7 +126,7 @@ public class HealthMvcEndpointTests {
.willReturn(new Health.Builder().outOfService().build()); .willReturn(new Health.Builder().outOfService().build());
this.mvc.setStatusMapping(Collections.singletonMap("out-of-service", this.mvc.setStatusMapping(Collections.singletonMap("out-of-service",
HttpStatus.INTERNAL_SERVER_ERROR)); HttpStatus.INTERNAL_SERVER_ERROR));
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof ResponseEntity).isTrue(); assertThat(result instanceof ResponseEntity).isTrue();
ResponseEntity<Health> response = (ResponseEntity<Health>) result; ResponseEntity<Health> response = (ResponseEntity<Health>) result;
assertThat(response.getBody().getStatus().equals(Status.OUT_OF_SERVICE)).isTrue(); assertThat(response.getBody().getStatus().equals(Status.OUT_OF_SERVICE)).isTrue();
@ -133,10 +134,9 @@ public class HealthMvcEndpointTests {
} }
@Test @Test
public void secureEvenWhenNotSensitive() { public void presenceOfRightRoleShouldExposeDetails() {
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
given(this.endpoint.isSensitive()).willReturn(false);
Object result = this.mvc.invoke(this.actuator); Object result = this.mvc.invoke(this.actuator);
assertThat(result instanceof Health).isTrue(); assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue(); assertThat(((Health) result).getStatus() == Status.UP).isTrue();
@ -144,7 +144,18 @@ public class HealthMvcEndpointTests {
} }
@Test @Test
public void secureNonAdmin() { public void managementSecurityDisabledShouldExposeDetails() throws Exception {
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(this.user);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar");
}
@Test
public void rightRoleNotPresentShouldNotExposeDetails() {
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(this.user); Object result = this.mvc.invoke(this.user);
@ -154,7 +165,7 @@ public class HealthMvcEndpointTests {
} }
@Test @Test
public void secureCustomRole() { public void customRolePresentShouldExposeDetails() {
this.environment.getPropertySources().addLast(SECURITY_ROLES); this.environment.getPropertySources().addLast(SECURITY_ROLES);
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
@ -165,7 +176,7 @@ public class HealthMvcEndpointTests {
} }
@Test @Test
public void secureCustomRoleNoAccess() { public void customRoleShouldNotExposeDetailsForDefaultRole() {
this.environment.getPropertySources().addLast(SECURITY_ROLES); this.environment.getPropertySources().addLast(SECURITY_ROLES);
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
@ -178,7 +189,6 @@ public class HealthMvcEndpointTests {
@Test @Test
public void healthIsCached() { public void healthIsCached() {
given(this.endpoint.getTimeToLive()).willReturn(10000L); given(this.endpoint.getTimeToLive()).willReturn(10000L);
given(this.endpoint.isSensitive()).willReturn(true);
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(this.actuator); Object result = this.mvc.invoke(this.actuator);
@ -188,7 +198,7 @@ public class HealthMvcEndpointTests {
assertThat(health.getDetails()).hasSize(1); assertThat(health.getDetails()).hasSize(1);
assertThat(health.getDetails().get("foo")).isEqualTo("bar"); assertThat(health.getDetails().get("foo")).isEqualTo("bar");
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());
result = this.mvc.invoke(null); // insecure now result = this.mvc.invoke(this.request); // insecure now
assertThat(result instanceof Health).isTrue(); assertThat(result instanceof Health).isTrue();
health = (Health) result; health = (Health) result;
// so the result is cached // so the result is cached
@ -197,52 +207,16 @@ public class HealthMvcEndpointTests {
assertThat(health.getDetails()).isEmpty(); assertThat(health.getDetails()).isEmpty();
} }
@Test
public void insecureAnonymousAccessUnrestricted() {
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
this.mvc.setEnvironment(this.environment);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar");
}
@Test
public void insensitiveAnonymousAccessRestricted() {
this.environment.getPropertySources().addLast(NON_SENSITIVE);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isNull();
}
@Test
public void insecureInsensitiveAnonymousAccessUnrestricted() {
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
this.mvc.setEnvironment(this.environment);
this.environment.getPropertySources().addLast(NON_SENSITIVE);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar");
}
@Test @Test
public void noCachingWhenTimeToLiveIsZero() { public void noCachingWhenTimeToLiveIsZero() {
given(this.endpoint.getTimeToLive()).willReturn(0L); given(this.endpoint.getTimeToLive()).willReturn(0L);
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof Health).isTrue(); assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue(); assertThat(((Health) result).getStatus() == Status.UP).isTrue();
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());
result = this.mvc.invoke(null); result = this.mvc.invoke(this.request);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Health health = ((ResponseEntity<Health>) result).getBody(); Health health = ((ResponseEntity<Health>) result).getBody();
assertThat(health.getStatus() == Status.DOWN).isTrue(); assertThat(health.getStatus() == Status.DOWN).isTrue();
@ -251,59 +225,16 @@ public class HealthMvcEndpointTests {
@Test @Test
public void newValueIsReturnedOnceTtlExpires() throws InterruptedException { public void newValueIsReturnedOnceTtlExpires() throws InterruptedException {
given(this.endpoint.getTimeToLive()).willReturn(50L); given(this.endpoint.getTimeToLive()).willReturn(50L);
given(this.endpoint.isSensitive()).willReturn(false);
given(this.endpoint.invoke()) given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null); Object result = this.mvc.invoke(this.request);
assertThat(result instanceof Health).isTrue(); assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue(); assertThat(((Health) result).getStatus() == Status.UP).isTrue();
Thread.sleep(100); Thread.sleep(100);
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());
result = this.mvc.invoke(null); result = this.mvc.invoke(this.request);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Health health = ((ResponseEntity<Health>) result).getBody(); Health health = ((ResponseEntity<Health>) result).getBody();
assertThat(health.getStatus() == Status.DOWN).isTrue(); assertThat(health.getStatus() == Status.DOWN).isTrue();
} }
@Test
public void detailIsHiddenWhenAllEndpointsAreSensitive() {
EnvironmentTestUtils.addEnvironment(this.environment, "endpoints.sensitive:true");
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
this.mvc.setEnvironment(this.environment);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isNull();
}
@Test
public void detailIsHiddenWhenHealthEndpointIsSensitive() {
EnvironmentTestUtils.addEnvironment(this.environment,
"endpoints.health.sensitive:true");
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
this.mvc.setEnvironment(this.environment);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isNull();
}
@Test
public void detailIsHiddenWhenOnlyHealthEndpointIsSensitive() {
EnvironmentTestUtils.addEnvironment(this.environment,
"endpoints.health.sensitive:true", "endpoints.sensitive:false");
this.mvc = new HealthMvcEndpoint(this.endpoint, false);
this.mvc.setEnvironment(this.environment);
given(this.endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isNull();
}
} }

@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfi
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.junit.runner.classpath.ClassPathExclusions; import org.springframework.boot.junit.runner.classpath.ClassPathExclusions;
import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
@ -48,6 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* Integration tests for the health endpoint when Spring Security is not available. * Integration tests for the health endpoint when Spring Security is not available.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Madhura Bhave
*/ */
@RunWith(ModifiedClassPathRunner.class) @RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("spring-security-*.jar") @ClassPathExclusions("spring-security-*.jar")
@ -61,14 +63,28 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests {
} }
@Test @Test
public void healthDetailIsPresent() throws Exception { public void healthDetailNotPresent() throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/health")).andExpect(status().isOk()) mockMvc.perform(get("/health")).andExpect(status().isOk())
.andExpect(content().string(containsString("\"hello\":\"world\""))); .andExpect(content().string(containsString("\"status\":\"UP\"")));
}
@Test
public void healthDetailPresent() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context,
"management.security.enabled:false");
this.context.refresh();
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/health")).andExpect(status().isOk())
.andExpect(content().string(containsString(
"\"status\":\"UP\",\"test\":{\"status\":\"UP\",\"hello\":\"world\"}")));
} }
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, @ImportAutoConfiguration({ JacksonAutoConfiguration.class,

Loading…
Cancel
Save