From 07d6eb6397d5362db6917c134f0190d76a0c5fb6 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Fri, 3 May 2019 13:42:04 -0700 Subject: [PATCH] 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 --- .../audit/AuditAutoConfiguration.java | 23 ++---- ...itional-spring-configuration-metadata.json | 6 ++ .../audit/AuditAutoConfigurationTests.java | 73 ++++++++++++------- ...tEventsEndpointAutoConfigurationTests.java | 26 ++++++- .../JmxEndpointIntegrationTests.java | 14 +++- ...ebMvcEndpointExposureIntegrationTests.java | 14 +++- .../asciidoc/production-ready-features.adoc | 17 ++++- 7 files changed, 124 insertions(+), 49 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java index 194bff875b..6d0c31490d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java @@ -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) 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(); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 08a7529ec5..4c34bb09c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfigurationTests.java index f7db26a4e6..ad69596079 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfigurationTests.java @@ -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) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfigurationTests.java index d66d8dd9bf..73e3aba7ef 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfigurationTests.java @@ -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(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java index 8c93f3c688..fa364d14ba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java @@ -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(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index b2a908b26a..57ddc96e71 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -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(); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 410566dbc3..9edd393952 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -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`).