I am trying to set up a spring boot application to talk to my Vault server over TLS. I want to use mutual certificate authentication. I could set up the Vault server with TLS and I am able to use the CLI to login to it using client certificates. However, the spring boot application is unable to present its client certificate to the Vault server - each time I run my app, the Vault server prints:
http: TLS handshake error from 127.0.0.1:33288: tls: client didn't provide a certificate
The client (my spring boot app) prints:
org.springframework.vault.authentication.VaultLoginException:
Cannot login using org.springframework.web.client.ResourceAccessException:
I/O error on POST request for "https://localhost:8200/v1/auth/cert/login":
Received fatal alert: bad_certificate;
nested exception is javax.net.ssl.SSLHandshakeException:
Received fatal alert: bad_certificate
... (a stack trace for VaultLoginException)
Here is my bootstrap.yml
:
spring:
application.name: vault-demo
cloud.vault:
host: localhost
port: 8200
scheme: https
uri: https://localhost:8200
connection-timeout: 5000
read-timeout: 15000
config.order: -10
authentication: CERT
ssl:
trust-store: classpath:keystore.jks
trust-store-password: changeit
key-store: classpath:client-cert.jks
key-store-password: changeit
cert-auth-path: cert
I can't find any documentation around the various properties that needs to be configured under spring.cloud.vault.*
.
My client-cert.jks
store has the client certificate and key. Enabling SSL verbose logs, I can see this:
*** ServerHelloDone
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain
which indicates that the client found the server's certificate in its trust store, but it's not sending the client's certificate to the server.
Further, if I use curl
to send a login request, it is successful:
curl -k --request POST \
--cert work/ca/certs/client.cert.pem \
--key work/ca/private/client.decrypted.key.pem \
--data @payload.json \
https://localhost:8200/v1/auth/cert/login
# gives me back a JSON with newly issued token.
I also tried using a config class and passed the javax.net.ssl.keyStore
and related properties as JAVA_OPTS
, but there is absolutely no difference - vault keeps on saying that the client didn't send a certificate:
@Configuration
public class AppConfig extends AbstractVaultConfiguration {
@Value("${vault.uri}")
URI vaultUri;
@Override
public VaultEndpoint vaultEndpoint() {
return VaultEndpoint.from(vaultUri);
}
@Override
public ClientAuthentication clientAuthentication() {
try {
RestTemplate restTemplate = new RestTemplate();
HttpClientBuilder httpClientBuilder = HttpClients.custom()
.setSSLContext(SSLContext.getDefault())
.useSystemProperties();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(
httpClientBuilder.build()));
return new ClientCertificateAuthentication(restTemplate);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
Anyone can point out what I am missing / have done wrongly?
The issue was that my Vault configuration did not specify the tls_client_ca_file
property correctly. Set it to the root CA certificate and everything worked. There was no need to add an AppConfig
class either.