From 09b3461394bc94eeed3efc75e540eb9a8b6abf68 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 14 Mar 2025 14:45:14 +0100 Subject: [PATCH] Remove use of self-signed.badssl.com Closes gh-43708 --- ...FoundryActuatorAutoConfigurationTests.java | 116 ++++++++++-------- .../cloudfoundry/reactive/test.jks | Bin 0 -> 2050 bytes 2 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/test.jks 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 856bb2af972..2a0e7adceea 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 @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; +import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.Collection; @@ -24,6 +25,8 @@ import java.util.Map; import javax.net.ssl.SSLException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import reactor.netty.http.HttpResources; @@ -52,13 +55,19 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.jks.JksSslStoreBundle; +import org.springframework.boot.ssl.jks.JksSslStoreDetails; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.resources.WithPackageResources; import org.springframework.boot.testsupport.classpath.resources.WithResource; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; @@ -78,6 +87,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link ReactiveCloudFoundryActuatorAutoConfiguration}. * * @author Madhura Bhave + * @author Moritz Halbritter */ class ReactiveCloudFoundryActuatorAutoConfigurationTests { @@ -300,53 +310,63 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { } @Test - void skipSslValidation() { - this.contextRunner.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) - .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", - "vcap.application.cf_api:https://my-cloud-controller.com", - "management.cloudfoundry.skip-ssl-validation:true") - .run((context) -> { - CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(context); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - WebClient webClient = (WebClient) ReflectionTestUtils.getField(interceptorSecurityService, "webClient"); - doesNotFailWithSslException(() -> webClient.get() - .uri("https://self-signed.badssl.com/") - .retrieve() - .toBodilessEntity() - .block(Duration.ofSeconds(30))); - }); - } - - private static void doesNotFailWithSslException(Runnable action) { - try { - action.run(); - } - catch (RuntimeException ex) { - assertThat(findCause(ex, SSLException.class)).isNull(); + @WithPackageResources("test.jks") + void skipSslValidation() throws IOException { + JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("JKS", null, "classpath:test.jks", "secret"); + SslBundle sslBundle = SslBundle.of(new JksSslStoreBundle(keyStoreDetails, keyStoreDetails)); + try (MockWebServer server = new MockWebServer()) { + server.useHttps(sslBundle.createSslContext().getSocketFactory(), false); + server.enqueue(new MockResponse().setResponseCode(204)); + server.start(); + this.contextRunner.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) + .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", + "vcap.application.cf_api:https://my-cloud-controller.com", + "management.cloudfoundry.skip-ssl-validation:true") + .run((context) -> { + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(context); + Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor"); + Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + "cloudFoundrySecurityService"); + WebClient webClient = (WebClient) ReflectionTestUtils.getField(interceptorSecurityService, + "webClient"); + ResponseEntity response = webClient.get() + .uri(server.url("/").uri()) + .retrieve() + .toBodilessEntity() + .block(Duration.ofSeconds(30)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204)); + }); } } @Test - void sslValidationNotSkippedByDefault() { - this.contextRunner.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) - .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", - "vcap.application.cf_api:https://my-cloud-controller.com") - .run((context) -> { - CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(context); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - WebClient webClient = (WebClient) ReflectionTestUtils.getField(interceptorSecurityService, "webClient"); - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> webClient.get() - .uri("https://self-signed.badssl.com/") - .retrieve() - .toBodilessEntity() - .block(Duration.ofSeconds(30))) - .withCauseInstanceOf(SSLException.class); - }); + @WithPackageResources("test.jks") + void sslValidationNotSkippedByDefault() throws IOException { + JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("JKS", null, "classpath:test.jks", "secret"); + SslBundle sslBundle = SslBundle.of(new JksSslStoreBundle(keyStoreDetails, keyStoreDetails)); + try (MockWebServer server = new MockWebServer()) { + server.useHttps(sslBundle.createSslContext().getSocketFactory(), false); + server.enqueue(new MockResponse().setResponseCode(204)); + server.start(); + this.contextRunner.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) + .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", + "vcap.application.cf_api:https://my-cloud-controller.com") + .run((context) -> { + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(context); + Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor"); + Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + "cloudFoundrySecurityService"); + WebClient webClient = (WebClient) ReflectionTestUtils.getField(interceptorSecurityService, + "webClient"); + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> webClient.get() + .uri(server.url("/").uri()) + .retrieve() + .toBodilessEntity() + .block(Duration.ofSeconds(30))) + .withCauseInstanceOf(SSLException.class); + }); + } } private CloudFoundryWebFluxEndpointHandlerMapping getHandlerMapping(ApplicationContext context) { @@ -365,16 +385,6 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { "No operation found with request path " + requestPath + " from " + endpoint.getOperations()); } - private static E findCause(Throwable failure, Class type) { - while (failure != null) { - if (type.isInstance(failure)) { - return type.cast(failure); - } - failure = failure.getCause(); - } - return null; - } - @Endpoint(id = "test") static class TestEndpoint { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/test.jks b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/test.jks new file mode 100644 index 0000000000000000000000000000000000000000..b60731bcc8427c94de7415ddb7990d6cfa12c8e9 GIT binary patch literal 2050 zcmZ{kX*3j!9>!=MH8TCV$kx$pgOKRjQabDs16pXWK}cd&A>0ssI&M*{vWUjI8@9{zrJ zLPC$6OzKF;CIG;BfFN=1A|Tv+s@y2sbtm8U?hB!}sLyUf^~{+v z?;5sJJ9xL(LK-U?(EBtb;Sp6=U3!ZWj2)XMY!IGgBmR^GqsnY$^}Tg)>>>sh93C#5B* z(Q0;72MMBO&(Ma^bz7f!RDN{`qT|}|0|tz5Q^tvSyOmlApUEDga>ewr{fvJFteQOY zHQJ?XH#gjvs9{KDyV|FwIt3n%I4^-753GME3AbFofUCIjZW6R-`HM$>wdAw(p#dI~ zl{5{i_$(&iwtcm(s%|GGJ-JV)#L6^9T}}47LE+eFxjS$8h|LAIPN^iEsU?Mb%ESxB z9292oLk1|o*Vizj-5-J5Rewk%JQRbyDm|7Ap&c3g2n1dwqw1epgf6&(z44YlpM za%tnGvj)dvpW@;e84GPz1BYX%lX*ekeZ`6^*@Ur% z1?MW6xCkC}FeYtx$PwkBFfxHIgk6VjD88LZXcbp_6}%P;lh%$tAI9aF5K(}^#?PJh zV;w%!3)6*PQi;}i$^J#3^CiWXy&ehc-=Bw05^)x^&>w;(o5+Wk8(3<_g{kWjNl1@l z`u3%J!fRM!@s%L10*iBN6ey&2END`J-u~SCu}YhqLu8ScPEQoarcUz4fib}%IDd4MbLhwv0ZPt@?uJBy59>CWg|85@*Za`uPt@$3Ff>W4>{cy ze5rACq?2OrY(a`?4tW>9$u+x54VMTu_cO7Zv9>OcW)3;@TQ@P@O);zeimV`xqnw#R zJf>g1E`N}gax+3Rl~b&QA|UQ+*bvbd<4p$>3pPFi+?HnNGhs!CPM>25rlJ>9Vbsa5 zgP-ij)a;;;gmW=$!vtW9*FpE8qVdYW*-G(O3fSuAfJNamhsk@K6#n?B+|G)|$@yZZ z{u~eD%(Q}Ca^k{rr-c2u5pCtk1J7qX$LVFN&-*iy?*5rS(9nO(qT#_c8~v2+CHVKQ z)H#=%$_Kg2GY6?f`D8+9E_exEE@zs|yoycx?QF5PL#@*i?P$^rA(brUdzl()<1C>9 z6=oZJ3?d&|yeINcC?@2?sbliZ#r%!{S8JgXLTKf!4WDO5hPGOf&`@H zbAW&#kjO<~>i9g&XGGKoS25W8A7&a2A^5p@RpQWb+x$Qvm6ETZl=1N4Zr8CYLQ+hJNshtTH4LvS_AjiY3lS%+Hsag}uw0pj3hjL=E zRl(ZY=%z2Og(!hHJFT71d*fym1wy#(3?XP?>j%wY8tyQ8h*4bYw=Usx5pSO=hb^NB zjV@L|BOR@C=Np|!%y(j^_ve*MATwhMOD=g=e(#Q2jg4i}`+c>kbjmG}j(|xFrcMIR$Q9VEjYf$_h{KZSw(o6<<@(a2 zZiO4hF-x!OkcNz8mMRFo$op`8>bV@}n!i4(6{O>di)Lv0KDpG{585unpq*In@x0;s z(~vY8cBb+fRpKYv`?JysK|zw7bv8=&_5O6cnqQmoVcC-{;)u)2ivdS|_z=M5QHZZfZ`!*8YiA6vm} X)wZ>64aY|IsUB6=_S0YKrHcOs&YZcr literal 0 HcmV?d00001