Search code examples
spring-bootsslspring-cloudhashicorp-vaultspring-cloud-vault-config

Spring Cloud Vault mutual TLS authentication issue


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?


Solution

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