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?
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.