spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.54k stars 3.33k forks source link

JKS with null password for mTLS 'silently' fails #3521

Open krpors opened 2 months ago

krpors commented 2 months ago

Problem:

We are using Spring Cloud Gateway 4.1.4 in a project, where we are routing incoming requests downstream to an mTLS enabled server. Thus we are required to provide a few properties in the configuration:

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          key-store: classpath:tls/keystore.jks
          key-password: password
          key-store-type: JKS
          use-insecure-trust-manager: true

When the routing happens, setting up a TLS connection fails:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

There were no further helpful messages available (even after tuning logging to a finer degree). After some headache, it came to my attention that the JKS was password protected, so the thing that was missing was the property key-store-password, and after I set the value properly, all was well.

When supplying a faulty (non-null) password, Spring Cloud Gateway perfectly reports an error (but does not prematurely exit...):

java.lang.IllegalStateException: java.lang.RuntimeException: Could not load key store 'classpath:tls/keystore.jks'

Feature request

It would be more user-friendly to report some kind of error when the keystore cannot be properly loaded when booting up an instance. This may be a bit tricky, because Keystore.load(InputStream, null) does not throw an exception. It does fail though when a faulty, non-null password is used. For example:

// Null password, no exception:
{
    var store = KeyStore.getInstance("jks");
    store.load(getClass().getResourceAsStream("/tls/keystore.jks"), null);
}
// Correct password, no exception:
{
    var store = KeyStore.getInstance("jks");
    store.load(getClass().getResourceAsStream("/tls/keystore.jks"), "password".toCharArray());
}
// Incorrect, non-null password, exception:
{
    var store = KeyStore.getInstance("jks");
    store.load(getClass().getResourceAsStream("/tls/keystore.jks"), "derp".toCharArray());
}

Proposed solutions

Some things come to mind:

  1. If a keystore supplied is of type JKS, assert that the key-store-password is non-null to prevent this silent failure. I'm not sure though how other keystore types should/must act on null passwords.
  2. Allow a null password, but iterate over the certificates, execute getCertificate and assert they are not null. Rationale: if the password is null, you are able to iterate over the entries but getting any certificate returns a null.
  3. ???

In any case, this can be implemented in AbstractSslConfigurerer.

Please advise, if needed I could spend a bit of time to make a PR.

spencergibb commented 2 days ago

PRs welcome