Disable auditing infrastructure by default

Prior to this commit, the audit auto-configuration provided
an `InMemoryAuditEventRepository` bean. This commit changes the auto-config
so that an `AuditEventRepository` is not provided and instead the auto-config
is conditional on the presence of a `AuditEventRepository` bean. This is done
to encourage the use of a custom implementation of `AuditEventRepository`
since the in-memory one is quite limited and not suitable for production.
A flag is available if the auto-configuration needs to be turned off even
in the presence of a bean.

Closes gh-16110
pull/16714/head
Madhura Bhave 6 years ago
parent e2b15c3f2a
commit 07d6eb6397

@ -16,10 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.audit; package org.springframework.boot.actuate.autoconfigure.audit;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.audit.listener.AbstractAuditListener; import org.springframework.boot.actuate.audit.listener.AbstractAuditListener;
import org.springframework.boot.actuate.audit.listener.AuditListener; import org.springframework.boot.actuate.audit.listener.AuditListener;
import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener;
@ -27,8 +25,10 @@ import org.springframework.boot.actuate.security.AbstractAuthorizationAuditListe
import org.springframework.boot.actuate.security.AuthenticationAuditListener; import org.springframework.boot.actuate.security.AuthenticationAuditListener;
import org.springframework.boot.actuate.security.AuthorizationAuditListener; import org.springframework.boot.actuate.security.AuthorizationAuditListener;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -40,13 +40,15 @@ import org.springframework.context.annotation.Configuration;
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnProperty(prefix = "management.auditevents", name = "enabled",
matchIfMissing = true)
public class AuditAutoConfiguration { public class AuditAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(AbstractAuditListener.class) @ConditionalOnMissingBean(AbstractAuditListener.class)
public AuditListener auditListener( public AuditListener auditListener(AuditEventRepository auditEventRepository) {
ObjectProvider<AuditEventRepository> auditEventRepository) throws Exception { return new AuditListener(auditEventRepository);
return new AuditListener(auditEventRepository.getIfAvailable());
} }
@Bean @Bean
@ -65,15 +67,4 @@ public class AuditAutoConfiguration {
return new AuthorizationAuditListener(); return new AuthorizationAuditListener();
} }
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public InMemoryAuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
} }

@ -65,6 +65,12 @@
"description": "Whether to skip SSL verification for Cloud Foundry actuator endpoint security calls.", "description": "Whether to skip SSL verification for Cloud Foundry actuator endpoint security calls.",
"defaultValue": false "defaultValue": false
}, },
{
"name": "management.auditevents.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable storage of audit events.",
"defaultValue": true
},
{ {
"name": "management.health.cassandra.enabled", "name": "management.health.cassandra.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",

@ -22,12 +22,14 @@ import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.audit.listener.AbstractAuditListener; import org.springframework.boot.actuate.audit.listener.AbstractAuditListener;
import org.springframework.boot.actuate.audit.listener.AuditListener;
import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener;
import org.springframework.boot.actuate.security.AbstractAuthorizationAuditListener; import org.springframework.boot.actuate.security.AbstractAuthorizationAuditListener;
import org.springframework.boot.actuate.security.AuthenticationAuditListener; import org.springframework.boot.actuate.security.AuthenticationAuditListener;
import org.springframework.boot.actuate.security.AuthorizationAuditListener; import org.springframework.boot.actuate.security.AuthorizationAuditListener;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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.security.access.event.AbstractAuthorizationEvent; import org.springframework.security.access.event.AbstractAuthorizationEvent;
@ -40,54 +42,71 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Dave Syer * @author Dave Syer
* @author Vedran Pavic * @author Vedran Pavic
* @author Madhura Bhave
*/ */
public class AuditAutoConfigurationTests { public class AuditAutoConfigurationTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AuditAutoConfiguration.class));
@Test @Test
public void defaultConfiguration() { public void autoConfigurationIsDisabledByDefault() {
registerAndRefresh(AuditAutoConfiguration.class); this.contextRunner.run((context) -> assertThat(context)
assertThat(this.context.getBean(AuditEventRepository.class)).isNotNull(); .doesNotHaveBean(AuditAutoConfiguration.class));
assertThat(this.context.getBean(AuthenticationAuditListener.class)).isNotNull();
assertThat(this.context.getBean(AuthorizationAuditListener.class)).isNotNull();
} }
@Test @Test
public void ownAuditEventRepository() { public void autoConfigurationIsEnabledWhenAuditEventRepositoryBeanPresent() {
registerAndRefresh(CustomAuditEventRepositoryConfiguration.class, this.contextRunner
AuditAutoConfiguration.class); .withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
assertThat(this.context.getBean(AuditEventRepository.class)) .run((context) -> {
.isInstanceOf(TestAuditEventRepository.class); assertThat(context.getBean(AuditEventRepository.class)).isNotNull();
assertThat(context.getBean(AuthenticationAuditListener.class))
.isNotNull();
assertThat(context.getBean(AuthorizationAuditListener.class))
.isNotNull();
});
} }
@Test @Test
public void ownAuthenticationAuditListener() { public void ownAuthenticationAuditListener() {
registerAndRefresh(CustomAuthenticationAuditListenerConfiguration.class, this.contextRunner
AuditAutoConfiguration.class); .withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
assertThat(this.context.getBean(AbstractAuthenticationAuditListener.class)) .withUserConfiguration(
.isInstanceOf(TestAuthenticationAuditListener.class); CustomAuthenticationAuditListenerConfiguration.class)
.run((context) -> assertThat(
context.getBean(AbstractAuthenticationAuditListener.class))
.isInstanceOf(TestAuthenticationAuditListener.class));
} }
@Test @Test
public void ownAuthorizationAuditListener() { public void ownAuthorizationAuditListener() {
registerAndRefresh(CustomAuthorizationAuditListenerConfiguration.class, this.contextRunner
AuditAutoConfiguration.class); .withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
assertThat(this.context.getBean(AbstractAuthorizationAuditListener.class)) .withUserConfiguration(
.isInstanceOf(TestAuthorizationAuditListener.class); CustomAuthorizationAuditListenerConfiguration.class)
.run((context) -> assertThat(
context.getBean(AbstractAuthorizationAuditListener.class))
.isInstanceOf(TestAuthorizationAuditListener.class));
} }
@Test @Test
public void ownAuditListener() { public void ownAuditListener() {
registerAndRefresh(CustomAuditListenerConfiguration.class, this.contextRunner
AuditAutoConfiguration.class); .withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
assertThat(this.context.getBean(AbstractAuditListener.class)) .withUserConfiguration(CustomAuditListenerConfiguration.class)
.isInstanceOf(TestAuditListener.class); .run((context) -> assertThat(context.getBean(AbstractAuditListener.class))
.isInstanceOf(TestAuditListener.class));
} }
private void registerAndRefresh(Class<?>... annotatedClasses) { @Test
this.context.register(annotatedClasses); public void backsOffWhenDisabled() {
this.context.refresh(); this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withPropertyValues("management.auditevents.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean(AuditListener.class)
.doesNotHaveBean(AuthenticationAuditListener.class)
.doesNotHaveBean(AuthorizationAuditListener.class));
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)

@ -19,8 +19,11 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -38,14 +41,24 @@ public class AuditEventsEndpointAutoConfigurationTests {
AuditEventsEndpointAutoConfiguration.class)); AuditEventsEndpointAutoConfiguration.class));
@Test @Test
public void runShouldHaveEndpointBean() { public void runWhenRepositoryBeanAvailableShouldHaveEndpointBean() {
this.contextRunner this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withPropertyValues( .withPropertyValues(
"management.endpoints.web.exposure.include=auditevents") "management.endpoints.web.exposure.include=auditevents")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.hasSingleBean(AuditEventsEndpoint.class)); .hasSingleBean(AuditEventsEndpoint.class));
} }
@Test
public void endpointBacksOffWhenRepositoryNotAvailable() {
this.contextRunner
.withPropertyValues(
"management.endpoints.web.exposure.include=auditevents")
.run((context) -> assertThat(context)
.doesNotHaveBean(AuditEventsEndpoint.class));
}
@Test @Test
public void runWhenNotExposedShouldNotHaveEndpointBean() { public void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
@ -55,10 +68,21 @@ public class AuditEventsEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpoint() { public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpoint() {
this.contextRunner this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withPropertyValues("management.endpoint.auditevents.enabled:false") .withPropertyValues("management.endpoint.auditevents.enabled:false")
.withPropertyValues("management.endpoints.web.exposure.include=*") .withPropertyValues("management.endpoints.web.exposure.include=*")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(AuditEventsEndpoint.class)); .doesNotHaveBean(AuditEventsEndpoint.class));
} }
@Configuration(proxyBeanMethods = false)
public static class CustomAuditEventRepositoryConfiguration {
@Bean
public InMemoryAuditEventRepository testAuditEventRepository() {
return new InMemoryAuditEventRepository();
}
}
} }

@ -26,6 +26,7 @@ import javax.management.ReflectionException;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
@ -53,7 +54,8 @@ public class JmxEndpointIntegrationTests {
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HttpTraceAutoConfiguration.class)) HttpTraceAutoConfiguration.class))
.withUserConfiguration(HttpTraceRepositoryConfiguration.class) .withUserConfiguration(HttpTraceRepositoryConfiguration.class,
AuditEventRepositoryConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true").withConfiguration( .withPropertyValues("spring.jmx.enabled=true").withConfiguration(
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL));
@ -152,4 +154,14 @@ public class JmxEndpointIntegrationTests {
} }
@Configuration(proxyBeanMethods = false)
public static class AuditEventRepositoryConfiguration {
@Bean
public InMemoryAuditEventRepository auditEventRepository() {
return new InMemoryAuditEventRepository();
}
}
} }

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
@ -83,7 +84,8 @@ public class WebMvcEndpointExposureIntegrationTests {
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL))
.withUserConfiguration(CustomMvcEndpoint.class, .withUserConfiguration(CustomMvcEndpoint.class,
CustomServletEndpoint.class, CustomServletEndpoint.class,
HttpTraceRepositoryConfiguration.class) HttpTraceRepositoryConfiguration.class,
AuditEventRepositoryConfiguration.class)
.withPropertyValues("server.port:0"); .withPropertyValues("server.port:0");
@Test @Test
@ -229,4 +231,14 @@ public class WebMvcEndpointExposureIntegrationTests {
} }
@Configuration(proxyBeanMethods = false)
public static class AuditEventRepositoryConfiguration {
@Bean
public InMemoryAuditEventRepository auditEventRepository() {
return new InMemoryAuditEventRepository();
}
}
} }

@ -2200,12 +2200,23 @@ maximum size for the "Metaspace", you could add an additional `tag=id:Metaspace`
Once Spring Security is in play, Spring Boot Actuator has a flexible audit framework that Once Spring Security is in play, Spring Boot Actuator has a flexible audit framework that
publishes events (by default, "`authentication success`", "`failure`" and publishes events (by default, "`authentication success`", "`failure`" and
"`access denied`" exceptions). This feature can be very useful for reporting and for "`access denied`" exceptions). This feature can be very useful for reporting and for
implementing a lock-out policy based on authentication failures. To customize published implementing a lock-out policy based on authentication failures.
security events, you can provide your own implementations of
Auditing can be enabled by providing a bean of type `AuditEventRepository` in your application's
configuration. For convenience, Spring Boot offers an `InMemoryAuditEventRepository`.
`InMemoryAuditEventRepository` has limited capabilities and we recommend using it only for development
environments. For production environments, consider creating your own alternative `AuditEventRepository`
implementation.
[[production-ready-auditing-custom]]
=== Custom Auditing
To customize published security events, you can provide your own implementations of
`AbstractAuthenticationAuditListener` and `AbstractAuthorizationAuditListener`. `AbstractAuthenticationAuditListener` and `AbstractAuthorizationAuditListener`.
You can also use the audit services for your own business events. To do so, either inject You can also use the audit services for your own business events. To do so, either inject
the existing `AuditEventRepository` into your own components and use that directly or the `AuditEventRepository` bean into your own components and use that directly or
publish an `AuditApplicationEvent` with the Spring `ApplicationEventPublisher` (by publish an `AuditApplicationEvent` with the Spring `ApplicationEventPublisher` (by
implementing `ApplicationEventPublisherAware`). implementing `ApplicationEventPublisherAware`).

Loading…
Cancel
Save