springkotlinjava-17spring-vault

Spring Vault can't validate self-signed cert signed by self-signed CA after upgrading from Spring Boot 2.7.x -> Spring Boot 3.0.0


I am running Spring Boot app using a self-signed CA cert to verify Vault certificate that is signed by the self-signed CA.

This setup has worked with Spring Vault until upgrading from Spring Boot 2.7.x -> 3.0.0. Since Spring Boot 3.0.0 required Spring Framework 6.x and hence Java 17, I've also had to upgrade org.springframework.vault:spring-vault-core from 2.3.2 to 3.0.0 which now supports Java 17.

I configure Spring Vault via extending AbstractVaultConfiguration class which requires overriding sslConfiguration method in this way:

    override fun sslConfiguration(): SslConfiguration {
        val caPemBytes = java.util.Base64.getDecoder().decode(vaultCaPemBase64)
        val vaultCaStore = ByteArrayResource(caPemBytes)
        return SslConfiguration(
            KeyStoreConfiguration.unconfigured(),
            KeyStoreConfiguration.of(vaultCaStore, null, "pem")
        )
    }

Now When I run the Spring Boot application, I get a long stack trace saying

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.vault.authentication.SessionManager]: Circular reference involving containing bean 'vaultConfiguration' - consider declaring the factory method as static for independence from its containing instance. Factory method 'sessionManager' threw exception with message: I/O error on POST request for "https://my.vault.host/v1/auth/userpass/login/my-user-name": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

which means that the request to connect to Vault to log in failed because of the HTTP client being unable to verify the self-signed Vault certificate.

Also I get this warning in the logs during Spring Boot app startup: o.s.v.c.ClientHttpRequestFactoryFactory : VaultProperties has SSL configured but the SSL configuration must be applied outside the Vault Client to use the JDK HTTP client

Here's the long stack trace:

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://my.vault.host/v1/auth/userpass/login/my-user-name": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:888) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:868) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:481) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.vault.config.AbstractVaultConfiguration.sessionManager(AbstractVaultConfiguration.java:149) ~[spring-vault-core-3.0.0.jar!/:3.0.0]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:257) ~[spring-core-6.0.2.jar!/:6.0.2]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-6.0.2.jar!/:6.0.2]
    at com.noona.authenticationservice.config.VaultConfiguration$$SpringCGLIB$$0.sessionManager(<generated>) ~[classes!/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.2.jar!/:6.0.2]
    ... 163 common frames omitted
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:309) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1357) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1232) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1175) ~[na:na]
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396) ~[na:na]
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:480) ~[na:na]
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:458) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:201) ~[na:na]
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172) ~[na:na]
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1500) ~[na:na]
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1415) ~[na:na]
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:450) ~[na:na]
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:421) ~[na:na]
    at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:580) ~[na:na]
    at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183) ~[na:na]
    at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:142) ~[na:na]
    at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:75) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:101) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.vault.client.RestTemplateBuilder.lambda$createTemplate$4(RestTemplateBuilder.java:239) ~[spring-vault-core-3.0.0.jar!/:3.0.0]
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:87) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.vault.client.VaultClients.lambda$createRestTemplate$0(VaultClients.java:117) ~[spring-vault-core-3.0.0.jar!/:3.0.0]
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:87) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:71) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66) ~[spring-web-6.0.2.jar!/:6.0.2]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:862) ~[spring-web-6.0.2.jar!/:6.0.2]
    ... 177 common frames omitted
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) ~[na:na]
    at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) ~[na:na]
    at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231) ~[na:na]
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1341) ~[na:na]
    ... 203 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
    at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ~[na:na]
    ... 208 common frames omitted

My question is, what exactly is that needs to be done here to get Spring Vault to use the self-signed CA cert to verify the self-signed Vault certificate when connecting to Vault?


Solution

  • Problem was that in newer version of spring-vault-core did not anymore include a compile-time dependency on org.apache.httpcomponents:httpclient like in the previous versions before 3.0.0, see https://mvnrepository.com/artifact/org.springframework.vault/spring-vault-core/2.3.2 for example. Version 3.0.0 has optional compile-time dependency on org.apache.httpcomponents.client5:httpclient5: https://mvnrepository.com/artifact/org.springframework.vault/spring-vault-core/3.0.0

    The reason the setup with older spring-vault-core worked was that I had other dependency pulling in the older dependency org.apache.httpcomponents:httpclient.

    In order to preserve this functionality, adding dependency to org.apache.httpcomponents.client5:httpclient5 to pom.xml or build.gradle (for example version 5.2.1) did the trick and allowed the spring-vault-core to leverage httpclient5 as 3rd party http client as opposed to the platform default.