Add option to skip ssl for reactive CF actuators

Fixes gh-10898
pull/11967/merge
Madhura Bhave 7 years ago
parent fee9dacab1
commit e9fe918d25

@ -127,6 +127,11 @@
<artifactId>micrometer-registry-statsd</artifactId> <artifactId>micrometer-registry-statsd</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>io.searchbox</groupId> <groupId>io.searchbox</groupId>
<artifactId>jest</artifactId> <artifactId>jest</artifactId>
@ -379,11 +384,6 @@
<artifactId>json-path</artifactId> <artifactId>json-path</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>io.undertow</groupId> <groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId> <artifactId>undertow-core</artifactId>

@ -111,9 +111,9 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
} }
private CloudFoundrySecurityInterceptor getSecurityInterceptor( private CloudFoundrySecurityInterceptor getSecurityInterceptor(
WebClient.Builder restTemplateBuilder, Environment environment) { WebClient.Builder webClientBuilder, Environment environment) {
ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService( ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(
restTemplateBuilder, environment); webClientBuilder, environment);
ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator( ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator(
cloudfoundrySecurityService); cloudfoundrySecurityService);
return new CloudFoundrySecurityInterceptor(tokenValidator, return new CloudFoundrySecurityInterceptor(tokenValidator,
@ -124,9 +124,11 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
private ReactiveCloudFoundrySecurityService getCloudFoundrySecurityService( private ReactiveCloudFoundrySecurityService getCloudFoundrySecurityService(
WebClient.Builder webClientBuilder, Environment environment) { WebClient.Builder webClientBuilder, Environment environment) {
String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); String cloudControllerUrl = environment.getProperty("vcap.application.cf_api");
boolean skipSslValidation = environment.getProperty(
"management.cloudfoundry.skip-ssl-validation", Boolean.class, false);
return (cloudControllerUrl == null ? null return (cloudControllerUrl == null ? null
: new ReactiveCloudFoundrySecurityService(webClientBuilder, : new ReactiveCloudFoundrySecurityService(webClientBuilder,
cloudControllerUrl)); cloudControllerUrl, skipSslValidation));
} }
private CorsConfiguration getCorsConfiguration() { private CorsConfiguration getCorsConfiguration() {

@ -20,6 +20,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; 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.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
@ -50,13 +53,24 @@ class ReactiveCloudFoundrySecurityService {
private Mono<String> uaaUrl; private Mono<String> uaaUrl;
ReactiveCloudFoundrySecurityService(WebClient.Builder webClientBuilder, ReactiveCloudFoundrySecurityService(WebClient.Builder webClientBuilder,
String cloudControllerUrl) { String cloudControllerUrl, boolean skipSslValidation) {
Assert.notNull(webClientBuilder, "Webclient must not be null"); Assert.notNull(webClientBuilder, "Webclient must not be null");
Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null"); Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null");
if (skipSslValidation) {
webClientBuilder.clientConnector(buildTrustAllSslConnector());
}
this.webClient = webClientBuilder.build(); this.webClient = webClientBuilder.build();
this.cloudControllerUrl = cloudControllerUrl; 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. * Return a Mono of the access level that should be granted to the given token.
* @param token the token * @param token the token

@ -21,9 +21,14 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.net.ssl.SSLException;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; 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.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; 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.util.ReflectionTestUtils;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.cors.CorsConfiguration; 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.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -69,6 +76,9 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
private AnnotationConfigReactiveWebApplicationContext context; private AnnotationConfigReactiveWebApplicationContext context;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before @Before
public void setup() { public void setup() {
this.context = new AnnotationConfigReactiveWebApplicationContext(); this.context = new AnnotationConfigReactiveWebApplicationContext();
@ -76,6 +86,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
@After @After
public void close() { public void close() {
HttpResources.reset();
if (this.context != null) { if (this.context != null) {
this.context.close(); this.context.close();
} }
@ -233,6 +244,38 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
.isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class); .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() { private void setupContextWithCloudEnabled() {
TestPropertyValues TestPropertyValues
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",

@ -59,7 +59,7 @@ public class ReactiveCloudFoundrySecurityServiceTests {
this.server = new MockWebServer(); this.server = new MockWebServer();
this.builder = WebClient.builder().baseUrl(this.server.url("/").toString()); this.builder = WebClient.builder().baseUrl(this.server.url("/").toString());
this.securityService = new ReactiveCloudFoundrySecurityService(this.builder, this.securityService = new ReactiveCloudFoundrySecurityService(this.builder,
CLOUD_CONTROLLER); CLOUD_CONTROLLER, false);
} }
@After @After

Loading…
Cancel
Save