spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
73.63k stars 40.32k forks source link

Ssl Bundle InsecureTrustManagerFactory Configuration #38920

Open syedyusufh opened 6 months ago

syedyusufh commented 6 months ago

How do we configure the Ssl Bundle to use InsecureTrustManagerFactory or custom TrustManagerFactory or skip hostname verification? Couldn't find these details in the documentation. Thanks

Nikunj2788 commented 6 months ago

If you need to work with self-signed certificates or for testing purposes, consider using an InsecureTrustManagerFactory. This involves creating a custom SSLContext with an InsecureTrustManager and setting it as the default for your HTTPS connections.

For the Custom manager Factory you have the scenarios where you have specific truststore requirements, create a custom SSLContext with a TrustManagerFactory initialized with your custom truststore. Ensure that you load your truststore appropriately and initialize the SSLContext with the custom trust manager.

syedyusufh commented 6 months ago

@Nikunj2788 ask is how to do the same via Ssl Bundles. Thanks

wilkinsona commented 6 months ago

@syedyusufh if you're interacting with a server that uses a self-signed certificate, have you considered trusting that certificate alone on the client-side? That, I think, would be the SSL bundle way of doing things. If you want the client to trust all self-signed certificates or to skip hostname verification, that should be done with client-specific configuration and not through SSL bundles.

syedyusufh commented 6 months ago

@wilkinsona thanks for your comments.

My understanding is that Ssl Bundle manages the trustStore, trustStrategy, etc as part of the Ssl Bundle configuration via application.properties. How do we alter the trust strategy of the Ssl Bundle managed TrustStore still benefiting from the Ssl Bundle benefits?

In other words, how can I construct a full beneficial Ssl Bundle of my underlying JKS with trustStore, trustStrategy programmatically?

Thanks

wilkinsona commented 6 months ago

I don't think I understand why you need both. You can configure an SSL bundle that trusts the server's unsigned certificates or you can configure whatever HTTP client you're using to use an insecure trust manager. Doing one of these negates the need for the other, does it not?

syedyusufh commented 6 months ago

Let us please consider the below sample from Spring.io Blog on how to setup SSL for WebClient via Ssl Bundle,

@Service
public class MyService {

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }
}

How do we now configure the underlying (WebClientSsl) TrustStore to ignore hostNameVerification? We were able to do the same without Ssl Bundle by configuring SslContext, TrustManagerFactory, KeyManagerFactory like below.

// Create an SSL context that uses that certificate
    return SslContextBuilder.forClient()
                          .keyManager(keyManagerFactory)
                          .build();
scottfrederick commented 6 months ago

I agree with Andy's statement above:

If you want the client to trust all self-signed certificates or to skip hostname verification, that should be done with client-specific configuration and not through SSL bundles.

Disabling hostname verification is a very dangerous thing to do. It might be useful for testing sometimes, but I don't think Spring Boot should do anything more to make this easy to do so that user's don't mistakenly disable verification in production applications.

We were able to do the same without Ssl Bundle by configuring SslContext, TrustManagerFactory, KeyManagerFactory like below.

One option would be to keep your code that sets up the SSLContext manually, but retrieve the trust material from a configured SSL bundle. To do this, you'd need to auto-wire an instance of SslBundles into your code, retrieve the bundle you want using SslBundles.getBundle(String name). Once you get an SslBundle from SslBundles, you can use SslBundle.getManagers() to get any KeyManagerFactory and TrustManagerFactory instances you need to configure the SSLContext.

syedyusufh commented 6 months ago

Hi @scottfrederick thanks for your inputs.

Like any enterprise we have both modern and legacy systems, so the custom TrustStrategy is a much needed one. The default SSL behavior out-of-box does NOT fit every system :(

Ssl Bundles solve the problem of application restart to update the application's Ssl Context due to the underlying certificate change and this is an awesome feature in a Microservices environment where multiple applications are run.

but I don't think Spring Boot should do anything more to make this easy to do so that user's don't mistakenly disable verification in production applications.

Spring Boot framework today provides options to configure your own TrustStrategy when setting up the SSL either via Apache Http or Netty implementations. For instance, we have out of framework implementations like InSecureTrustManagerFactory though with a warning and a disclaimer.

Once you get an SslBundle from SslBundles, you can use SslBundle.getManagers() to get any KeyManagerFactory and TrustManagerFactory instances you need to configure the SSLContext

Can we get the Ssl Bundle benefit of dynamic reloading of Ssl Context if the framework managed KeyManagerFactory and TrustManagerFactory are overridden?

Thanks

scottfrederick commented 6 months ago

@syedyusufh I'm afraid it's still not completely clear what you're trying to accomplish. It seems that you are mostly concerned with configuring SSL for client connections. Reloading of SSL material with SSL bundles is only supported for server-side connections when using Tomcat or Netty as an embedded web server, so your questions about reloading the SSL context would not apply to your client connections.

I think we would make more progress on this discussion if we had a small code sample of what you are doing now. Can you provide a complete minimal application that demonstrates your use case, and share it with us by pushing it to a separate repository on GitHub or by zipping it and attaching it to this issue? That would make it much easier for us to see if there's anything we can add to our APIs for custom configuration of client connections.

syedyusufh commented 6 months ago

Reloading of SSL material with SSL bundles is only supported for server-side connections when using Tomcat or Netty as an embedded web server, so your questions about reloading the SSL context would not apply to your client connections

I got it now. Sorry, I was with the impression Ssl Bundle applicable components are all reloadable by default.

Kindly consider allowing an option to customize the TrustStrategy for client connections when we use Ssl Bundles

Thanks

guybedo commented 1 month ago

@syedyusufh i'm a bit late to the party, but i had a similar situation i think where i needed to connect to a https server. I needed to provide client cert to connect to the server and needed to accept the server's self signed cert.

It wasn't possible to customize the SSLBundle trustmanagers / trustmanagerfactory so i ended up using the SSLBundle only to load the keys from the files. I didn't use it to build my RestTemplate and initialized the SSLContext with the SSLBundle's KeyManager[].

So, instead of:

SslBundle sslBundle = sslBundles.getBundle("rest");

return builder
    .setSslBundle(sslBundle)
    .messageConverters(converters)
    .build();

i did:

SslBundle sslBundle = sslBundles.getBundle("rest");
KeyManager[] keyManagers = sslBundle.getManagers().getKeyManagers();
SslUtils.configureDefaultSslSockerFactory(keyManagers);
return builder
    .messageConverters(converters)
    .build();

with SSLUtils:

public class SslUtils {

    public static TrustManager[] trustAllCerts() {
        return new TrustManager[] {
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }

                public void checkClientTrusted(
                    java.security.cert.X509Certificate[] certs,
                    String authType) {
                }

                public void checkServerTrusted(
                    java.security.cert.X509Certificate[] certs,
                    String authType) {
                }
            }
        };

    }

    public static void configureDefaultSslSockerFactory(KeyManager[] keyManagers) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(keyManagers, trustAllCerts(), new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }
}