spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.25k stars 40.7k forks source link

Provide configuration properties for configuring Jetty's SNI support #36271

Open blacknebula1 opened 1 year ago

blacknebula1 commented 1 year ago

Summary Need a way to disable sniHostCheck

Sample Repository https://github.com/blacknebula1/sni-host-check-example

Setup Java Version: 17.x Spring Version: 3.1.1

Description We are in the process of migrating to spring boot 3.x When we moved to spring boot 3, we noticed that unless the domain name in the request matches what is present in the keystore we get this error:

HTTP ERROR 400 Invalid SNI
org.eclipse.jetty.http.BadMessageException: 400: Invalid SNI
    at org.eclipse.jetty.server.SecureRequestCustomizer.customize(SecureRequestCustomizer.java:267)
    at org.eclipse.jetty.server.SecureRequestCustomizer.customize(SecureRequestCustomizer.java:208)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:501)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:558)
    at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
    at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
    at java.base/java.lang.Thread.run(Thread.java:833)</pre> 

After doing some further digging into this issue, it appears the issue is only present when not using the fqdn found in the provided keystore.

The issue is only encountered on the main app when SecureRequestCustomizer is called using the default constructor on this class https://github.com/maharshi95/Jetty/blob/master/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java#L67

I noticed in SslServerCustomizer we are calling the default constructor, therefore enforcing sniHostCheck https://github.com/spring-projects/spring-boot/blob/a460f7474fa66253158415834f3e41d121641dda/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java#L84

This sni host checking seems to be an issue on the main app as well as actuator when running on different ports.

Suggesting we provide some sort of config option that will allow us to access the app as well as actuator (running on a separate port) via IP address. something like this would be awesome

server.ssl.sni-host-check=false management.server.ssl.sni-host-check=false

kernalex-exx commented 1 year ago

Since I have the same problem: the corresponding config attributes for jetty are jetty.sslContext.sniRequired and jetty.ssl.sniHostCheck (source)

wilkinsona commented 1 year ago

We can certainly consider adding a configuration property to disable the check. In the meantime, you can disable it with a JettyServerCustomizer:

@Bean
JettyServerCustomizer disableSniHostCheck() {
    return (server) -> {
        for (Connector connector : server.getConnectors()) {
            if (connector instanceof ServerConnector serverConnector) {
                HttpConnectionFactory connectionFactory = serverConnector
                    .getConnectionFactory(HttpConnectionFactory.class);
                if (connectionFactory != null) {
                    SecureRequestCustomizer secureRequestCustomizer = connectionFactory.getHttpConfiguration()
                        .getCustomizer(SecureRequestCustomizer.class);
                    if (secureRequestCustomizer != null) {
                        secureRequestCustomizer.setSniHostCheck(false);
                    }
                }
            }
        }
    };
}
wilkinsona commented 1 year ago

When looking at this we should see if the other embedded web servers have similar configuration options and expand the scope of the issue as appropriate.

blacknebula1 commented 1 year ago

@wilkinsona your workaround seems to work for the primary app, but not for management. As my example shows, we have the primary app running on port 8443 while the management app runs on 8444. Do you have an idea how to override the management SecureRequestCustomizer by any chance?

wilkinsona commented 1 year ago

If you wrap it in a WebServerFactoryCustomizer, I think it'll be applied to be the main server and the management server:

@Bean
WebServerFactoryCustomizer<JettyServletWebServerFactory> disableSniHostCheck() {
    return (factory) -> {
        factory.addServerCustomizers((server) -> {
            for (Connector connector : server.getConnectors()) {
                if (connector instanceof ServerConnector serverConnector) {
                    HttpConnectionFactory connectionFactory = serverConnector
                        .getConnectionFactory(HttpConnectionFactory.class);
                    if (connectionFactory != null) {
                        SecureRequestCustomizer secureRequestCustomizer = connectionFactory.getHttpConfiguration()
                            .getCustomizer(SecureRequestCustomizer.class);
                        if (secureRequestCustomizer != null) {
                            secureRequestCustomizer.setSniHostCheck(false);
                        }
                    }
                }
            }
        });
    };
}
blacknebula1 commented 1 year ago

@wilkinsona still seeing the same issue on management with the above snippet. Seems that bean is hit before creating the management stuff.

wilkinsona commented 1 year ago

Sorry, I think I've got it this time. The bean needs to be defined within a @ManagementContextConfiguration:

@ManagementContextConfiguration(proxyBeanMethods = false)
class DisableSniHostCheckConfiguration {

    @Bean
    WebServerFactoryCustomizer<JettyServletWebServerFactory> disableSniHostCheck() {
        return (factory) -> {
            factory.addServerCustomizers((server) -> {
                for (Connector connector : server.getConnectors()) {
                    if (connector instanceof ServerConnector serverConnector) {
                        HttpConnectionFactory connectionFactory = serverConnector
                            .getConnectionFactory(HttpConnectionFactory.class);
                        if (connectionFactory != null) {
                            SecureRequestCustomizer secureRequestCustomizer = connectionFactory.getHttpConfiguration()
                                .getCustomizer(SecureRequestCustomizer.class);
                            if (secureRequestCustomizer != null) {
                                secureRequestCustomizer.setSniHostCheck(false);
                            }
                        }
                    }
                }
            });
        };
    }

}

If this class is in a package that's picked up by component scanning, that will be sufficient for the main web server. For the Actuator's server you also need to create a META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports file in src/main/resources that lists the class:

com.example.actuatorservice.DisableSniHostCheckConfiguration
blacknebula1 commented 1 year ago

The above solution seems to do the trick.

deepakab03 commented 1 year ago

We can certainly consider adding a configuration property to disable the check. In the meantime, you can disable it with a JettyServerCustomizer:

@Bean
JettyServerCustomizer disableSniHostCheck() {
    return (server) -> {
        for (Connector connector : server.getConnectors()) {
            if (connector instanceof ServerConnector serverConnector) {
                HttpConnectionFactory connectionFactory = serverConnector
                    .getConnectionFactory(HttpConnectionFactory.class);
                if (connectionFactory != null) {
                    SecureRequestCustomizer secureRequestCustomizer = connectionFactory.getHttpConfiguration()
                        .getCustomizer(SecureRequestCustomizer.class);
                    if (secureRequestCustomizer != null) {
                        secureRequestCustomizer.setSniHostCheck(false);
                    }
                }
            }
        }
    };
}

Had the same issue, after scrounging around for a solution for day or two, Andy's first workaround given above worked.. Thanks!

thomas4v commented 9 months ago

The above solution seems to do the trick.

Same for me, Thank you @wilkinsona

scottfrederick commented 7 months ago

I'm not sure it's a good idea for Spring Boot to support properties that make it easier to disable important security features like hostname validation on SSL/TLS connections. I realize that it is sometimes necessary, but we should think about how easy we should make it.

Andy has shown that it's possible to do this custom configuration with a JettyServerCustomizer now, but digging into the org.eclipse.jetty.server.Server to get down to the SecureRequestCustomizer is a bit cumbersome. Perhaps instead of adding support for a property we could improve the JettyServerCustomizer or add a new type of customizer that makes it easier to get to the HttpConnectionFactory or the SecureRequestCustomizer.

tdalbo92 commented 6 months ago

I'm hitting this while trying to configure kubernetes liveness and readiness checks against my application. End to end encryption, kubernetes, and Spring Boot are all common enough that I'm surprised there isn't more discussion around it.

Configuring SSL on Jetty via Spring Boot is easy enough via configuration, but the 400 SNI error hits when kubernetes performs its liveness probe. It uses the IP address of the pod directly, which isn't an allowed host.

The provided workaround is very helpful, but it's still a bit of a gross solution to the problem.