Provide a way to opt-in to endpoint enablement

Update AbstractEndpoint so that the `enable` property is optional and
when it not specified the `endpoints.enabled` property will be used.

This allows users to switch the way that endpoints are enabled. Rather
than opting-out specific endpoint enablement the `endpoints.enabled`
property can be set to `false` and specific endpoints can be opted-in.

Fixes gh-2102
pull/2111/head
Phillip Webb 10 years ago
parent 3e2433c842
commit a27217ae43

@ -19,13 +19,20 @@ package org.springframework.boot.actuate.endpoint;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
/** /**
* Abstract base for {@link Endpoint} implementations. * Abstract base for {@link Endpoint} implementations.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
*/ */
public abstract class AbstractEndpoint<T> implements Endpoint<T> { public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAware {
private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";
private Environment environment;
/** /**
* Endpoint identifier. With HTTP monitoring the identifier of the endpoint is mapped * Endpoint identifier. With HTTP monitoring the identifier of the endpoint is mapped
@ -36,25 +43,52 @@ public abstract class AbstractEndpoint<T> implements Endpoint<T> {
private String id; private String id;
/** /**
* Enable security on the endpoint. * Mark if the endpoint exposes sensitive information.
*/ */
private boolean sensitive; private boolean sensitive;
/** /**
* Enable the endpoint. * Enable the endpoint.
*/ */
private boolean enabled = true; private Boolean enabled;
/**
* Create a new sensitive endpoint instance. The enpoint will enabled flag will be
* based on the spring {@link Environment} unless explicitly set.
* @param id the endpoint ID
*/
public AbstractEndpoint(String id) { public AbstractEndpoint(String id) {
this(id, true, true); this(id, true);
}
/**
* Create a new endpoint instance. The enpoint will enabled flag will be based on the
* spring {@link Environment} unless explicitly set.
* @param id the endpoint ID
* @param sensitive if the endpoint is sensitive
*/
public AbstractEndpoint(String id, boolean sensitive) {
this.id = id;
this.sensitive = sensitive;
} }
/**
* Create a new endpoint instance.
* @param id the endpoint ID
* @param sensitive if the endpoint is sensitive
* @param enabled if the endpoint is enabled or not.
*/
public AbstractEndpoint(String id, boolean sensitive, boolean enabled) { public AbstractEndpoint(String id, boolean sensitive, boolean enabled) {
this.id = id; this.id = id;
this.sensitive = sensitive; this.sensitive = sensitive;
this.enabled = enabled; this.enabled = enabled;
} }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override @Override
public String getId() { public String getId() {
return this.id; return this.id;
@ -66,10 +100,16 @@ public abstract class AbstractEndpoint<T> implements Endpoint<T> {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return this.enabled; if (this.enabled != null) {
return this.enabled;
}
if (this.environment != null) {
this.environment.getProperty(ENDPOINTS_ENABLED_PROPERTY, Boolean.class, true);
}
return true;
} }
public void setEnabled(boolean enabled) { public void setEnabled(Boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }

@ -18,11 +18,13 @@ package org.springframework.boot.actuate.endpoint;
/** /**
* An endpoint that can be used to expose useful information to operations. Usually * An endpoint that can be used to expose useful information to operations. Usually
* exposed via Spring MVC but could also be exposed using some other technique. * exposed via Spring MVC but could also be exposed using some other technique. Consider
* extending {@link AbstractEndpoint} if you are developing your own endpoint.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis * @author Christian Dupuis
* @see AbstractEndpoint
*/ */
public interface Endpoint<T> { public interface Endpoint<T> {

@ -47,7 +47,7 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
*/ */
public HealthEndpoint(HealthAggregator healthAggregator, public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
super("health", false, true); super("health", false);
Assert.notNull(healthAggregator, "HealthAggregator must not be null"); Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null"); Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator( CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(

@ -39,7 +39,7 @@ public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
* @param info the info to expose * @param info the info to expose
*/ */
public InfoEndpoint(Map<String, ? extends Object> info) { public InfoEndpoint(Map<String, ? extends Object> info) {
super("info", false, true); super("info", false);
Assert.notNull(info, "Info must not be null"); Assert.notNull(info, "Info must not be null");
this.info = info; this.info = info;
} }

@ -1,4 +1,10 @@
{"properties": [ {"properties": [
{
"name": "endpoints.enabled",
"type": "java.lang.Boolean",
"description": "Enable endpoints.",
"defaultValue": true
},
{ {
"name": "endpoints.configprops.keys-to-sanitize", "name": "endpoints.configprops.keys-to-sanitize",
"type": "java.lang.String", "type": "java.lang.String",

@ -202,7 +202,7 @@ public class EndpointMBeanExportAutoConfigurationTests {
protected static class ManagedEndpoint extends AbstractEndpoint<Boolean> { protected static class ManagedEndpoint extends AbstractEndpoint<Boolean> {
public ManagedEndpoint() { public ManagedEndpoint() {
super("managed", true, true); super("managed", true);
} }
@Override @Override
@ -224,7 +224,7 @@ public class EndpointMBeanExportAutoConfigurationTests {
class Nested extends AbstractEndpoint<Boolean> { class Nested extends AbstractEndpoint<Boolean> {
public Nested() { public Nested() {
super("managed", true, true); super("managed", true);
} }
@Override @Override

@ -102,6 +102,37 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
assertThat(getEndpointBean().isSensitive(), equalTo(!this.sensitive)); assertThat(getEndpointBean().isSensitive(), equalTo(!this.sensitive));
} }
@Test
public void isEnabledByDefault() throws Exception {
assertThat(getEndpointBean().isEnabled(), equalTo(true));
}
@Test
public void isEnabledFallbackToEnvironment() throws Exception {
this.context = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.<String, Object> singletonMap(this.property + ".enabled",
false));
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().isEnabled(), equalTo(false));
}
@Test
@SuppressWarnings("rawtypes")
public void isExplicitlyEnabled() throws Exception {
this.context = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.<String, Object> singletonMap(this.property + ".enabled",
false));
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
((AbstractEndpoint) getEndpointBean()).setEnabled(true);
assertThat(getEndpointBean().isEnabled(), equalTo(true));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected T getEndpointBean() { protected T getEndpointBean() {
return (T) this.context.getBean(this.type); return (T) this.context.getBean(this.type);

@ -26,6 +26,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextClosedEvent;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -43,6 +44,12 @@ public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoin
"endpoints.shutdown"); "endpoints.shutdown");
} }
@Override
public void isEnabledByDefault() throws Exception {
// Shutdown is dangerous so is disabled by default
assertThat(getEndpointBean().isEnabled(), equalTo(false));
}
@Test @Test
public void invoke() throws Exception { public void invoke() throws Exception {
CountDownLatch latch = this.context.getBean(Config.class).latch; CountDownLatch latch = this.context.getBean(Config.class).latch;
@ -61,7 +68,6 @@ public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoin
@Bean @Bean
public ShutdownEndpoint endpoint() { public ShutdownEndpoint endpoint() {
ShutdownEndpoint endpoint = new ShutdownEndpoint(); ShutdownEndpoint endpoint = new ShutdownEndpoint();
endpoint.setEnabled(true);
return endpoint; return endpoint;
} }

@ -135,6 +135,16 @@ of the `beans` endpoint and also enables `shutdown`.
NOTE: The prefix ‟`endpoints` + `.` + `name`” is used to uniquely identify the endpoint NOTE: The prefix ‟`endpoints` + `.` + `name`” is used to uniquely identify the endpoint
that is being configured. that is being configured.
By default, all endpoints except for `shutdown` are enabled. If you prefer to
specifically "`opt-in`" endpoint enablement you can use the `endpoints.enabled` property.
For example, the following will disable _all_ endpoints except for `info`:
[source,properties,indent=0]
----
endpoints.enabled=false
endpoints.info.enabled=true
----
[[production-ready-health]] [[production-ready-health]]
@ -394,6 +404,10 @@ in your `application.properties`:
management.security.role=SUPERUSER management.security.role=SUPERUSER
---- ----
TIP: If you don't use Spring Security and your HTTP endpoints are exposed publicly,
you should carefully consider which endpoints you enable. See
<<production-ready-customizing-endpoints>> for details of how you can set
`endpoints.enabled` to `false` then "`opt-in`" only specific endpoints.
[[production-ready-customizing-management-server-context-path]] [[production-ready-customizing-management-server-context-path]]

Loading…
Cancel
Save