From 082ccbd2f280c83b9744377e57eb655f2a7435c2 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 1 Jun 2021 16:21:06 +0200 Subject: [PATCH] Spring Security --- docs/src/main/asciidoc/_configprops.adoc | 1 + docs/src/main/asciidoc/_spans.adoc | 6 ++ docs/src/main/asciidoc/integrations.adoc | 7 ++ .../instrument/web/LazyTracingFilter.java | 67 ++++++++++++++ ...TraceServletSecurityBeanPostProcessor.java | 43 +++++++++ .../web/TraceWebFluxConfiguration.java | 14 +++ ...TraceWebFluxSecurityBeanPostProcessor.java | 36 ++++++++ .../web/TraceWebServletConfiguration.java | 58 +++--------- ...itional-spring-configuration-metadata.json | 6 ++ .../TraceQuartzAutoConfigurationTest.java | 10 +- ...raceWebFluxSecurityConfigurationTests.java | 91 +++++++++++++++++++ ...eWebServletSecurityConfigurationTests.java | 91 +++++++++++++++++++ .../BraveWebClientAutoConfigurationTests.java | 10 +- .../instrument/web/client/GH846Tests.java | 10 +- .../client/TraceWebClientDisabledTests.java | 10 +- .../zipkin2/ZipkinSamplerTests.java | 10 +- .../src/test/resources/application.yml | 2 +- .../sleuth/instrument/web/SleuthWebSpan.java | 72 ++++++++++++++- .../web/TracingSecurityServletFilter.java | 91 +++++++++++++++++++ .../web/TracingSecurityTagSetter.java | 57 ++++++++++++ .../web/TracingSecurityWebFilter.java | 56 ++++++++++++ .../TracingSecurityServletFilterTests.java | 73 +++++++++++++++ .../TracingSecurityWebFluxFilterTests.java | 77 ++++++++++++++++ 23 files changed, 831 insertions(+), 67 deletions(-) create mode 100644 spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/LazyTracingFilter.java create mode 100644 spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceServletSecurityBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityConfigurationTests.java create mode 100644 spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletSecurityConfigurationTests.java create mode 100644 spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilter.java create mode 100644 spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityTagSetter.java create mode 100644 spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFilter.java create mode 100644 spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilterTests.java create mode 100644 spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFluxFilterTests.java diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index bc9c8b87b..d8d32f515 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -75,6 +75,7 @@ |spring.sleuth.sampler.refresh.enabled | `true` | Enable refresh scope for sampler. |spring.sleuth.scheduled.enabled | `true` | Enable tracing for {@link org.springframework.scheduling.annotation.Scheduled}. |spring.sleuth.scheduled.skip-pattern | | Pattern for the fully qualified name of a class that should be skipped. +|spring.sleuth.security.enabled | `true` | Enable Spring Security instrumentation. |spring.sleuth.span-filter.additional-span-name-patterns-to-ignore | | Additional list of span names to ignore. Will be appended to {@link #spanNamePatternsToSkip}. |spring.sleuth.span-filter.enabled | `false` | Will turn on the default Sleuth handler mechanism. Might ignore exporting of certain spans; |spring.sleuth.span-filter.span-name-patterns-to-skip | `^catalogWatchTaskScheduler$` | List of span names to ignore. They will not be sent to external systems. diff --git a/docs/src/main/asciidoc/_spans.adoc b/docs/src/main/asciidoc/_spans.adoc index 4172c04e5..988df6c26 100644 --- a/docs/src/main/asciidoc/_spans.adoc +++ b/docs/src/main/asciidoc/_spans.adoc @@ -558,5 +558,11 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in |http.status_code|Response status code. |mvc.controller.class|Name of the class that is processing the request. |mvc.controller.method|Name of the method that is processing the request. +|security.authentication.authenticated|Whether user is authenticated. +|security.authentication.authorities|Authorities assigned to the user. +|security.principal.account-non-expired|Whether principal's account is non expired. +|security.principal.authorities|Principal's authorities. +|security.principal.credentials-non-expired|Whether principal's credentials are non expired. +|security.principal.enabled|Whether principal is enabled. |=== diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc index e57563d3a..b4f139766 100644 --- a/docs/src/main/asciidoc/integrations.adoc +++ b/docs/src/main/asciidoc/integrations.adoc @@ -651,6 +651,13 @@ This feature is available for all tracer implementations. We're adding an instrumented Tomcat's `Valve` that originates the span. In order to disable this instrumentation set `spring.sleuth.web.tomcat.enabled` to `false`. +[[sleuth-security-integration]] +== Spring Security + +This feature is available for all tracer implementations. + +We're injecting a custom filter into the Spring Security filter chains and we're tagging the existing span with security related tags. +In order to disable this instrumentation set `spring.sleuth.security.enabled` to `false`. [[sleuth-jdbc-integration]] == Spring JDBC diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/LazyTracingFilter.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/LazyTracingFilter.java new file mode 100644 index 000000000..79ad6ce8b --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/LazyTracingFilter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.CurrentTraceContext; +import org.springframework.cloud.sleuth.http.HttpServerHandler; +import org.springframework.cloud.sleuth.instrument.web.servlet.TracingFilter; + +final class LazyTracingFilter implements Filter { + + private final BeanFactory beanFactory; + + private Filter tracingFilter; + + LazyTracingFilter(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + tracingFilter().init(filterConfig); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + tracingFilter().doFilter(request, response, chain); + } + + @Override + public void destroy() { + tracingFilter().destroy(); + } + + private Filter tracingFilter() { + if (this.tracingFilter == null) { + this.tracingFilter = TracingFilter.create(this.beanFactory.getBean(CurrentTraceContext.class), + this.beanFactory.getBean(HttpServerHandler.class)); + } + return this.tracingFilter; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceServletSecurityBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceServletSecurityBeanPostProcessor.java new file mode 100644 index 000000000..746006713 --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceServletSecurityBeanPostProcessor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.instrument.web.TracingSecurityServletFilter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; + +class TraceServletSecurityBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + TraceServletSecurityBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof HttpSecurity) { + HttpSecurity httpSecurity = (HttpSecurity) bean; + httpSecurity.addFilterAfter(TracingSecurityServletFilter.lazy(this.beanFactory), SwitchUserFilter.class); + } + return bean; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxConfiguration.java index c38d8275c..6fa5fdb38 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.autoconfig.instrument.web; import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.cloud.sleuth.CurrentTraceContext; @@ -25,6 +26,7 @@ import org.springframework.cloud.sleuth.http.HttpServerHandler; import org.springframework.cloud.sleuth.instrument.web.TraceWebFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.web.server.ServerHttpSecurity; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -52,4 +54,16 @@ class TraceWebFluxConfiguration { return new TraceHandlerFunctionAdapterBeanPostProcessor(beanFactory); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ServerHttpSecurity.class) + @ConditionalOnProperty(value = "spring.sleuth.security.enabled", matchIfMissing = true) + protected static class TraceSecurityWebFluxAutoConfiguration { + + @Bean + static TraceWebFluxSecurityBeanPostProcessor traceWebFluxSecurityBeanPostProcessor() { + return new TraceWebFluxSecurityBeanPostProcessor(); + } + + } + } diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityBeanPostProcessor.java new file mode 100644 index 000000000..03e87776c --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityBeanPostProcessor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.instrument.web.TracingSecurityWebFilter; +import org.springframework.security.config.web.server.SecurityWebFiltersOrder; +import org.springframework.security.config.web.server.ServerHttpSecurity; + +class TraceWebFluxSecurityBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ServerHttpSecurity) { + ServerHttpSecurity httpSecurity = (ServerHttpSecurity) bean; + httpSecurity.addFilterBefore(new TracingSecurityWebFilter(), SecurityWebFiltersOrder.LAST); + } + return bean; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java index abb21771a..822e610b4 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java @@ -16,15 +16,7 @@ package org.springframework.cloud.sleuth.autoconfig.instrument.web; -import java.io.IOException; - import javax.servlet.DispatcherType; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import org.apache.catalina.Valve; @@ -41,13 +33,13 @@ import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.http.HttpServerHandler; import org.springframework.cloud.sleuth.instrument.web.TraceWebAspect; import org.springframework.cloud.sleuth.instrument.web.mvc.SpanCustomizingAsyncHandlerInterceptor; -import org.springframework.cloud.sleuth.instrument.web.servlet.TracingFilter; import org.springframework.cloud.sleuth.instrument.web.tomcat.TraceValve; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -92,6 +84,18 @@ class TraceWebServletConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(HttpSecurity.class) + @ConditionalOnProperty(value = "spring.sleuth.security.enabled", matchIfMissing = true) + protected static class TraceSecurityWebMvcAutoConfiguration { + + @Bean + static TraceServletSecurityBeanPostProcessor traceServletSecurityBeanPostProcessor(BeanFactory beanFactory) { + return new TraceServletSecurityBeanPostProcessor(beanFactory); + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Valve.class, ConfigurableTomcatWebServerFactory.class }) @ConditionalOnProperty(value = "spring.sleuth.web.tomcat.enabled", matchIfMissing = true) @@ -108,40 +112,4 @@ class TraceWebServletConfiguration { } - static final class LazyTracingFilter implements Filter { - - private final BeanFactory beanFactory; - - private Filter tracingFilter; - - LazyTracingFilter(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - tracingFilter().init(filterConfig); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - tracingFilter().doFilter(request, response, chain); - } - - @Override - public void destroy() { - tracingFilter().destroy(); - } - - private Filter tracingFilter() { - if (this.tracingFilter == null) { - this.tracingFilter = TracingFilter.create(this.beanFactory.getBean(CurrentTraceContext.class), - this.beanFactory.getBean(HttpServerHandler.class)); - } - return this.tracingFilter; - } - - } - } diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 95cc9ca24..42461c14a 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -190,6 +190,12 @@ "type": "java.lang.Boolean", "description": "Enable Spring Vault instrumentation.", "defaultValue": true + }, + { + "name": "spring.sleuth.security.enabled", + "type": "java.lang.Boolean", + "description": "Enable Spring Security instrumentation.", + "defaultValue": true } ] } diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java index 165e728c6..bd70f0af3 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java @@ -115,10 +115,12 @@ public class TraceQuartzAutoConfigurationTest { } @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, - MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, - R2dbcDataAutoConfiguration.class }) + @EnableAutoConfiguration( + exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, + MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, + R2dbcDataAutoConfiguration.class }, + excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration") public static class EnableAutoConfig { } diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityConfigurationTests.java new file mode 100644 index 000000000..5b37c0d1d --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebFluxSecurityConfigurationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import org.junit.Test; +import org.mockito.BDDMockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.cloud.sleuth.CurrentTraceContext; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.http.HttpServerHandler; +import org.springframework.cloud.sleuth.internal.DefaultSpanNamer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.web.server.ServerHttpSecurity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Michał Ziemba + */ +public class TraceWebFluxSecurityConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TraceWebAutoConfiguration.class)) + .withUserConfiguration(TestConfig.class); + + @Test + public void shouldNotCreateTracedWebBeansWhenSecurityMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(ServerHttpSecurity.class)).run((context) -> { + assertThat(context).doesNotHaveBean(TraceWebFluxSecurityBeanPostProcessor.class); + }); + } + + @Test + public void shouldNotCreateTracedWebBeansWhenSecurityTraceDisabled() { + this.contextRunner.withPropertyValues("spring.sleuth.security.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(TraceWebFluxSecurityBeanPostProcessor.class); + }); + } + + @Test + public void shouldCreateTraceSecurityBeanPostProcessorWhenServletClassPresent() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(TraceWebFluxSecurityBeanPostProcessor.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + Tracer tracer() { + return BDDMockito.mock(Tracer.class); + } + + @Bean + CurrentTraceContext currentTraceContext() { + return BDDMockito.mock(CurrentTraceContext.class); + } + + @Bean + SpanNamer spanNamer() { + return new DefaultSpanNamer(); + } + + @Bean + HttpServerHandler httpServerHandler() { + return BDDMockito.mock(HttpServerHandler.class); + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletSecurityConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletSecurityConfigurationTests.java new file mode 100644 index 000000000..fff8f02ca --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletSecurityConfigurationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import org.junit.Test; +import org.mockito.BDDMockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.sleuth.CurrentTraceContext; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.http.HttpServerHandler; +import org.springframework.cloud.sleuth.internal.DefaultSpanNamer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Michał Ziemba + */ +public class TraceWebServletSecurityConfigurationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TraceWebAutoConfiguration.class)) + .withUserConfiguration(TestConfig.class); + + @Test + public void shouldNotCreateTracedWebBeansWhenSecurityMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(HttpSecurity.class)).run((context) -> { + assertThat(context).doesNotHaveBean(TraceServletSecurityBeanPostProcessor.class); + }); + } + + @Test + public void shouldNotCreateTracedWebBeansWhenSecurityTraceDisabled() { + this.contextRunner.withPropertyValues("spring.sleuth.security.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(TraceServletSecurityBeanPostProcessor.class); + }); + } + + @Test + public void shouldCreateTraceSecurityBeanPostProcessorWhenServletClassPresent() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(TraceServletSecurityBeanPostProcessor.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + Tracer tracer() { + return BDDMockito.mock(Tracer.class); + } + + @Bean + CurrentTraceContext currentTraceContext() { + return BDDMockito.mock(CurrentTraceContext.class); + } + + @Bean + SpanNamer spanNamer() { + return new DefaultSpanNamer(); + } + + @Bean + HttpServerHandler httpServerHandler() { + return BDDMockito.mock(HttpServerHandler.class); + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java index 8306face3..e9aaf2370 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java @@ -120,10 +120,12 @@ public class BraveWebClientAutoConfigurationTests { } @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, - MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, - R2dbcDataAutoConfiguration.class }) + @EnableAutoConfiguration( + exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, + MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, + R2dbcDataAutoConfiguration.class }, + excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration") static class Config { // custom builder diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java index 0fd557aad..0c194bdae 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java @@ -54,10 +54,12 @@ public class GH846Tests { } @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, - MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, - R2dbcDataAutoConfiguration.class }) + @EnableAutoConfiguration( + exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, + MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, + R2dbcDataAutoConfiguration.class }, + excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration") static class App { @Bean diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java index 43e9d29d9..bb93c2a96 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java @@ -43,10 +43,12 @@ public class TraceWebClientDisabledTests { } @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, - MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, - R2dbcDataAutoConfiguration.class }) + @EnableAutoConfiguration( + exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, + MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, + R2dbcDataAutoConfiguration.class }, + excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration") public static class Config { } diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java index f3497539d..29efb690d 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java @@ -53,10 +53,12 @@ public class ZipkinSamplerTests { } @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, - MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, - R2dbcDataAutoConfiguration.class }) + @EnableAutoConfiguration( + exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, + MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, + R2dbcDataAutoConfiguration.class }, + excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration") static class TestConfig { } diff --git a/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml b/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml index 1e05c6764..c02afb533 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml +++ b/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml @@ -1,3 +1,3 @@ logging.level.org.springframework.cloud: DEBUG -spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration +spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebSpan.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebSpan.java index a46a8f9e7..29d04a15b 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebSpan.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebSpan.java @@ -33,7 +33,7 @@ enum SleuthWebSpan implements DocumentedSpan { @Override public TagKey[] getTagKeys() { - return Tags.values(); + return TagKey.merge(Tags.values(), SecurityTags.values()); } }; @@ -78,4 +78,74 @@ enum SleuthWebSpan implements DocumentedSpan { } + /** + * Tags related to security. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ + enum SecurityTags implements TagKey { + + /** + * Authorities assigned to the user. + */ + AUTHORITIES { + @Override + public String getKey() { + return "security.authentication.authorities"; + } + }, + + /** + * Whether user is authenticated. + */ + AUTHENTICATED { + @Override + public String getKey() { + return "security.authentication.authenticated"; + } + }, + + /** + * Whether principal is enabled. + */ + PRINCIPAL_ENABLED { + @Override + public String getKey() { + return "security.principal.enabled"; + } + }, + + /** + * Principal's authorities. + */ + PRINCIPAL_AUTHORITIES { + @Override + public String getKey() { + return "security.principal.authorities"; + } + }, + + /** + * Whether principal's account is non expired. + */ + PRINCIPAL_ACCOUNT_NON_EXPIRED { + @Override + public String getKey() { + return "security.principal.account-non-expired"; + } + }, + + /** + * Whether principal's credentials are non expired. + */ + PRINCIPAL_CREDENTIALS_NON_EXPIRED { + @Override + public String getKey() { + return "security.principal.credentials-non-expired"; + } + } + + } + } diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilter.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilter.java new file mode 100644 index 000000000..0fb14768d --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +/** + * A filter that adds security related tags. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TracingSecurityServletFilter extends GenericFilterBean { + + private final Tracer tracer; + + public TracingSecurityServletFilter(Tracer tracer) { + this.tracer = tracer; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws ServletException, IOException { + SecurityContext securityContext = getContext(); + if (securityContext != null) { + Span span = this.tracer.currentSpan(); + if (span != null) { + TracingSecurityTagSetter.setSecurityTags(span, securityContext.getAuthentication()); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } + + SecurityContext getContext() { + return SecurityContextHolder.getContext(); + } + + /** + * Lazy version of the {@link TracingSecurityServletFilter}. + * @param beanFactory bean factory + * @return lazy version of the filter + */ + public static Filter lazy(BeanFactory beanFactory) { + return new Filter() { + + private TracingSecurityServletFilter tracingSecurityServletFilter; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + tracingSecurityFilter().doFilter(servletRequest, servletResponse, filterChain); + } + + private TracingSecurityServletFilter tracingSecurityFilter() { + if (this.tracingSecurityServletFilter == null) { + this.tracingSecurityServletFilter = new TracingSecurityServletFilter( + beanFactory.getBean(Tracer.class)); + } + return this.tracingSecurityServletFilter; + } + }; + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityTagSetter.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityTagSetter.java new file mode 100644 index 000000000..39793a6f6 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityTagSetter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.docs.AssertingSpan; +import org.springframework.cloud.sleuth.instrument.web.SleuthWebSpan.SecurityTags; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.util.StringUtils; + +final class TracingSecurityTagSetter { + + private static final Log log = LogFactory.getLog(TracingSecurityTagSetter.class); + + private TracingSecurityTagSetter() { + throw new IllegalStateException("Can't instantiate a utility class"); + } + + static void setSecurityTags(Span span, Authentication authentication) { + if (log.isDebugEnabled()) { + log.debug("Will set security tags on span [" + span + "]"); + } + AssertingSpan assertingSpan = AssertingSpan.of(SleuthWebSpan.WEB_FILTER_SPAN, span); + assertingSpan.tag(SecurityTags.AUTHORITIES, + StringUtils.collectionToCommaDelimitedString(authentication.getAuthorities())); + assertingSpan.tag(SecurityTags.AUTHENTICATED, String.valueOf(authentication.isAuthenticated())); + Object principal = authentication.getPrincipal(); + if (principal instanceof User) { + User user = (User) principal; + assertingSpan.tag(SecurityTags.PRINCIPAL_ENABLED, String.valueOf(user.isEnabled())); + assertingSpan.tag(SecurityTags.PRINCIPAL_AUTHORITIES, + StringUtils.collectionToCommaDelimitedString(user.getAuthorities())); + assertingSpan.tag(SecurityTags.PRINCIPAL_ACCOUNT_NON_EXPIRED, String.valueOf(user.isAccountNonExpired())); + assertingSpan.tag(SecurityTags.PRINCIPAL_CREDENTIALS_NON_EXPIRED, + String.valueOf(user.isCredentialsNonExpired())); + } + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFilter.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFilter.java new file mode 100644 index 000000000..1bda6292c --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFilter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; + +/** + * A filter that adds security related tags. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TracingSecurityWebFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + // @formatter:off + return getContext() + .filter((c) -> c.getAuthentication() != null) + .map(SecurityContext::getAuthentication) + .doOnNext(authentication -> { + Object attribute = exchange.getAttribute(TraceWebFilter.TRACE_REQUEST_ATTR); + if (attribute instanceof Span) { + TracingSecurityTagSetter.setSecurityTags((Span) attribute, authentication); + } + }) + .then(chain.filter(exchange)); + // @formatter:on + } + + Mono getContext() { + return ReactiveSecurityContextHolder.getContext(); + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilterTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilterTests.java new file mode 100644 index 000000000..0bbdaae4e --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityServletFilterTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.io.IOException; +import java.util.Collections; + +import javax.servlet.ServletException; + +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.sleuth.tracer.SimpleSpan; +import org.springframework.cloud.sleuth.tracer.SimpleTracer; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.User; + +import static org.assertj.core.api.BDDAssertions.then; + +class TracingSecurityServletFilterTests { + + SimpleTracer tracer = new SimpleTracer(); + + TracingSecurityServletFilter filter = new TracingSecurityServletFilter(this.tracer) { + @Override + SecurityContext getContext() { + return new SecurityContextImpl(new TestingAuthenticationToken( + new User("foo", "bar", Collections.singletonList(new SimpleGrantedAuthority("my-role"))), null, + "my-authority")); + } + }; + + @Test + void should_tag_current_span_with_security_info() throws ServletException, IOException { + SimpleSpan simpleSpan = this.tracer.nextSpan().start(); + + this.filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain()); + + then(simpleSpan.tags).containsEntry("security.authentication.authorities", "my-authority") + .containsEntry("security.authentication.authenticated", "true") + .containsEntry("security.principal.enabled", "true") + .containsEntry("security.principal.authorities", "my-role") + .containsEntry("security.principal.account-non-expired", "true") + .containsEntry("security.principal.credentials-non-expired", "true"); + } + + @Test + void should_do_nothing_when_there_is_no_current_span() throws ServletException, IOException { + this.filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain()); + + then(this.tracer.spans).isEmpty(); + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFluxFilterTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFluxFilterTests.java new file mode 100644 index 000000000..f9cac6a98 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/TracingSecurityWebFluxFilterTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.time.Duration; +import java.util.Collections; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.sleuth.tracer.SimpleSpan; +import org.springframework.cloud.sleuth.tracer.SimpleTracer; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.User; + +import static org.assertj.core.api.BDDAssertions.then; + +class TracingSecurityWebFluxFilterTests { + + SimpleTracer tracer = new SimpleTracer(); + + TracingSecurityWebFilter filter = new TracingSecurityWebFilter() { + @Override + Mono getContext() { + return Mono.just(new SecurityContextImpl(new TestingAuthenticationToken( + new User("foo", "bar", Collections.singletonList(new SimpleGrantedAuthority("my-role"))), null, + "my-authority"))); + } + }; + + @Test + void should_tag_current_span_with_security_info() { + MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar").build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); + SimpleSpan simpleSpan = this.tracer.nextSpan().start(); + exchange.getAttributes().put(TraceWebFilter.TRACE_REQUEST_ATTR, simpleSpan); + + this.filter.filter(exchange, exchange1 -> Mono.empty()).block(Duration.ofMillis(10)); + + then(simpleSpan.tags).containsEntry("security.authentication.authorities", "my-authority") + .containsEntry("security.authentication.authenticated", "true") + .containsEntry("security.principal.enabled", "true") + .containsEntry("security.principal.authorities", "my-role") + .containsEntry("security.principal.account-non-expired", "true") + .containsEntry("security.principal.credentials-non-expired", "true"); + } + + @Test + void should_do_nothing_when_there_is_no_current_span() { + MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar").build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); + + this.filter.filter(exchange, exchange1 -> Mono.empty()).block(Duration.ofMillis(10)); + + then(this.tracer.spans).isEmpty(); + } + +}