From e9fe918d258ea058d331135630ca1068710d122d Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 8 Feb 2018 16:24:53 -0800 Subject: [PATCH] Add option to skip ssl for reactive CF actuators Fixes gh-10898 --- .../pom.xml | 10 ++--- ...CloudFoundryActuatorAutoConfiguration.java | 8 ++-- .../ReactiveCloudFoundrySecurityService.java | 16 ++++++- ...FoundryActuatorAutoConfigurationTests.java | 43 +++++++++++++++++++ ...ctiveCloudFoundrySecurityServiceTests.java | 2 +- 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index 2de8b044da..5723543a5c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -127,6 +127,11 @@ micrometer-registry-statsd true + + io.projectreactor.ipc + reactor-netty + true + io.searchbox jest @@ -379,11 +384,6 @@ json-path test - - io.projectreactor.ipc - reactor-netty - test - io.undertow undertow-core diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java index 0e4edfa56c..5ccda4dd9b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java @@ -111,9 +111,9 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { } private CloudFoundrySecurityInterceptor getSecurityInterceptor( - WebClient.Builder restTemplateBuilder, Environment environment) { + WebClient.Builder webClientBuilder, Environment environment) { ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService( - restTemplateBuilder, environment); + webClientBuilder, environment); ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator( cloudfoundrySecurityService); return new CloudFoundrySecurityInterceptor(tokenValidator, @@ -124,9 +124,11 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { private ReactiveCloudFoundrySecurityService getCloudFoundrySecurityService( WebClient.Builder webClientBuilder, Environment environment) { String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); + boolean skipSslValidation = environment.getProperty( + "management.cloudfoundry.skip-ssl-validation", Boolean.class, false); return (cloudControllerUrl == null ? null : new ReactiveCloudFoundrySecurityService(webClientBuilder, - cloudControllerUrl)); + cloudControllerUrl, skipSslValidation)); } private CorsConfiguration getCorsConfiguration() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java index 01562c13e3..10526a0f1e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; @@ -27,6 +29,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryA import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; @@ -50,13 +53,24 @@ class ReactiveCloudFoundrySecurityService { private Mono uaaUrl; ReactiveCloudFoundrySecurityService(WebClient.Builder webClientBuilder, - String cloudControllerUrl) { + String cloudControllerUrl, boolean skipSslValidation) { Assert.notNull(webClientBuilder, "Webclient must not be null"); Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null"); + if (skipSslValidation) { + webClientBuilder.clientConnector(buildTrustAllSslConnector()); + } this.webClient = webClientBuilder.build(); this.cloudControllerUrl = cloudControllerUrl; } + protected ReactorClientHttpConnector buildTrustAllSslConnector() { + return new ReactorClientHttpConnector( + (options) -> options.sslSupport((sslContextBuilder) -> { + sslContextBuilder.sslProvider(SslProvider.JDK) + .trustManager(InsecureTrustManagerFactory.INSTANCE); + })); + } + /** * Return a Mono of the access level that should be granted to the given token. * @param token the token diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index d1f64f573a..3486c712b9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -21,9 +21,14 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import javax.net.ssl.SSLException; + import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import reactor.ipc.netty.http.HttpResources; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; @@ -56,8 +61,10 @@ import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.mockito.Mockito.mock; /** @@ -69,6 +76,9 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { private AnnotationConfigReactiveWebApplicationContext context; + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Before public void setup() { this.context = new AnnotationConfigReactiveWebApplicationContext(); @@ -76,6 +86,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { @After public void close() { + HttpResources.reset(); if (this.context != null) { this.context.close(); } @@ -233,6 +244,38 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { .isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class); } + @Test + public void skipSslValidation() { + setupContextWithCloudEnabled(); + TestPropertyValues + .of("management.cloudfoundry.skip-ssl-validation:true") + .applyTo(this.context); + this.context.refresh(); + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(); + Object interceptor = ReflectionTestUtils.getField(handlerMapping, + "securityInterceptor"); + Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + "cloudFoundrySecurityService"); + WebClient webClient = (WebClient) ReflectionTestUtils + .getField(interceptorSecurityService, "webClient"); + webClient.get().uri("https://self-signed.badssl.com/").exchange().block(); + } + + @Test + public void sslValidationNotSkippedByDefault() { + setupContextWithCloudEnabled(); + this.context.refresh(); + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(); + Object interceptor = ReflectionTestUtils.getField(handlerMapping, + "securityInterceptor"); + Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + "cloudFoundrySecurityService"); + WebClient webClient = (WebClient) ReflectionTestUtils + .getField(interceptorSecurityService, "webClient"); + this.thrown.expectCause(instanceOf(SSLException.class)); + webClient.get().uri("https://self-signed.badssl.com/").exchange().block(); + } + private void setupContextWithCloudEnabled() { TestPropertyValues .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java index 99dbe13349..f1f5614eee 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java @@ -59,7 +59,7 @@ public class ReactiveCloudFoundrySecurityServiceTests { this.server = new MockWebServer(); this.builder = WebClient.builder().baseUrl(this.server.url("/").toString()); this.securityService = new ReactiveCloudFoundrySecurityService(this.builder, - CLOUD_CONTROLLER); + CLOUD_CONTROLLER, false); } @After