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;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.audit.AuditEvent;
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.AuditListener;
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.AuthorizationAuditListener;
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.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -40,13 +40,15 @@ import org.springframework.context.annotation.Configuration;
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnProperty(prefix = "management.auditevents", name = "enabled",
matchIfMissing = true)
public class AuditAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AbstractAuditListener.class)
public AuditListener auditListener(
ObjectProvider<AuditEventRepository> auditEventRepository) throws Exception {
return new AuditListener(auditEventRepository.getIfAvailable());
public AuditListener auditListener(AuditEventRepository auditEventRepository) {
return new AuditListener(auditEventRepository);
}
@Bean
@ -65,15 +67,4 @@ public class AuditAutoConfiguration {
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.",
"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",
"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.InMemoryAuditEventRepository;
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.AbstractAuthorizationAuditListener;
import org.springframework.boot.actuate.security.AuthenticationAuditListener;
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.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.event.AbstractAuthorizationEvent;
@ -40,54 +42,71 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
* @author Vedran Pavic
* @author Madhura Bhave
*/
public class AuditAutoConfigurationTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AuditAutoConfiguration.class));
@Test
public void defaultConfiguration() {
registerAndRefresh(AuditAutoConfiguration.class);
assertThat(this.context.getBean(AuditEventRepository.class)).isNotNull();
assertThat(this.context.getBean(AuthenticationAuditListener.class)).isNotNull();
assertThat(this.context.getBean(AuthorizationAuditListener.class)).isNotNull();
public void autoConfigurationIsDisabledByDefault() {
this.contextRunner.run((context) -> assertThat(context)
.doesNotHaveBean(AuditAutoConfiguration.class));
}
@Test
public void ownAuditEventRepository() {
registerAndRefresh(CustomAuditEventRepositoryConfiguration.class,
AuditAutoConfiguration.class);
assertThat(this.context.getBean(AuditEventRepository.class))
.isInstanceOf(TestAuditEventRepository.class);
public void autoConfigurationIsEnabledWhenAuditEventRepositoryBeanPresent() {
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.run((context) -> {
assertThat(context.getBean(AuditEventRepository.class)).isNotNull();
assertThat(context.getBean(AuthenticationAuditListener.class))
.isNotNull();
assertThat(context.getBean(AuthorizationAuditListener.class))
.isNotNull();
});
}
@Test
public void ownAuthenticationAuditListener() {
registerAndRefresh(CustomAuthenticationAuditListenerConfiguration.class,
AuditAutoConfiguration.class);
assertThat(this.context.getBean(AbstractAuthenticationAuditListener.class))
.isInstanceOf(TestAuthenticationAuditListener.class);
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withUserConfiguration(
CustomAuthenticationAuditListenerConfiguration.class)
.run((context) -> assertThat(
context.getBean(AbstractAuthenticationAuditListener.class))
.isInstanceOf(TestAuthenticationAuditListener.class));
}
@Test
public void ownAuthorizationAuditListener() {
registerAndRefresh(CustomAuthorizationAuditListenerConfiguration.class,
AuditAutoConfiguration.class);
assertThat(this.context.getBean(AbstractAuthorizationAuditListener.class))
.isInstanceOf(TestAuthorizationAuditListener.class);
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withUserConfiguration(
CustomAuthorizationAuditListenerConfiguration.class)
.run((context) -> assertThat(
context.getBean(AbstractAuthorizationAuditListener.class))
.isInstanceOf(TestAuthorizationAuditListener.class));
}
@Test
public void ownAuditListener() {
registerAndRefresh(CustomAuditListenerConfiguration.class,
AuditAutoConfiguration.class);
assertThat(this.context.getBean(AbstractAuditListener.class))
.isInstanceOf(TestAuditListener.class);
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withUserConfiguration(CustomAuditListenerConfiguration.class)
.run((context) -> assertThat(context.getBean(AbstractAuditListener.class))
.isInstanceOf(TestAuditListener.class));
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@Test
public void backsOffWhenDisabled() {
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)

@ -19,8 +19,11 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.autoconfigure.AutoConfigurations;
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;
@ -38,14 +41,24 @@ public class AuditEventsEndpointAutoConfigurationTests {
AuditEventsEndpointAutoConfiguration.class));
@Test
public void runShouldHaveEndpointBean() {
public void runWhenRepositoryBeanAvailableShouldHaveEndpointBean() {
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withPropertyValues(
"management.endpoints.web.exposure.include=auditevents")
.run((context) -> assertThat(context)
.hasSingleBean(AuditEventsEndpoint.class));
}
@Test
public void endpointBacksOffWhenRepositoryNotAvailable() {
this.contextRunner
.withPropertyValues(
"management.endpoints.web.exposure.include=auditevents")
.run((context) -> assertThat(context)
.doesNotHaveBean(AuditEventsEndpoint.class));
}
@Test
public void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner.run((context) -> assertThat(context)
@ -55,10 +68,21 @@ public class AuditEventsEndpointAutoConfigurationTests {
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpoint() {
this.contextRunner
.withUserConfiguration(CustomAuditEventRepositoryConfiguration.class)
.withPropertyValues("management.endpoint.auditevents.enabled:false")
.withPropertyValues("management.endpoints.web.exposure.include=*")
.run((context) -> assertThat(context)
.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.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
@ -53,7 +54,8 @@ public class JmxEndpointIntegrationTests {
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class,
HttpTraceAutoConfiguration.class))
.withUserConfiguration(HttpTraceRepositoryConfiguration.class)
.withUserConfiguration(HttpTraceRepositoryConfiguration.class,
AuditEventRepositoryConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true").withConfiguration(
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.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
@ -83,7 +84,8 @@ public class WebMvcEndpointExposureIntegrationTests {
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL))
.withUserConfiguration(CustomMvcEndpoint.class,
CustomServletEndpoint.class,
HttpTraceRepositoryConfiguration.class)
HttpTraceRepositoryConfiguration.class,
AuditEventRepositoryConfiguration.class)
.withPropertyValues("server.port:0");
@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
publishes events (by default, "`authentication success`", "`failure`" and
"`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
security events, you can provide your own implementations of
implementing a lock-out policy based on authentication failures.
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`.
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
implementing `ApplicationEventPublisherAware`).

Loading…
Cancel
Save