Add SNI support to web server SSL auto-configuration
Properties under `server.ssl.server-name-bundles` and `management.server.ssl.server-name-bundles` can be used to configure mappings of host names to SSL bundles to support SNI in embedded web servers. Closes gh-26022
This commit is contained in:
parent
d726e523f5
commit
ad79c373f8
@ -79,6 +79,7 @@ include "spring-boot-tests:spring-boot-integration-tests:spring-boot-launch-scri
|
|||||||
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-tests"
|
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-tests"
|
||||||
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-classic-tests"
|
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-classic-tests"
|
||||||
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-server-tests"
|
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-server-tests"
|
||||||
|
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-sni-tests"
|
||||||
include "spring-boot-system-tests:spring-boot-deployment-tests"
|
include "spring-boot-system-tests:spring-boot-deployment-tests"
|
||||||
include "spring-boot-system-tests:spring-boot-image-tests"
|
include "spring-boot-system-tests:spring-boot-image-tests"
|
||||||
|
|
||||||
|
@ -2173,6 +2173,10 @@
|
|||||||
"description": "SSL protocol to use.",
|
"description": "SSL protocol to use.",
|
||||||
"defaultValue": "TLS"
|
"defaultValue": "TLS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "management.server.ssl.server-name-bundles",
|
||||||
|
"description": "Mapping of host names to SSL bundles for SNI configuration."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "management.server.ssl.trust-certificate",
|
"name": "management.server.ssl.trust-certificate",
|
||||||
"description": "Path to a PEM-encoded SSL certificate authority file."
|
"description": "Path to a PEM-encoded SSL certificate authority file."
|
||||||
|
@ -337,6 +337,10 @@
|
|||||||
"description": "SSL protocol to use.",
|
"description": "SSL protocol to use.",
|
||||||
"defaultValue": "TLS"
|
"defaultValue": "TLS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "server.ssl.server-name-bundles",
|
||||||
|
"description": "Mapping of host names to SSL bundles for SNI configuration."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "server.ssl.trust-certificate",
|
"name": "server.ssl.trust-certificate",
|
||||||
"description": "Path to a PEM-encoded SSL certificate authority file."
|
"description": "Path to a PEM-encoded SSL certificate authority file."
|
||||||
@ -2676,6 +2680,10 @@
|
|||||||
"description": "SSL protocol to use.",
|
"description": "SSL protocol to use.",
|
||||||
"defaultValue": "TLS"
|
"defaultValue": "TLS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.rsocket.server.ssl.server-name-bundles",
|
||||||
|
"description": "Mapping of host names to SSL bundles for SNI configuration."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spring.rsocket.server.ssl.trust-certificate",
|
"name": "spring.rsocket.server.ssl.trust-certificate",
|
||||||
"description": "Path to a PEM-encoded SSL certificate authority file."
|
"description": "Path to a PEM-encoded SSL certificate authority file."
|
||||||
|
@ -148,6 +148,8 @@ You can configure this behavior by setting the configprop:server.compression.mim
|
|||||||
== Configure SSL
|
== Configure SSL
|
||||||
|
|
||||||
SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yaml`.
|
SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yaml`.
|
||||||
|
See xref:api:java/org/springframework/boot/web/server/Ssl.html[`Ssl`] for details of all of the supported properties.
|
||||||
|
|
||||||
The following example shows setting SSL properties using a Java KeyStore file:
|
The following example shows setting SSL properties using a Java KeyStore file:
|
||||||
|
|
||||||
[configprops,yaml]
|
[configprops,yaml]
|
||||||
@ -193,6 +195,9 @@ server:
|
|||||||
trust-certificate: "classpath:ca-cert.crt"
|
trust-certificate: "classpath:ca-cert.crt"
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[howto.webserver.configure-ssl.bundles]]
|
||||||
|
=== Using SSL Bundles
|
||||||
|
|
||||||
Alternatively, the SSL trust material can be configured in an xref:reference:features/ssl.adoc[SSL bundle] and applied to the web server as shown in this example:
|
Alternatively, the SSL trust material can be configured in an xref:reference:features/ssl.adoc[SSL bundle] and applied to the web server as shown in this example:
|
||||||
|
|
||||||
[configprops,yaml]
|
[configprops,yaml]
|
||||||
@ -205,7 +210,29 @@ server:
|
|||||||
|
|
||||||
NOTE: The `server.ssl.bundle` property can not be combined with the discrete Java KeyStore or PEM property options under `server.ssl`.
|
NOTE: The `server.ssl.bundle` property can not be combined with the discrete Java KeyStore or PEM property options under `server.ssl`.
|
||||||
|
|
||||||
See xref:api:java/org/springframework/boot/web/server/Ssl.html[`Ssl`] for details of all of the supported properties.
|
[[howto.webserver.configure-ssl.sni]]
|
||||||
|
=== Configure Server Name Indication
|
||||||
|
|
||||||
|
Tomcat, Netty, and Undertow can be configured to use unique SSL trust material for individual host names to support Server Name Indication (SNI).
|
||||||
|
SNI configuration is not supported with Jetty, but Jetty can https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html#og-protocols-ssl-sni[automatically set up SNI] if multiple certificates are provided to it.
|
||||||
|
|
||||||
|
Assuming xref:reference:features/ssl.adoc[SSL bundles] named `web`, `web-alt1`, and `web-alt2` have been configured, the following configuration can be used to assign each bundle to a host name served by the embedded web server:
|
||||||
|
|
||||||
|
[configprops,yaml]
|
||||||
|
----
|
||||||
|
server:
|
||||||
|
port: 8443
|
||||||
|
ssl:
|
||||||
|
bundle: "web"
|
||||||
|
server-name-bundles:
|
||||||
|
- server-name: "alt1.example.com"
|
||||||
|
bundle: "web-alt1"
|
||||||
|
- server-name: "alt2.example.com"
|
||||||
|
bundle: "web-alt2"
|
||||||
|
----
|
||||||
|
|
||||||
|
The bundle specified with `server.ssl.bundle` will be used for the default host, and for any client that does support SNI.
|
||||||
|
This default bundle must be configured if any `server.ssl.server-name-bundles` are configured.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import io.rsocket.SocketAcceptor;
|
import io.rsocket.SocketAcceptor;
|
||||||
import io.rsocket.transport.ServerTransport;
|
import io.rsocket.transport.ServerTransport;
|
||||||
@ -43,6 +45,7 @@ import org.springframework.boot.ssl.SslBundle;
|
|||||||
import org.springframework.boot.ssl.SslBundles;
|
import org.springframework.boot.ssl.SslBundles;
|
||||||
import org.springframework.boot.web.embedded.netty.SslServerCustomizer;
|
import org.springframework.boot.web.embedded.netty.SslServerCustomizer;
|
||||||
import org.springframework.boot.web.server.Ssl;
|
import org.springframework.boot.web.server.Ssl;
|
||||||
|
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
||||||
import org.springframework.boot.web.server.WebServerSslBundle;
|
import org.springframework.boot.web.server.WebServerSslBundle;
|
||||||
import org.springframework.http.client.ReactorResourceFactory;
|
import org.springframework.http.client.ReactorResourceFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -181,7 +184,8 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
||||||
return new SslServerCustomizer(null, this.ssl.getClientAuth(), getSslBundle()).apply(httpServer);
|
return new SslServerCustomizer(null, this.ssl.getClientAuth(), getSslBundle(), getServerNameSslBundles())
|
||||||
|
.apply(httpServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerTransport<CloseableChannel> createTcpTransport() {
|
private ServerTransport<CloseableChannel> createTcpTransport() {
|
||||||
@ -190,7 +194,8 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||||||
tcpServer = tcpServer.runOn(this.resourceFactory.getLoopResources());
|
tcpServer = tcpServer.runOn(this.resourceFactory.getLoopResources());
|
||||||
}
|
}
|
||||||
if (Ssl.isEnabled(this.ssl)) {
|
if (Ssl.isEnabled(this.ssl)) {
|
||||||
tcpServer = new TcpSslServerCustomizer(this.ssl.getClientAuth(), getSslBundle()).apply(tcpServer);
|
tcpServer = new TcpSslServerCustomizer(this.ssl.getClientAuth(), getSslBundle(), getServerNameSslBundles())
|
||||||
|
.apply(tcpServer);
|
||||||
}
|
}
|
||||||
return TcpServerTransport.create(tcpServer.bindAddress(this::getListenAddress));
|
return TcpServerTransport.create(tcpServer.bindAddress(this::getListenAddress));
|
||||||
}
|
}
|
||||||
@ -199,6 +204,13 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||||||
return WebServerSslBundle.get(this.ssl, this.sslBundles);
|
return WebServerSslBundle.get(this.ssl, this.sslBundles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final Map<String, SslBundle> getServerNameSslBundles() {
|
||||||
|
return this.ssl.getServerNameBundles()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(Ssl.ServerNameSslBundle::serverName,
|
||||||
|
(serverNameSslBundle) -> this.sslBundles.getBundle(serverNameSslBundle.bundle())));
|
||||||
|
}
|
||||||
|
|
||||||
private InetSocketAddress getListenAddress() {
|
private InetSocketAddress getListenAddress() {
|
||||||
if (this.address != null) {
|
if (this.address != null) {
|
||||||
return new InetSocketAddress(this.address.getHostAddress(), this.port);
|
return new InetSocketAddress(this.address.getHostAddress(), this.port);
|
||||||
@ -211,8 +223,9 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||||||
|
|
||||||
private final SslBundle sslBundle;
|
private final SslBundle sslBundle;
|
||||||
|
|
||||||
private TcpSslServerCustomizer(Ssl.ClientAuth clientAuth, SslBundle sslBundle) {
|
private TcpSslServerCustomizer(ClientAuth clientAuth, SslBundle sslBundle,
|
||||||
super(null, clientAuth, sslBundle);
|
Map<String, SslBundle> serverNameSslBundles) {
|
||||||
|
super(null, clientAuth, sslBundle, serverNameSslBundles);
|
||||||
this.sslBundle = sslBundle;
|
this.sslBundle = sslBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +248,9 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void customizeSsl(Server server, InetSocketAddress address) {
|
private void customizeSsl(Server server, InetSocketAddress address) {
|
||||||
|
if (!getSsl().getServerNameBundles().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Server name SSL bundles are not supported with Jetty");
|
||||||
|
}
|
||||||
new SslServerCustomizer(getHttp2(), address, getSsl().getClientAuth(), getSslBundle()).customize(server);
|
new SslServerCustomizer(getHttp2(), address, getSsl().getClientAuth(), getSslBundle()).customize(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
|
public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
|
||||||
@ -172,14 +173,22 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
||||||
SslServerCustomizer customizer = new SslServerCustomizer(getHttp2(), getSsl().getClientAuth(), getSslBundle());
|
SslServerCustomizer customizer = new SslServerCustomizer(getHttp2(), getSsl().getClientAuth(), getSslBundle(),
|
||||||
String bundleName = getSsl().getBundle();
|
getServerNameSslBundles());
|
||||||
if (StringUtils.hasText(bundleName)) {
|
addBundleUpdateHandler(null, getSsl().getBundle(), customizer);
|
||||||
getSslBundles().addBundleUpdateHandler(bundleName, customizer::updateSslBundle);
|
getSsl().getServerNameBundles()
|
||||||
}
|
.forEach((serverNameSslBundle) -> addBundleUpdateHandler(serverNameSslBundle.serverName(),
|
||||||
|
serverNameSslBundle.bundle(), customizer));
|
||||||
return customizer.apply(httpServer);
|
return customizer.apply(httpServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addBundleUpdateHandler(String hostName, String bundleName, SslServerCustomizer customizer) {
|
||||||
|
if (StringUtils.hasText(bundleName)) {
|
||||||
|
getSslBundles().addBundleUpdateHandler(bundleName,
|
||||||
|
(sslBundle) -> customizer.updateSslBundle(hostName, sslBundle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private HttpProtocol[] listProtocols() {
|
private HttpProtocol[] listProtocols() {
|
||||||
List<HttpProtocol> protocols = new ArrayList<>();
|
List<HttpProtocol> protocols = new ArrayList<>();
|
||||||
protocols.add(HttpProtocol.HTTP11);
|
protocols.add(HttpProtocol.HTTP11);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.boot.web.embedded.netty;
|
package org.springframework.boot.web.embedded.netty;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import io.netty.handler.ssl.ClientAuth;
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@ -54,13 +57,17 @@ public class SslServerCustomizer implements NettyServerCustomizer {
|
|||||||
|
|
||||||
private volatile SslProvider sslProvider;
|
private volatile SslProvider sslProvider;
|
||||||
|
|
||||||
|
private final Map<String, SslProvider> serverNameSslProviders;
|
||||||
|
|
||||||
private volatile SslBundle sslBundle;
|
private volatile SslBundle sslBundle;
|
||||||
|
|
||||||
public SslServerCustomizer(Http2 http2, Ssl.ClientAuth clientAuth, SslBundle sslBundle) {
|
public SslServerCustomizer(Http2 http2, Ssl.ClientAuth clientAuth, SslBundle sslBundle,
|
||||||
|
Map<String, SslBundle> serverNameSslBundles) {
|
||||||
this.http2 = http2;
|
this.http2 = http2;
|
||||||
this.clientAuth = Ssl.ClientAuth.map(clientAuth, ClientAuth.NONE, ClientAuth.OPTIONAL, ClientAuth.REQUIRE);
|
this.clientAuth = Ssl.ClientAuth.map(clientAuth, ClientAuth.NONE, ClientAuth.OPTIONAL, ClientAuth.REQUIRE);
|
||||||
this.sslBundle = sslBundle;
|
this.sslBundle = sslBundle;
|
||||||
this.sslProvider = createSslProvider(sslBundle);
|
this.sslProvider = createSslProvider(sslBundle);
|
||||||
|
this.serverNameSslProviders = createServerNameSslProviders(serverNameSslBundles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -69,14 +76,29 @@ public class SslServerCustomizer implements NettyServerCustomizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applySecurity(SslContextSpec spec) {
|
private void applySecurity(SslContextSpec spec) {
|
||||||
spec.sslContext(this.sslProvider.getSslContext())
|
spec.sslContext(this.sslProvider.getSslContext()).setSniAsyncMappings((domainName, promise) -> {
|
||||||
.setSniAsyncMappings((domainName, promise) -> promise.setSuccess(this.sslProvider));
|
SslProvider provider = (domainName != null) ? this.serverNameSslProviders.get(domainName)
|
||||||
|
: this.sslProvider;
|
||||||
|
return promise.setSuccess(provider);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSslBundle(SslBundle sslBundle) {
|
void updateSslBundle(String hostName, SslBundle sslBundle) {
|
||||||
logger.debug("SSL Bundle has been updated, reloading SSL configuration");
|
logger.debug("SSL Bundle has been updated, reloading SSL configuration");
|
||||||
this.sslBundle = sslBundle;
|
if (hostName == null) {
|
||||||
this.sslProvider = createSslProvider(sslBundle);
|
this.sslBundle = sslBundle;
|
||||||
|
this.sslProvider = createSslProvider(sslBundle);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.serverNameSslProviders.put(hostName, createSslProvider(sslBundle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, SslProvider> createServerNameSslProviders(Map<String, SslBundle> serverNameSslBundles) {
|
||||||
|
Map<String, SslProvider> serverNameSslProviders = new HashMap<>();
|
||||||
|
serverNameSslBundles
|
||||||
|
.forEach((hostName, sslBundle) -> serverNameSslProviders.put(hostName, createSslProvider(sslBundle)));
|
||||||
|
return serverNameSslProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslProvider createSslProvider(SslBundle sslBundle) {
|
private SslProvider createSslProvider(SslBundle sslBundle) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.boot.web.embedded.tomcat;
|
package org.springframework.boot.web.embedded.tomcat;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.catalina.connector.Connector;
|
import org.apache.catalina.connector.Connector;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.coyote.ProtocolHandler;
|
import org.apache.coyote.ProtocolHandler;
|
||||||
@ -56,36 +58,47 @@ class SslConnectorCustomizer {
|
|||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(SslBundle updatedSslBundle) {
|
void update(String hostName, SslBundle updatedSslBundle) {
|
||||||
this.logger.debug("SSL Bundle has been updated, reloading SSL configuration");
|
AbstractHttp11JsseProtocol<?> protocol = (AbstractHttp11JsseProtocol<?>) this.connector.getProtocolHandler();
|
||||||
customize(updatedSslBundle);
|
String host = (hostName != null) ? hostName : protocol.getDefaultSSLHostConfigName();
|
||||||
|
this.logger.debug("SSL Bundle for host " + host + " has been updated, reloading SSL configuration");
|
||||||
|
addSslHostConfig(protocol, host, updatedSslBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void customize(SslBundle sslBundle) {
|
void customize(SslBundle sslBundle, Map<String, SslBundle> serverNameSslBundles) {
|
||||||
ProtocolHandler handler = this.connector.getProtocolHandler();
|
ProtocolHandler handler = this.connector.getProtocolHandler();
|
||||||
Assert.state(handler instanceof AbstractHttp11JsseProtocol,
|
Assert.state(handler instanceof AbstractHttp11JsseProtocol,
|
||||||
"To use SSL, the connector's protocol handler must be an AbstractHttp11JsseProtocol subclass");
|
"To use SSL, the connector's protocol handler must be an AbstractHttp11JsseProtocol subclass");
|
||||||
configureSsl(sslBundle, (AbstractHttp11JsseProtocol<?>) handler);
|
configureSsl((AbstractHttp11JsseProtocol<?>) handler, sslBundle, serverNameSslBundles);
|
||||||
this.connector.setScheme("https");
|
this.connector.setScheme("https");
|
||||||
this.connector.setSecure(true);
|
this.connector.setSecure(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL.
|
* Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL.
|
||||||
* @param sslBundle the SSL bundle
|
|
||||||
* @param protocol the protocol
|
* @param protocol the protocol
|
||||||
|
* @param sslBundle the SSL bundle
|
||||||
|
* @param serverNameSslBundles the SSL bundles for specific SNI host names
|
||||||
*/
|
*/
|
||||||
private void configureSsl(SslBundle sslBundle, AbstractHttp11JsseProtocol<?> protocol) {
|
private void configureSsl(AbstractHttp11JsseProtocol<?> protocol, SslBundle sslBundle,
|
||||||
|
Map<String, SslBundle> serverNameSslBundles) {
|
||||||
protocol.setSSLEnabled(true);
|
protocol.setSSLEnabled(true);
|
||||||
|
if (sslBundle != null) {
|
||||||
|
addSslHostConfig(protocol, protocol.getDefaultSSLHostConfigName(), sslBundle);
|
||||||
|
}
|
||||||
|
serverNameSslBundles.forEach((hostName, bundle) -> addSslHostConfig(protocol, hostName, bundle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSslHostConfig(AbstractHttp11JsseProtocol<?> protocol, String hostName, SslBundle sslBundle) {
|
||||||
SSLHostConfig sslHostConfig = new SSLHostConfig();
|
SSLHostConfig sslHostConfig = new SSLHostConfig();
|
||||||
sslHostConfig.setHostName(protocol.getDefaultSSLHostConfigName());
|
sslHostConfig.setHostName(hostName);
|
||||||
configureSslClientAuth(sslHostConfig);
|
configureSslClientAuth(sslHostConfig);
|
||||||
applySslBundle(sslBundle, protocol, sslHostConfig);
|
applySslBundle(protocol, sslHostConfig, sslBundle);
|
||||||
protocol.addSslHostConfig(sslHostConfig, true);
|
protocol.addSslHostConfig(sslHostConfig, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySslBundle(SslBundle sslBundle, AbstractHttp11JsseProtocol<?> protocol,
|
private void applySslBundle(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
|
||||||
SSLHostConfig sslHostConfig) {
|
SslBundle sslBundle) {
|
||||||
SslBundleKey key = sslBundle.getKey();
|
SslBundleKey key = sslBundle.getKey();
|
||||||
SslStoreBundle stores = sslBundle.getStores();
|
SslStoreBundle stores = sslBundle.getStores();
|
||||||
SslOptions options = sslBundle.getOptions();
|
SslOptions options = sslBundle.getOptions();
|
||||||
|
@ -61,6 +61,7 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
* @author HaiTao Zhang
|
* @author HaiTao Zhang
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||||
@ -237,10 +238,17 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
|||||||
|
|
||||||
private void customizeSsl(Connector connector) {
|
private void customizeSsl(Connector connector) {
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
||||||
customizer.customize(getSslBundle());
|
customizer.customize(getSslBundle(), getServerNameSslBundles());
|
||||||
String sslBundleName = getSsl().getBundle();
|
addBundleUpdateHandler(null, getSsl().getBundle(), customizer);
|
||||||
|
getSsl().getServerNameBundles()
|
||||||
|
.forEach((serverNameSslBundle) -> addBundleUpdateHandler(serverNameSslBundle.serverName(),
|
||||||
|
serverNameSslBundle.bundle(), customizer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBundleUpdateHandler(String hostName, String sslBundleName, SslConnectorCustomizer customizer) {
|
||||||
if (StringUtils.hasText(sslBundleName)) {
|
if (StringUtils.hasText(sslBundleName)) {
|
||||||
getSslBundles().addBundleUpdateHandler(sslBundleName, customizer::update);
|
getSslBundles().addBundleUpdateHandler(sslBundleName,
|
||||||
|
(sslBundle) -> customizer.update(hostName, sslBundle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Christoffer Sawicki
|
* @author Christoffer Sawicki
|
||||||
* @author Dawid Antecki
|
* @author Dawid Antecki
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
* @see #setPort(int)
|
* @see #setPort(int)
|
||||||
* @see #setContextLifecycleListeners(Collection)
|
* @see #setContextLifecycleListeners(Collection)
|
||||||
@ -379,10 +380,17 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
|||||||
|
|
||||||
private void customizeSsl(Connector connector) {
|
private void customizeSsl(Connector connector) {
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
||||||
customizer.customize(getSslBundle());
|
customizer.customize(getSslBundle(), getServerNameSslBundles());
|
||||||
String sslBundleName = getSsl().getBundle();
|
addBundleUpdateHandler(null, getSsl().getBundle(), customizer);
|
||||||
|
getSsl().getServerNameBundles()
|
||||||
|
.forEach((serverNameSslBundle) -> addBundleUpdateHandler(serverNameSslBundle.serverName(),
|
||||||
|
serverNameSslBundle.bundle(), customizer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBundleUpdateHandler(String hostName, String sslBundleName, SslConnectorCustomizer customizer) {
|
||||||
if (StringUtils.hasText(sslBundleName)) {
|
if (StringUtils.hasText(sslBundleName)) {
|
||||||
getSslBundles().addBundleUpdateHandler(sslBundleName, customizer::update);
|
getSslBundles().addBundleUpdateHandler(sslBundleName,
|
||||||
|
(sslBundle) -> customizer.update(hostName, sslBundle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,10 +17,13 @@
|
|||||||
package org.springframework.boot.web.embedded.undertow;
|
package org.springframework.boot.web.embedded.undertow;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
|
import io.undertow.protocols.ssl.SNIContextMatcher;
|
||||||
|
import io.undertow.protocols.ssl.SNISSLContext;
|
||||||
import org.xnio.Options;
|
import org.xnio.Options;
|
||||||
import org.xnio.Sequence;
|
import org.xnio.Sequence;
|
||||||
import org.xnio.SslClientAuthMode;
|
import org.xnio.SslClientAuthMode;
|
||||||
@ -47,18 +50,21 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
|
|||||||
|
|
||||||
private final SslBundle sslBundle;
|
private final SslBundle sslBundle;
|
||||||
|
|
||||||
SslBuilderCustomizer(int port, InetAddress address, ClientAuth clientAuth, SslBundle sslBundle) {
|
private final Map<String, SslBundle> serverNameSslBundles;
|
||||||
|
|
||||||
|
SslBuilderCustomizer(int port, InetAddress address, ClientAuth clientAuth, SslBundle sslBundle,
|
||||||
|
Map<String, SslBundle> serverNameSslBundles) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.clientAuth = clientAuth;
|
this.clientAuth = clientAuth;
|
||||||
this.sslBundle = sslBundle;
|
this.sslBundle = sslBundle;
|
||||||
|
this.serverNameSslBundles = serverNameSslBundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(Undertow.Builder builder) {
|
public void customize(Undertow.Builder builder) {
|
||||||
SslOptions options = this.sslBundle.getOptions();
|
SslOptions options = this.sslBundle.getOptions();
|
||||||
SSLContext sslContext = this.sslBundle.createSslContext();
|
builder.addHttpsListener(this.port, getListenAddress(), createSslContext());
|
||||||
builder.addHttpsListener(this.port, getListenAddress(), sslContext);
|
|
||||||
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, ClientAuth.map(this.clientAuth,
|
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, ClientAuth.map(this.clientAuth,
|
||||||
SslClientAuthMode.NOT_REQUESTED, SslClientAuthMode.REQUESTED, SslClientAuthMode.REQUIRED));
|
SslClientAuthMode.NOT_REQUESTED, SslClientAuthMode.REQUESTED, SslClientAuthMode.REQUIRED));
|
||||||
if (options.getEnabledProtocols() != null) {
|
if (options.getEnabledProtocols() != null) {
|
||||||
@ -69,6 +75,14 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SSLContext createSslContext() {
|
||||||
|
SNIContextMatcher.Builder builder = new SNIContextMatcher.Builder();
|
||||||
|
builder.setDefaultContext(this.sslBundle.createSslContext());
|
||||||
|
this.serverNameSslBundles
|
||||||
|
.forEach((server, sslBundle) -> builder.addMatch(server, sslBundle.createSslContext()));
|
||||||
|
return new SNISSLContext(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
private String getListenAddress() {
|
private String getListenAddress() {
|
||||||
if (this.address == null) {
|
if (this.address == null) {
|
||||||
return "0.0.0.0";
|
return "0.0.0.0";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -31,6 +31,7 @@ import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
|
|||||||
* {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s.
|
* {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s.
|
||||||
*
|
*
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||||
@ -137,7 +138,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||||
Undertow.Builder builder = this.delegate.createBuilder(this, this::getSslBundle);
|
Undertow.Builder builder = this.delegate.createBuilder(this, this::getSslBundle, this::getServerNameSslBundles);
|
||||||
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
|
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
|
||||||
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
|
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
|
||||||
return new UndertowWebServer(builder, httpHandlerFactories, getPort() >= 0);
|
return new UndertowWebServer(builder, httpHandlerFactories, getPort() >= 0);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -85,6 +85,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Marcos Barbero
|
* @author Marcos Barbero
|
||||||
* @author Eddú Meléndez
|
* @author Eddú Meléndez
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
* @see UndertowServletWebServer
|
* @see UndertowServletWebServer
|
||||||
*/
|
*/
|
||||||
@ -295,7 +296,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebServer getWebServer(ServletContextInitializer... initializers) {
|
public WebServer getWebServer(ServletContextInitializer... initializers) {
|
||||||
Builder builder = this.delegate.createBuilder(this, this::getSslBundle);
|
Builder builder = this.delegate.createBuilder(this, this::getSslBundle, this::getServerNameSslBundles);
|
||||||
DeploymentManager manager = createManager(initializers);
|
DeploymentManager manager = createManager(initializers);
|
||||||
return getUndertowWebServer(builder, manager, getPort());
|
return getUndertowWebServer(builder, manager, getPort());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,6 +23,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
class UndertowWebServerFactoryDelegate {
|
class UndertowWebServerFactoryDelegate {
|
||||||
|
|
||||||
@ -143,7 +145,8 @@ class UndertowWebServerFactoryDelegate {
|
|||||||
return this.useForwardHeaders;
|
return this.useForwardHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
Builder createBuilder(AbstractConfigurableWebServerFactory factory, Supplier<SslBundle> sslBundleSupplier) {
|
Builder createBuilder(AbstractConfigurableWebServerFactory factory, Supplier<SslBundle> sslBundleSupplier,
|
||||||
|
Supplier<Map<String, SslBundle>> serverNameSslBundlesSupplier) {
|
||||||
InetAddress address = factory.getAddress();
|
InetAddress address = factory.getAddress();
|
||||||
int port = factory.getPort();
|
int port = factory.getPort();
|
||||||
Builder builder = Undertow.builder();
|
Builder builder = Undertow.builder();
|
||||||
@ -165,7 +168,8 @@ class UndertowWebServerFactoryDelegate {
|
|||||||
}
|
}
|
||||||
Ssl ssl = factory.getSsl();
|
Ssl ssl = factory.getSsl();
|
||||||
if (Ssl.isEnabled(ssl)) {
|
if (Ssl.isEnabled(ssl)) {
|
||||||
new SslBuilderCustomizer(factory.getPort(), address, ssl.getClientAuth(), sslBundleSupplier.get())
|
new SslBuilderCustomizer(factory.getPort(), address, ssl.getClientAuth(), sslBundleSupplier.get(),
|
||||||
|
serverNameSslBundlesSupplier.get())
|
||||||
.customize(builder);
|
.customize(builder);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -22,10 +22,13 @@ import java.net.InetAddress;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.boot.ssl.SslBundle;
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.boot.ssl.SslBundles;
|
import org.springframework.boot.ssl.SslBundles;
|
||||||
|
import org.springframework.boot.web.server.Ssl.ServerNameSslBundle;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,6 +198,13 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
|
|||||||
return WebServerSslBundle.get(this.ssl, this.sslBundles);
|
return WebServerSslBundle.get(this.ssl, this.sslBundles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final Map<String, SslBundle> getServerNameSslBundles() {
|
||||||
|
return this.ssl.getServerNameBundles()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(ServerNameSslBundle::serverName,
|
||||||
|
(serverNameSslBundle) -> this.sslBundles.getBundle(serverNameSslBundle.bundle())));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the absolute temp dir for given web server.
|
* Return the absolute temp dir for given web server.
|
||||||
* @param prefix server name
|
* @param prefix server name
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.boot.web.server;
|
package org.springframework.boot.web.server;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple server-independent abstraction for SSL configuration.
|
* Simple server-independent abstraction for SSL configuration.
|
||||||
*
|
*
|
||||||
@ -67,6 +70,8 @@ public class Ssl {
|
|||||||
|
|
||||||
private String protocol = "TLS";
|
private String protocol = "TLS";
|
||||||
|
|
||||||
|
private List<ServerNameSslBundle> serverNameBundles = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether to enable SSL support.
|
* Return whether to enable SSL support.
|
||||||
* @return whether to enable SSL support
|
* @return whether to enable SSL support
|
||||||
@ -325,6 +330,18 @@ public class Ssl {
|
|||||||
return (ssl != null) && ssl.isEnabled();
|
return (ssl != null) && ssl.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the mapping of host names to SSL bundles for SNI configuration.
|
||||||
|
* @return the host name to SSL bundle mapping
|
||||||
|
*/
|
||||||
|
public List<ServerNameSslBundle> getServerNameBundles() {
|
||||||
|
return this.serverNameBundles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerNameBundles(List<ServerNameSslBundle> serverNames) {
|
||||||
|
this.serverNameBundles = serverNames;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to create an {@link Ssl} instance for a specific bundle name.
|
* Factory method to create an {@link Ssl} instance for a specific bundle name.
|
||||||
* @param bundle the name of the bundle
|
* @param bundle the name of the bundle
|
||||||
@ -337,6 +354,9 @@ public class Ssl {
|
|||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record ServerNameSslBundle(String serverName, String bundle) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client authentication types.
|
* Client authentication types.
|
||||||
*/
|
*/
|
||||||
|
@ -206,7 +206,8 @@ public final class WebServerSslBundle implements SslBundle {
|
|||||||
private final String keyStorePassword;
|
private final String keyStorePassword;
|
||||||
|
|
||||||
private WebServerSslStoreBundle(KeyStore keyStore, KeyStore trustStore, String keyStorePassword) {
|
private WebServerSslStoreBundle(KeyStore keyStore, KeyStore trustStore, String keyStorePassword) {
|
||||||
Assert.state(keyStore != null || trustStore != null, "SSL is enabled but no trust material is configured");
|
Assert.state(keyStore != null || trustStore != null,
|
||||||
|
"SSL is enabled but no trust material is configured for the default host");
|
||||||
this.keyStore = keyStore;
|
this.keyStore = keyStore;
|
||||||
this.trustStore = trustStore;
|
this.trustStore = trustStore;
|
||||||
this.keyStorePassword = keyStorePassword;
|
this.keyStorePassword = keyStorePassword;
|
||||||
|
@ -24,6 +24,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -67,6 +68,7 @@ import org.springframework.boot.web.server.GracefulShutdownResult;
|
|||||||
import org.springframework.boot.web.server.PortInUseException;
|
import org.springframework.boot.web.server.PortInUseException;
|
||||||
import org.springframework.boot.web.server.Shutdown;
|
import org.springframework.boot.web.server.Shutdown;
|
||||||
import org.springframework.boot.web.server.Ssl;
|
import org.springframework.boot.web.server.Ssl;
|
||||||
|
import org.springframework.boot.web.server.Ssl.ServerNameSslBundle;
|
||||||
import org.springframework.boot.web.server.WebServerException;
|
import org.springframework.boot.web.server.WebServerException;
|
||||||
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
|
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
|
||||||
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests;
|
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests;
|
||||||
@ -74,6 +76,7 @@ import org.springframework.util.ReflectionUtils;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
@ -283,6 +286,19 @@ class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryT
|
|||||||
assertThat(sslContextFactory.getIncludeProtocols()).containsExactly("TLSv1.1");
|
assertThat(sslContextFactory.getIncludeProtocols()).containsExactly("TLSv1.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sslServerNameBundlesConfigurationThrowsException() {
|
||||||
|
Ssl ssl = new Ssl();
|
||||||
|
ssl.setBundle("test");
|
||||||
|
List<ServerNameSslBundle> bundles = List.of(new ServerNameSslBundle("first", "test1"),
|
||||||
|
new ServerNameSslBundle("second", "test2"));
|
||||||
|
ssl.setServerNameBundles(bundles);
|
||||||
|
JettyServletWebServerFactory factory = getFactory();
|
||||||
|
factory.setSsl(ssl);
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.webServer = factory.getWebServer())
|
||||||
|
.withMessageContaining("Server name SSL bundles are not supported with Jetty");
|
||||||
|
}
|
||||||
|
|
||||||
private SslContextFactory extractSslContextFactory(SslConnectionFactory connectionFactory) {
|
private SslContextFactory extractSslContextFactory(SslConnectionFactory connectionFactory) {
|
||||||
try {
|
try {
|
||||||
return connectionFactory.getSslContextFactory();
|
return connectionFactory.getSslContextFactory();
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.boot.web.embedded.tomcat;
|
package org.springframework.boot.web.embedded.tomcat;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.apache.catalina.connector.Connector;
|
import org.apache.catalina.connector.Connector;
|
||||||
import org.apache.catalina.startup.Tomcat;
|
import org.apache.catalina.startup.Tomcat;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
@ -76,7 +78,7 @@ class SslConnectorCustomizerTests {
|
|||||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||||
Connector connector = this.tomcat.getConnector();
|
Connector connector = this.tomcat.getConnector();
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||||
customizer.customize(WebServerSslBundle.get(ssl));
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
this.tomcat.start();
|
this.tomcat.start();
|
||||||
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
|
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
|
||||||
assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE");
|
assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE");
|
||||||
@ -91,7 +93,7 @@ class SslConnectorCustomizerTests {
|
|||||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||||
Connector connector = this.tomcat.getConnector();
|
Connector connector = this.tomcat.getConnector();
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||||
customizer.customize(WebServerSslBundle.get(ssl));
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
this.tomcat.start();
|
this.tomcat.start();
|
||||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||||
@ -107,7 +109,7 @@ class SslConnectorCustomizerTests {
|
|||||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||||
Connector connector = this.tomcat.getConnector();
|
Connector connector = this.tomcat.getConnector();
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||||
customizer.customize(WebServerSslBundle.get(ssl));
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
this.tomcat.start();
|
this.tomcat.start();
|
||||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||||
@ -119,7 +121,7 @@ class SslConnectorCustomizerTests {
|
|||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
assertThatIllegalStateException().isThrownBy(() -> {
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||||
Ssl.ClientAuth.NONE);
|
Ssl.ClientAuth.NONE);
|
||||||
customizer.customize(WebServerSslBundle.get(new Ssl()));
|
customizer.customize(WebServerSslBundle.get(new Ssl()), Collections.emptyMap());
|
||||||
}).withMessageContaining("SSL is enabled but no trust material is configured");
|
}).withMessageContaining("SSL is enabled but no trust material is configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +135,7 @@ class SslConnectorCustomizerTests {
|
|||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
assertThatIllegalStateException().isThrownBy(() -> {
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||||
ssl.getClientAuth());
|
ssl.getClientAuth());
|
||||||
customizer.customize(WebServerSslBundle.get(ssl));
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
}).withMessageContaining("must be empty or null for PKCS11 hardware key stores");
|
}).withMessageContaining("must be empty or null for PKCS11 hardware key stores");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +147,8 @@ class SslConnectorCustomizerTests {
|
|||||||
ssl.setKeyStorePassword("1234");
|
ssl.setKeyStorePassword("1234");
|
||||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||||
ssl.getClientAuth());
|
ssl.getClientAuth());
|
||||||
assertThatNoException().isThrownBy(() -> customizer.customize(WebServerSslBundle.get(ssl)));
|
assertThatNoException()
|
||||||
|
.isThrownBy(() -> customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
id "org.springframework.boot.conventions"
|
||||||
|
id "org.springframework.boot.integration-test"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Spring Boot SNI Integration Tests"
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
app project(path: ":spring-boot-project:spring-boot-dependencies", configuration: "mavenRepository")
|
||||||
|
app project(path: ":spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin", configuration: "mavenRepository")
|
||||||
|
app project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-web", configuration: "mavenRepository")
|
||||||
|
app project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter", configuration: "mavenRepository")
|
||||||
|
|
||||||
|
intTestImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-parent")))
|
||||||
|
intTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
|
||||||
|
intTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||||
|
intTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
|
||||||
|
intTestImplementation("org.testcontainers:junit-jupiter")
|
||||||
|
intTestImplementation("org.testcontainers:testcontainers")
|
||||||
|
}
|
||||||
|
|
||||||
|
task syncMavenRepository(type: Sync) {
|
||||||
|
from configurations.app
|
||||||
|
into "${buildDir}/int-test-maven-repository"
|
||||||
|
}
|
||||||
|
|
||||||
|
task syncReactiveServerAppSource(type: org.springframework.boot.build.SyncAppSource) {
|
||||||
|
sourceDirectory = file("spring-boot-sni-reactive-app")
|
||||||
|
destinationDirectory = file("${buildDir}/spring-boot-sni-reactive-app")
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildReactiveServerApps(type: GradleBuild) {
|
||||||
|
dependsOn syncReactiveServerAppSource, syncMavenRepository
|
||||||
|
dir = "${buildDir}/spring-boot-sni-reactive-app"
|
||||||
|
startParameter.buildCacheEnabled = false
|
||||||
|
tasks = [
|
||||||
|
"nettyServerApp",
|
||||||
|
"tomcatServerApp",
|
||||||
|
"undertowServerApp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
task syncServletServerAppSource(type: org.springframework.boot.build.SyncAppSource) {
|
||||||
|
sourceDirectory = file("spring-boot-sni-servlet-app")
|
||||||
|
destinationDirectory = file("${buildDir}/spring-boot-sni-servlet-app")
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildServletServerApps(type: GradleBuild) {
|
||||||
|
dependsOn syncServletServerAppSource, syncMavenRepository
|
||||||
|
dir = "${buildDir}/spring-boot-sni-servlet-app"
|
||||||
|
startParameter.buildCacheEnabled = false
|
||||||
|
tasks = [
|
||||||
|
"tomcatServerApp",
|
||||||
|
"undertowServerApp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
task syncClientAppSource(type: org.springframework.boot.build.SyncAppSource) {
|
||||||
|
sourceDirectory = file("spring-boot-sni-client-app")
|
||||||
|
destinationDirectory = file("${buildDir}/spring-boot-sni-client-app")
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildClientApp(type: GradleBuild) {
|
||||||
|
dependsOn syncClientAppSource, syncMavenRepository
|
||||||
|
dir = "${buildDir}/spring-boot-sni-client-app"
|
||||||
|
startParameter.buildCacheEnabled = false
|
||||||
|
tasks = ["build"]
|
||||||
|
}
|
||||||
|
|
||||||
|
intTest {
|
||||||
|
inputs.files(
|
||||||
|
"${buildDir}/spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-netty-reactive.jar",
|
||||||
|
"${buildDir}/spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-reactive.jar",
|
||||||
|
"${buildDir}/spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-servlet.jar",
|
||||||
|
"${buildDir}/spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow-reactive.jar",
|
||||||
|
"${buildDir}/spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow-servlet.jar")
|
||||||
|
.withPropertyName("applicationArchives")
|
||||||
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
.withNormalizer(ClasspathNormalizer)
|
||||||
|
dependsOn buildReactiveServerApps, buildServletServerApps, buildClientApp
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
create_ssl_config() {
|
||||||
|
cat > openssl.cnf <<_END_
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = example.com
|
||||||
|
DNS.2 = localhost
|
||||||
|
[ server_cert ]
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
nsCertType = server
|
||||||
|
[ client_cert ]
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
nsCertType = client
|
||||||
|
_END_
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_ca_cert() {
|
||||||
|
local location=$1
|
||||||
|
|
||||||
|
mkdir -p ${location}
|
||||||
|
|
||||||
|
openssl genrsa -out ${location}/test-ca.key 4096
|
||||||
|
openssl req -key ${location}/test-ca.key -out ${location}/test-ca.crt \
|
||||||
|
-x509 -new -nodes -sha256 -days 365 \
|
||||||
|
-subj "/O=Spring Boot Test/CN=Certificate Authority" \
|
||||||
|
-addext "subjectAltName=DNS:hello.example.com,DNS:hello-alt.example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_cert() {
|
||||||
|
local location=$1
|
||||||
|
local caLocation=$2
|
||||||
|
local hostname=$3
|
||||||
|
|
||||||
|
local keyfile=${location}/test-${hostname}-server.key
|
||||||
|
local certfile=${location}/test-${hostname}-server.crt
|
||||||
|
|
||||||
|
mkdir -p ${location}
|
||||||
|
|
||||||
|
openssl genrsa -out ${keyfile} 2048
|
||||||
|
openssl req -key ${keyfile} \
|
||||||
|
-new -sha256 \
|
||||||
|
-subj "/O=Spring Boot Test/CN=${hostname}.example.com" \
|
||||||
|
-addext "subjectAltName=DNS:${hostname}.example.com" | \
|
||||||
|
openssl x509 -req -out ${certfile} \
|
||||||
|
-CA ${caLocation}/test-ca.crt -CAkey ${caLocation}/test-ca.key -CAserial ${caLocation}/test-ca.txt -CAcreateserial \
|
||||||
|
-sha256 -days 365 \
|
||||||
|
-extfile openssl.cnf \
|
||||||
|
-extensions server_cert
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v openssl &> /dev/null; then
|
||||||
|
echo "openssl is required"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p certs
|
||||||
|
|
||||||
|
create_ssl_config
|
||||||
|
generate_ca_cert certs/ca
|
||||||
|
generate_cert certs/default certs/ca hello
|
||||||
|
generate_cert certs/alt certs/ca hello-alt
|
||||||
|
|
||||||
|
rm -f openssl.cnf
|
||||||
|
rm -f certs/ca/test-ca.key certs/ca/test-ca.txt
|
||||||
|
|
||||||
|
cp -r certs/* spring-boot-sni-reactive-app/src/main/resources
|
||||||
|
cp -r certs/* spring-boot-sni-servlet-app/src/main/resources
|
||||||
|
cp -r certs/ca/* spring-boot-sni-client-app/src/main/resources/ca
|
||||||
|
|
||||||
|
rm -rf certs
|
@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
id "org.springframework.boot"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "io.spring.dependency-management"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
resolutionStrategy {
|
||||||
|
eachPlugin {
|
||||||
|
if (requested.id.id == "org.springframework.boot") {
|
||||||
|
useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.boot.WebApplicationType;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.web.client.RestClientSsl;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
import org.springframework.boot.ssl.SslBundles;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SniClientApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new SpringApplicationBuilder(SniClientApplication.class)
|
||||||
|
.web(WebApplicationType.NONE).run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestClient restClient(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
|
||||||
|
return restClientBuilder.apply(ssl.fromBundle("server")).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CommandLineRunner commandLineRunner(RestClient client) {
|
||||||
|
return ((args) -> {
|
||||||
|
for (String hostname : args) {
|
||||||
|
callServer(client, hostname);
|
||||||
|
callActuator(client, hostname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void callServer(RestClient client, String hostname) {
|
||||||
|
String url = "https://" + hostname + ":8443/";
|
||||||
|
System.out.println(">>>>>> Calling server at '" + url + "'");
|
||||||
|
try {
|
||||||
|
ResponseEntity<String> response = client.get().uri(url).retrieve().toEntity(String.class);
|
||||||
|
System.out.println(">>>>>> Server response status code is '" + response.getStatusCode() + "'");
|
||||||
|
System.out.println(">>>>>> Server response body is '" + response + "'");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.out.println(">>>>>> Exception thrown calling server at '" + url + "': " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void callActuator(RestClient client, String hostname) {
|
||||||
|
String url = "https://" + hostname + ":8444/actuator/health";
|
||||||
|
System.out.println(">>>>>> Calling server actuator at '" + url + "'");
|
||||||
|
try {
|
||||||
|
ResponseEntity<String> response = client.get().uri(url).retrieve().toEntity(String.class);
|
||||||
|
System.out.println(">>>>>> Server actuator response status code is '" + response.getStatusCode() + "'");
|
||||||
|
System.out.println(">>>>>> Server actuator response body is '" + response + "'");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.out.println(">>>>>> Exception thrown calling server actuator at '" + url + "': " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
spring:
|
||||||
|
ssl:
|
||||||
|
bundle:
|
||||||
|
pem:
|
||||||
|
server:
|
||||||
|
truststore:
|
||||||
|
certificate: "classpath:ca/test-ca.crt"
|
@ -0,0 +1,32 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,55 @@
|
|||||||
|
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
id "org.springframework.boot" version "3.3.0-SNAPSHOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "io.spring.dependency-management"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
app {
|
||||||
|
extendsFrom(configurations.runtimeClasspath)
|
||||||
|
}
|
||||||
|
netty {
|
||||||
|
extendsFrom(app)
|
||||||
|
}
|
||||||
|
tomcat {
|
||||||
|
extendsFrom(app)
|
||||||
|
}
|
||||||
|
undertow {
|
||||||
|
extendsFrom(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("org.springframework:spring-webflux")
|
||||||
|
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
|
|
||||||
|
app(files(sourceSets.main.output))
|
||||||
|
app("org.springframework:spring-webflux") {
|
||||||
|
exclude group: "spring-boot-project", module: "spring-boot-starter-reactor-netty"
|
||||||
|
}
|
||||||
|
netty("org.springframework.boot:spring-boot-starter-webflux")
|
||||||
|
tomcat("org.springframework.boot:spring-boot-starter-tomcat")
|
||||||
|
undertow("org.springframework.boot:spring-boot-starter-undertow")
|
||||||
|
}
|
||||||
|
|
||||||
|
["netty", "tomcat", "undertow"].each { webServer ->
|
||||||
|
def configurer = { task ->
|
||||||
|
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
|
||||||
|
task.classpath = configurations.getByName(webServer)
|
||||||
|
task.archiveClassifier = webServer
|
||||||
|
task.targetJavaVersion = project.getTargetCompatibility()
|
||||||
|
}
|
||||||
|
tasks.register("${webServer}ServerApp", BootJar, configurer)
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
resolutionStrategy {
|
||||||
|
eachPlugin {
|
||||||
|
if (requested.id.id == "org.springframework.boot") {
|
||||||
|
useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class HelloController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String hello(ServerHttpRequest request) {
|
||||||
|
return "Hello from " + request.getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.JarURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SniServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(SniServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEZjCCAk6gAwIBAgIUOLEdvpEkuAYUivOxM2sfYLdaHL4wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
|
||||||
|
aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDIyNjIwMTIxMFoXDTI1MDIyNTIwMTIxMFow
|
||||||
|
OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVaGVsbG8tYWx0
|
||||||
|
LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hde
|
||||||
|
3zmOM539oWpp1KNNmGSSqZaYed3qAbrZK8fk4TjN9U+2Yv6glo4XDCRkY61PNmQF
|
||||||
|
l2+pwnPvscu6CIf+S8DzXa3Y38u0e14RdhfdeJyluul2vJME16S7psCmajqWHAAT
|
||||||
|
Y/sl1Yjt/5AsEaqjGIDGXDyUzEGrtxZUZmptLpl736Y262JZOmoPBQRhEHudQss6
|
||||||
|
utUqYbqeCO/rSIUoIAFtQfm4vJqDq0Jy+VAL9Emzn5SGjR7BZF266Xs0xsGTsBJM
|
||||||
|
rWCat6j2kJEsYo6IsC0JrFFdv6S8Orp5gBV06gHBlhjIiuvpCi9n9vxN19S+xJgj
|
||||||
|
YFJORxRnbNchkAwZeQIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEB
|
||||||
|
BAQDAgZAMB0GA1UdDgQWBBTrYnvysRCkQ0NwIw+GCkEFQga7FTAfBgNVHSMEGDAW
|
||||||
|
gBTHEQ3eDaELFTnoniIdtWY6pVrH1TANBgkqhkiG9w0BAQsFAAOCAgEAT8L/5jqb
|
||||||
|
64MkeLJQbyXjXpHIaP9dr4NHBeTjXPx8FhuI9RG9ywNzuSlH0DeCQbcdPuMAmrDa
|
||||||
|
oWoaYzHpuG2saHcCeBdE0HtVCofvKYVQK7vUsT+tKqNbkGzt0cRdwmciDUh6O+Ub
|
||||||
|
7iOSd7wIjeobEoOxBB+Rk6mRpXRUENAPKMiAuDvpRDFaFyGedzxIeaRp5Gi+LEBh
|
||||||
|
/2cRZkWcrcWuKNAIszvdZXIl8gUwPU4KVkqX/pALLK5Ax87uniEjl77BQSeuC4++
|
||||||
|
QYSnj76VRoyIqt9ZsjVG6VCOwze6M9nVmTR9mgADFsZFZPiqp73q8mOCEpbPfovi
|
||||||
|
rV8PFZzWZao4s4ulSA3GFlRxaBHe2AcR2pJSchG14OGjW8KWbsOEqLqy+f88ECEP
|
||||||
|
o9jjc6fq0QeznTZlRIw47UT2ykS1qV6xiKN67iMylCla8ogwK3HvF8pCME7LK99q
|
||||||
|
qslcLxj2DJQDHTqtDL2fOlxO0RGGuGg4jMfdBwHMjd30nHff5K5UZQARCFKTdbTZ
|
||||||
|
V5OOdJbDRcicXBQE7sbaTqztjjGKiRxCdvgcV760KJH+gYkzMikqC/xUIv7ac4qX
|
||||||
|
Q1FHQl+tsxZmM2nP7EapMYZqLle7go8Govze6r0pFEF6MT47ST7m0/ZBWKxm0CaU
|
||||||
|
uWpyhqoylaIqbR9rojPDSPDvslZvI4KGY8M=
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUd17fOY4znf2h
|
||||||
|
amnUo02YZJKplph53eoButkrx+ThOM31T7Zi/qCWjhcMJGRjrU82ZAWXb6nCc++x
|
||||||
|
y7oIh/5LwPNdrdjfy7R7XhF2F914nKW66Xa8kwTXpLumwKZqOpYcABNj+yXViO3/
|
||||||
|
kCwRqqMYgMZcPJTMQau3FlRmam0umXvfpjbrYlk6ag8FBGEQe51Cyzq61Sphup4I
|
||||||
|
7+tIhSggAW1B+bi8moOrQnL5UAv0SbOflIaNHsFkXbrpezTGwZOwEkytYJq3qPaQ
|
||||||
|
kSxijoiwLQmsUV2/pLw6unmAFXTqAcGWGMiK6+kKL2f2/E3X1L7EmCNgUk5HFGds
|
||||||
|
1yGQDBl5AgMBAAECggEAA/oNVzWck8Vr7zmEAZbiPO1PpsegKFJ0WX6bJ1ahJ2te
|
||||||
|
Gi6ucJaTgD3omzGTL3UZpnWjenzR5fHa6q5a17iz6cxoFw4fS7u5r8AdqOLezQMh
|
||||||
|
lv4HsD/ljKO+ChZRBxam+J8yaGIAXfQngEESkhdqNhXdoJxXAfu4sGnv6nrTewEb
|
||||||
|
sNTgzBceCotwVPBBnN+Ggx2LGp3FWnKqDeaLJM6+vBMDpJYm4BSU21H46bUu9dMm
|
||||||
|
yM+msZeBZMzAWBVOKoHJxoEE0I6EpwTIPuuq2oqjzQCzzAsKTTFhb51kwMj6k1o4
|
||||||
|
Zc11IlDqjNCdB61R+7dqE4LoZWOa/cu26m+lMaZcgQKBgQD4q8zoKLSZ2jlpt1qi
|
||||||
|
URhupUOyf2BcP8fjygItlMGi3BHY8Rq6DLvoxUMFk0IJAKTolzoVDGxqbrc0Im6k
|
||||||
|
jed8VQ7SX8soN0aoEyKulv7jCXCRAzoZ2a0oh9cwNLmoEzIoYYSvPSe47HJb1B+T
|
||||||
|
aN7qQ6h+k0Q0XObgUK7D6WzyswKBgQDaumiM44gECZ7LEW+c16MIOIJWqyJyQsd+
|
||||||
|
Bgnn2uHyT9B5ITExsws1oco7GabqZLvC+IpoVsbdS73wnuneLJ40ykqTkwKNLAzB
|
||||||
|
eUabqiLqLZZ2ACajA86uND7B4hfCeW1srcqU5hqL2/cY4CVJPkBO4mZ6//n8ruZs
|
||||||
|
OfQhRrbpIwKBgQCPKUlEdvrSgGIBTL/vJsTsHlUFFHQDZ+zKZWgvma6I9i2IOfZr
|
||||||
|
Gh2serSFJywjRq2qAjY8G/TmqWrrps8QCWo1mDp6PxAUzQ3ugWW8Ic4II00dD0CJ
|
||||||
|
1VntNZdbd19TNgnwWYQr5wdRXT7RQyQSl5OORvlgNaRUiQ+aIJkczOweJQKBgQCj
|
||||||
|
yjtIZYoRG/MhNalS1ddr7IUNyZE95uvkXzlDuhDAlywRyN1BzkVyn/kEUK1BkLVZ
|
||||||
|
xyw9/d1lEbbmXNncWaUO+vzljYy3kmjq6JoLL1h97C1jp7FHGS7IHK9yGJCaPLvI
|
||||||
|
SkwNPFJcsRdUNWU2d7tIVxlOuijFI2PBX5SE5qNJ6QKBgBex5fpTZ2y4Zn4mcF8s
|
||||||
|
qjWTAJ8L5xD84PWhZAgbxrawiS+z7S0vMcfxF154JTP89C0EtnMeygE9jFJjvE9E
|
||||||
|
TAmM2UlIp7p8BKlr15GZjEBn3DqtwV49zo1pLcTjujBCDl+tfR7EVD0VrNb5fely
|
||||||
|
iM4vJ89vAebkd7G7ro1nbBF6
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1,42 @@
|
|||||||
|
spring:
|
||||||
|
ssl:
|
||||||
|
bundle:
|
||||||
|
pem:
|
||||||
|
default:
|
||||||
|
keystore:
|
||||||
|
certificate: "classpath:default/test-hello-server.crt"
|
||||||
|
private-key: "classpath:default/test-hello-server.key"
|
||||||
|
truststore:
|
||||||
|
certificate: "classpath:ca/test-ca.crt"
|
||||||
|
alt:
|
||||||
|
keystore:
|
||||||
|
certificate: "classpath:alt/test-hello-alt-server.crt"
|
||||||
|
private-key: "classpath:alt/test-hello-alt-server.key"
|
||||||
|
truststore:
|
||||||
|
certificate: "classpath:ca/test-ca.crt"
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8443
|
||||||
|
ssl:
|
||||||
|
bundle: "default"
|
||||||
|
server-name-bundles:
|
||||||
|
- server-name: "hello.example.com"
|
||||||
|
bundle: "default"
|
||||||
|
- server-name: "hello-alt.example.com"
|
||||||
|
bundle: "alt"
|
||||||
|
|
||||||
|
management:
|
||||||
|
server:
|
||||||
|
port: 8444
|
||||||
|
ssl:
|
||||||
|
bundle: "default"
|
||||||
|
server-name-bundles:
|
||||||
|
- server-name: "hello.example.com"
|
||||||
|
bundle: "default"
|
||||||
|
- server-name: "hello-alt.example.com"
|
||||||
|
bundle: "alt"
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include:
|
||||||
|
- "*"
|
@ -0,0 +1,32 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEYjCCAkqgAwIBAgIUOLEdvpEkuAYUivOxM2sfYLdaHL0wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
|
||||||
|
aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDIyNjIwMTIxMFoXDTI1MDIyNTIwMTIxMFow
|
||||||
|
NzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEaMBgGA1UEAwwRaGVsbG8uZXhh
|
||||||
|
bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+mTU1h68x
|
||||||
|
R6T4cmRFhzw7Uop3V4gLY//8n4I+6qrLjZx4ZCIA/QHZ/80DzSFJlE5P1TP1k7Wc
|
||||||
|
+MCI5KYjMzAZoR9mBfH6MUArDvPU7eZdxT3QQUMHfpAuLhPhwxsgLlogl5vq74t0
|
||||||
|
WV+eipRJIEi7lqjNk1EuO3Nli5Wp6/tRv6v33stUwpWIWnSuxu6r+gS5xp1mpRgD
|
||||||
|
KBi950ZSUJx+0QcwOs52n9iZTruCxunEDVggj/xUsbdBBCT30bmqS681d7TZTj1R
|
||||||
|
6Y9PlMiJ4Do5SSs48hZic8ZVsc6TWTVntxUWZotd5NCTk7R4CwL/XdpphuL8mnA0
|
||||||
|
eblUXyTkc4xPAgMBAAGjYjBgMAsGA1UdDwQEAwIFoDARBglghkgBhvhCAQEEBAMC
|
||||||
|
BkAwHQYDVR0OBBYEFArqIg3CQz0XayzQ3mO9Ikl1bmsgMB8GA1UdIwQYMBaAFMcR
|
||||||
|
Dd4NoQsVOeieIh21ZjqlWsfVMA0GCSqGSIb3DQEBCwUAA4ICAQBJY1n6gOglZGzJ
|
||||||
|
rVDni0RYWB7xYoBEwQG0UhK6ThZe7bqtnaYA5LL8l7jjtIwSE0fVtkGHe0OaBIRQ
|
||||||
|
Ntn1cxu15JRdwIUyn0S+ypq3IGPFX5vKaNL1wd7RL227EjtXOnH7ByQ182aNrkp9
|
||||||
|
p2nwMiOKhFbK1qsz4zohdcdQq7sYQUAKABneGumk0c6UkmnYKHGkzK0pxrEU2qBN
|
||||||
|
xc9/NqmBlWyefvcdjAXWB7DBH+KScc7tatkZsOvwlGrWMxyLPDR1WzMZXbHwxPt6
|
||||||
|
bHE5fdOPnkel1DJMjjn2/dphTBWOZBrFWuIeEWCzR1sKWgxRUzIzgA3hXKC29p5f
|
||||||
|
Y1BaXtzrFFD/mP9p7h8VYDPUSuNzDO9iqPf60qL+WN3cbkabJtrhBqzWSbPlzMOH
|
||||||
|
5ysn7RbVsvwD+OjmXuNhlHq65uf3ftAxCFCsiaFvfaRV9PYdYZP5JAHLZVPFhRhO
|
||||||
|
qg0KkPKTFrOwBw1w+mDf2ZQ/d/9Hd9PnknutmkBPwaqb0dRZcCUFLy/gPMrLkQse
|
||||||
|
P3naqOpHGFPLbLIKbQ0vyTT/6I0/lglZJ2ijk52a2gbz2BMjL+5b+A6MP2GGy166
|
||||||
|
HxMKRbSQ3FgGGVuJd1ggUX8/juuP1Ykw58yS0OmfT2ryHLxfqF0Pr7uswNnPyGjZ
|
||||||
|
K/2fVlQBLbIfMjtmuoFvO01llElZHw==
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+mTU1h68xR6T4
|
||||||
|
cmRFhzw7Uop3V4gLY//8n4I+6qrLjZx4ZCIA/QHZ/80DzSFJlE5P1TP1k7Wc+MCI
|
||||||
|
5KYjMzAZoR9mBfH6MUArDvPU7eZdxT3QQUMHfpAuLhPhwxsgLlogl5vq74t0WV+e
|
||||||
|
ipRJIEi7lqjNk1EuO3Nli5Wp6/tRv6v33stUwpWIWnSuxu6r+gS5xp1mpRgDKBi9
|
||||||
|
50ZSUJx+0QcwOs52n9iZTruCxunEDVggj/xUsbdBBCT30bmqS681d7TZTj1R6Y9P
|
||||||
|
lMiJ4Do5SSs48hZic8ZVsc6TWTVntxUWZotd5NCTk7R4CwL/XdpphuL8mnA0eblU
|
||||||
|
XyTkc4xPAgMBAAECggEAJ/SBGYQknz2IIUcFqyei4kK24Ta5v72KW8BqctsJy9sX
|
||||||
|
WouPL0ramQMNTMczO7P5yLWGi2wYDdx9rBTWmRlxc2X56Y7Ef7DUZVJgnhnzCWRA
|
||||||
|
RYhwz0DiY7PoGhMm/BOLdDqkBleKEe1sZJVjaYL5jE2UfGfuBDWVRsvAp5rfF+8r
|
||||||
|
gp5ewvaZUeFC09/aN+1k84wxA0njAmjiYDSId5Ymn0AxHDeoESmysMfr48p39/XH
|
||||||
|
5YiPne3OlrkEFlN1NYvPGf09ifyQYH91HuTrzKTX+jhvYM7E+LzSTrbcwlMUKDem
|
||||||
|
5CnVAkAJwnMhQDAOwAn52vPEqcru9d7jq+nkf+LQ8QKBgQD50mr9ApGHLN5uZ1Zk
|
||||||
|
1BRkTzAL4+yDYq0V5TiMaN7WNGTmIEUjEvnmaegxw37lBmmGhn6EPzSN/ZszgQIj
|
||||||
|
EQohxfU8lQ8ftBdnhmWTSs2Z57fHXItbvvKVxVRHw9oX+ZVj3akqKD9K6UAqWJlJ
|
||||||
|
+L4H+yHMIByS0ICnZcxhnXpSHwKBgQDDT9sRM2SCCULvpVTwqye7b7gW5Dlcv300
|
||||||
|
X5CS1av7id1QMFcU3nohp7KzTkxRbeuGL2QF7wVa5NBMh7+GevnRrRtEImfcaA5n
|
||||||
|
zhDQDfOUg0/5csOCvL0WTIkHEpTEiqd2SVaaGzHvy09mLCDJxDpc9LuTZiDP9/JL
|
||||||
|
vzIcb0Nf0QKBgHnk0nEbFLjZCrrhzwSpej2raa0Ti+5bckqxqlLQRJJNxEGI01MW
|
||||||
|
yjpDyJinY74Jz+lkrEyIrnLtoBGUS9+iS8hI16y0qkl0zMqlh+BDamhC6Kfsns6o
|
||||||
|
L6MmQkY16K80B1FP8V9xfdhmUPmYe0rdhJNOVKJNtMNp2qxS/lNOzEVPAoGACmps
|
||||||
|
zVsHRiQGTM9tWzRVdxp7H8VmBbs0iyF5jUsV0+FDSy54xmUi8D6IOiW3zjPldo96
|
||||||
|
bxKTH4jKTvqCTUKrpfHsXVLUZR2rfv+vR9kmn0ntbuke4g78qn7EY/sqsdyPF7DL
|
||||||
|
jIZcwGQARPufeAMd9a0bf73XjB+17TIyEvAgELECgYBUyPSNDHl0S02Lmc9SJ24S
|
||||||
|
TH58t04sRf4cN7ZQdAgPutmnMVSFC6HCrxlAZ94vsG2wEsp9bwrt0bVj15WQFkWj
|
||||||
|
6aN3uwLNTB0MDkyC9R9DS9Rr2NUMRCJbCXtR4V2E8ueFkEltshTjrccgZPx2fpl3
|
||||||
|
2/tjshevKDJFL+y3pShqKg==
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1,52 @@
|
|||||||
|
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
id "org.springframework.boot" version "3.3.0-SNAPSHOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "io.spring.dependency-management"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
app {
|
||||||
|
extendsFrom(configurations.runtimeClasspath)
|
||||||
|
}
|
||||||
|
tomcat {
|
||||||
|
extendsFrom(app)
|
||||||
|
}
|
||||||
|
undertow {
|
||||||
|
extendsFrom(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web") {
|
||||||
|
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||||
|
}
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
|
|
||||||
|
app(files(sourceSets.main.output))
|
||||||
|
app('org.springframework.boot:spring-boot-starter-web') {
|
||||||
|
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||||
|
}
|
||||||
|
tomcat("org.springframework.boot:spring-boot-starter-tomcat")
|
||||||
|
undertow("org.springframework.boot:spring-boot-starter-undertow")
|
||||||
|
}
|
||||||
|
|
||||||
|
["tomcat", "undertow"].each { webServer ->
|
||||||
|
def configurer = { task ->
|
||||||
|
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
|
||||||
|
task.classpath = configurations.getByName(webServer)
|
||||||
|
task.archiveClassifier = webServer
|
||||||
|
task.targetJavaVersion = project.getTargetCompatibility()
|
||||||
|
}
|
||||||
|
tasks.register("${webServer}ServerApp", BootJar, configurer)
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { url "file:${rootDir}/../int-test-maven-repository"}
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
}
|
||||||
|
resolutionStrategy {
|
||||||
|
eachPlugin {
|
||||||
|
if (requested.id.id == "org.springframework.boot") {
|
||||||
|
useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class HelloController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String hello(HttpServletRequest request) {
|
||||||
|
return "Hello from " + request.getRequestURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.JarURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SniServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(SniServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEZjCCAk6gAwIBAgIUOLEdvpEkuAYUivOxM2sfYLdaHL4wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
|
||||||
|
aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDIyNjIwMTIxMFoXDTI1MDIyNTIwMTIxMFow
|
||||||
|
OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVaGVsbG8tYWx0
|
||||||
|
LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hde
|
||||||
|
3zmOM539oWpp1KNNmGSSqZaYed3qAbrZK8fk4TjN9U+2Yv6glo4XDCRkY61PNmQF
|
||||||
|
l2+pwnPvscu6CIf+S8DzXa3Y38u0e14RdhfdeJyluul2vJME16S7psCmajqWHAAT
|
||||||
|
Y/sl1Yjt/5AsEaqjGIDGXDyUzEGrtxZUZmptLpl736Y262JZOmoPBQRhEHudQss6
|
||||||
|
utUqYbqeCO/rSIUoIAFtQfm4vJqDq0Jy+VAL9Emzn5SGjR7BZF266Xs0xsGTsBJM
|
||||||
|
rWCat6j2kJEsYo6IsC0JrFFdv6S8Orp5gBV06gHBlhjIiuvpCi9n9vxN19S+xJgj
|
||||||
|
YFJORxRnbNchkAwZeQIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEB
|
||||||
|
BAQDAgZAMB0GA1UdDgQWBBTrYnvysRCkQ0NwIw+GCkEFQga7FTAfBgNVHSMEGDAW
|
||||||
|
gBTHEQ3eDaELFTnoniIdtWY6pVrH1TANBgkqhkiG9w0BAQsFAAOCAgEAT8L/5jqb
|
||||||
|
64MkeLJQbyXjXpHIaP9dr4NHBeTjXPx8FhuI9RG9ywNzuSlH0DeCQbcdPuMAmrDa
|
||||||
|
oWoaYzHpuG2saHcCeBdE0HtVCofvKYVQK7vUsT+tKqNbkGzt0cRdwmciDUh6O+Ub
|
||||||
|
7iOSd7wIjeobEoOxBB+Rk6mRpXRUENAPKMiAuDvpRDFaFyGedzxIeaRp5Gi+LEBh
|
||||||
|
/2cRZkWcrcWuKNAIszvdZXIl8gUwPU4KVkqX/pALLK5Ax87uniEjl77BQSeuC4++
|
||||||
|
QYSnj76VRoyIqt9ZsjVG6VCOwze6M9nVmTR9mgADFsZFZPiqp73q8mOCEpbPfovi
|
||||||
|
rV8PFZzWZao4s4ulSA3GFlRxaBHe2AcR2pJSchG14OGjW8KWbsOEqLqy+f88ECEP
|
||||||
|
o9jjc6fq0QeznTZlRIw47UT2ykS1qV6xiKN67iMylCla8ogwK3HvF8pCME7LK99q
|
||||||
|
qslcLxj2DJQDHTqtDL2fOlxO0RGGuGg4jMfdBwHMjd30nHff5K5UZQARCFKTdbTZ
|
||||||
|
V5OOdJbDRcicXBQE7sbaTqztjjGKiRxCdvgcV760KJH+gYkzMikqC/xUIv7ac4qX
|
||||||
|
Q1FHQl+tsxZmM2nP7EapMYZqLle7go8Govze6r0pFEF6MT47ST7m0/ZBWKxm0CaU
|
||||||
|
uWpyhqoylaIqbR9rojPDSPDvslZvI4KGY8M=
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUd17fOY4znf2h
|
||||||
|
amnUo02YZJKplph53eoButkrx+ThOM31T7Zi/qCWjhcMJGRjrU82ZAWXb6nCc++x
|
||||||
|
y7oIh/5LwPNdrdjfy7R7XhF2F914nKW66Xa8kwTXpLumwKZqOpYcABNj+yXViO3/
|
||||||
|
kCwRqqMYgMZcPJTMQau3FlRmam0umXvfpjbrYlk6ag8FBGEQe51Cyzq61Sphup4I
|
||||||
|
7+tIhSggAW1B+bi8moOrQnL5UAv0SbOflIaNHsFkXbrpezTGwZOwEkytYJq3qPaQ
|
||||||
|
kSxijoiwLQmsUV2/pLw6unmAFXTqAcGWGMiK6+kKL2f2/E3X1L7EmCNgUk5HFGds
|
||||||
|
1yGQDBl5AgMBAAECggEAA/oNVzWck8Vr7zmEAZbiPO1PpsegKFJ0WX6bJ1ahJ2te
|
||||||
|
Gi6ucJaTgD3omzGTL3UZpnWjenzR5fHa6q5a17iz6cxoFw4fS7u5r8AdqOLezQMh
|
||||||
|
lv4HsD/ljKO+ChZRBxam+J8yaGIAXfQngEESkhdqNhXdoJxXAfu4sGnv6nrTewEb
|
||||||
|
sNTgzBceCotwVPBBnN+Ggx2LGp3FWnKqDeaLJM6+vBMDpJYm4BSU21H46bUu9dMm
|
||||||
|
yM+msZeBZMzAWBVOKoHJxoEE0I6EpwTIPuuq2oqjzQCzzAsKTTFhb51kwMj6k1o4
|
||||||
|
Zc11IlDqjNCdB61R+7dqE4LoZWOa/cu26m+lMaZcgQKBgQD4q8zoKLSZ2jlpt1qi
|
||||||
|
URhupUOyf2BcP8fjygItlMGi3BHY8Rq6DLvoxUMFk0IJAKTolzoVDGxqbrc0Im6k
|
||||||
|
jed8VQ7SX8soN0aoEyKulv7jCXCRAzoZ2a0oh9cwNLmoEzIoYYSvPSe47HJb1B+T
|
||||||
|
aN7qQ6h+k0Q0XObgUK7D6WzyswKBgQDaumiM44gECZ7LEW+c16MIOIJWqyJyQsd+
|
||||||
|
Bgnn2uHyT9B5ITExsws1oco7GabqZLvC+IpoVsbdS73wnuneLJ40ykqTkwKNLAzB
|
||||||
|
eUabqiLqLZZ2ACajA86uND7B4hfCeW1srcqU5hqL2/cY4CVJPkBO4mZ6//n8ruZs
|
||||||
|
OfQhRrbpIwKBgQCPKUlEdvrSgGIBTL/vJsTsHlUFFHQDZ+zKZWgvma6I9i2IOfZr
|
||||||
|
Gh2serSFJywjRq2qAjY8G/TmqWrrps8QCWo1mDp6PxAUzQ3ugWW8Ic4II00dD0CJ
|
||||||
|
1VntNZdbd19TNgnwWYQr5wdRXT7RQyQSl5OORvlgNaRUiQ+aIJkczOweJQKBgQCj
|
||||||
|
yjtIZYoRG/MhNalS1ddr7IUNyZE95uvkXzlDuhDAlywRyN1BzkVyn/kEUK1BkLVZ
|
||||||
|
xyw9/d1lEbbmXNncWaUO+vzljYy3kmjq6JoLL1h97C1jp7FHGS7IHK9yGJCaPLvI
|
||||||
|
SkwNPFJcsRdUNWU2d7tIVxlOuijFI2PBX5SE5qNJ6QKBgBex5fpTZ2y4Zn4mcF8s
|
||||||
|
qjWTAJ8L5xD84PWhZAgbxrawiS+z7S0vMcfxF154JTP89C0EtnMeygE9jFJjvE9E
|
||||||
|
TAmM2UlIp7p8BKlr15GZjEBn3DqtwV49zo1pLcTjujBCDl+tfR7EVD0VrNb5fely
|
||||||
|
iM4vJ89vAebkd7G7ro1nbBF6
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1,42 @@
|
|||||||
|
spring:
|
||||||
|
ssl:
|
||||||
|
bundle:
|
||||||
|
pem:
|
||||||
|
default:
|
||||||
|
keystore:
|
||||||
|
certificate: "classpath:default/test-hello-server.crt"
|
||||||
|
private-key: "classpath:default/test-hello-server.key"
|
||||||
|
truststore:
|
||||||
|
certificate: "classpath:ca/test-ca.crt"
|
||||||
|
alt:
|
||||||
|
keystore:
|
||||||
|
certificate: "classpath:alt/test-hello-alt-server.crt"
|
||||||
|
private-key: "classpath:alt/test-hello-alt-server.key"
|
||||||
|
truststore:
|
||||||
|
certificate: "classpath:ca/test-ca.crt"
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8443
|
||||||
|
ssl:
|
||||||
|
bundle: "default"
|
||||||
|
server-name-bundles:
|
||||||
|
- server-name: "hello.example.com"
|
||||||
|
bundle: "default"
|
||||||
|
- server-name: "hello-alt.example.com"
|
||||||
|
bundle: "alt"
|
||||||
|
|
||||||
|
management:
|
||||||
|
server:
|
||||||
|
port: 8444
|
||||||
|
ssl:
|
||||||
|
bundle: "default"
|
||||||
|
server-name-bundles:
|
||||||
|
- server-name: "hello.example.com"
|
||||||
|
bundle: "default"
|
||||||
|
- server-name: "hello-alt.example.com"
|
||||||
|
bundle: "alt"
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include:
|
||||||
|
- "*"
|
@ -0,0 +1,32 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEYjCCAkqgAwIBAgIUOLEdvpEkuAYUivOxM2sfYLdaHL0wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
|
||||||
|
aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDIyNjIwMTIxMFoXDTI1MDIyNTIwMTIxMFow
|
||||||
|
NzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEaMBgGA1UEAwwRaGVsbG8uZXhh
|
||||||
|
bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+mTU1h68x
|
||||||
|
R6T4cmRFhzw7Uop3V4gLY//8n4I+6qrLjZx4ZCIA/QHZ/80DzSFJlE5P1TP1k7Wc
|
||||||
|
+MCI5KYjMzAZoR9mBfH6MUArDvPU7eZdxT3QQUMHfpAuLhPhwxsgLlogl5vq74t0
|
||||||
|
WV+eipRJIEi7lqjNk1EuO3Nli5Wp6/tRv6v33stUwpWIWnSuxu6r+gS5xp1mpRgD
|
||||||
|
KBi950ZSUJx+0QcwOs52n9iZTruCxunEDVggj/xUsbdBBCT30bmqS681d7TZTj1R
|
||||||
|
6Y9PlMiJ4Do5SSs48hZic8ZVsc6TWTVntxUWZotd5NCTk7R4CwL/XdpphuL8mnA0
|
||||||
|
eblUXyTkc4xPAgMBAAGjYjBgMAsGA1UdDwQEAwIFoDARBglghkgBhvhCAQEEBAMC
|
||||||
|
BkAwHQYDVR0OBBYEFArqIg3CQz0XayzQ3mO9Ikl1bmsgMB8GA1UdIwQYMBaAFMcR
|
||||||
|
Dd4NoQsVOeieIh21ZjqlWsfVMA0GCSqGSIb3DQEBCwUAA4ICAQBJY1n6gOglZGzJ
|
||||||
|
rVDni0RYWB7xYoBEwQG0UhK6ThZe7bqtnaYA5LL8l7jjtIwSE0fVtkGHe0OaBIRQ
|
||||||
|
Ntn1cxu15JRdwIUyn0S+ypq3IGPFX5vKaNL1wd7RL227EjtXOnH7ByQ182aNrkp9
|
||||||
|
p2nwMiOKhFbK1qsz4zohdcdQq7sYQUAKABneGumk0c6UkmnYKHGkzK0pxrEU2qBN
|
||||||
|
xc9/NqmBlWyefvcdjAXWB7DBH+KScc7tatkZsOvwlGrWMxyLPDR1WzMZXbHwxPt6
|
||||||
|
bHE5fdOPnkel1DJMjjn2/dphTBWOZBrFWuIeEWCzR1sKWgxRUzIzgA3hXKC29p5f
|
||||||
|
Y1BaXtzrFFD/mP9p7h8VYDPUSuNzDO9iqPf60qL+WN3cbkabJtrhBqzWSbPlzMOH
|
||||||
|
5ysn7RbVsvwD+OjmXuNhlHq65uf3ftAxCFCsiaFvfaRV9PYdYZP5JAHLZVPFhRhO
|
||||||
|
qg0KkPKTFrOwBw1w+mDf2ZQ/d/9Hd9PnknutmkBPwaqb0dRZcCUFLy/gPMrLkQse
|
||||||
|
P3naqOpHGFPLbLIKbQ0vyTT/6I0/lglZJ2ijk52a2gbz2BMjL+5b+A6MP2GGy166
|
||||||
|
HxMKRbSQ3FgGGVuJd1ggUX8/juuP1Ykw58yS0OmfT2ryHLxfqF0Pr7uswNnPyGjZ
|
||||||
|
K/2fVlQBLbIfMjtmuoFvO01llElZHw==
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+mTU1h68xR6T4
|
||||||
|
cmRFhzw7Uop3V4gLY//8n4I+6qrLjZx4ZCIA/QHZ/80DzSFJlE5P1TP1k7Wc+MCI
|
||||||
|
5KYjMzAZoR9mBfH6MUArDvPU7eZdxT3QQUMHfpAuLhPhwxsgLlogl5vq74t0WV+e
|
||||||
|
ipRJIEi7lqjNk1EuO3Nli5Wp6/tRv6v33stUwpWIWnSuxu6r+gS5xp1mpRgDKBi9
|
||||||
|
50ZSUJx+0QcwOs52n9iZTruCxunEDVggj/xUsbdBBCT30bmqS681d7TZTj1R6Y9P
|
||||||
|
lMiJ4Do5SSs48hZic8ZVsc6TWTVntxUWZotd5NCTk7R4CwL/XdpphuL8mnA0eblU
|
||||||
|
XyTkc4xPAgMBAAECggEAJ/SBGYQknz2IIUcFqyei4kK24Ta5v72KW8BqctsJy9sX
|
||||||
|
WouPL0ramQMNTMczO7P5yLWGi2wYDdx9rBTWmRlxc2X56Y7Ef7DUZVJgnhnzCWRA
|
||||||
|
RYhwz0DiY7PoGhMm/BOLdDqkBleKEe1sZJVjaYL5jE2UfGfuBDWVRsvAp5rfF+8r
|
||||||
|
gp5ewvaZUeFC09/aN+1k84wxA0njAmjiYDSId5Ymn0AxHDeoESmysMfr48p39/XH
|
||||||
|
5YiPne3OlrkEFlN1NYvPGf09ifyQYH91HuTrzKTX+jhvYM7E+LzSTrbcwlMUKDem
|
||||||
|
5CnVAkAJwnMhQDAOwAn52vPEqcru9d7jq+nkf+LQ8QKBgQD50mr9ApGHLN5uZ1Zk
|
||||||
|
1BRkTzAL4+yDYq0V5TiMaN7WNGTmIEUjEvnmaegxw37lBmmGhn6EPzSN/ZszgQIj
|
||||||
|
EQohxfU8lQ8ftBdnhmWTSs2Z57fHXItbvvKVxVRHw9oX+ZVj3akqKD9K6UAqWJlJ
|
||||||
|
+L4H+yHMIByS0ICnZcxhnXpSHwKBgQDDT9sRM2SCCULvpVTwqye7b7gW5Dlcv300
|
||||||
|
X5CS1av7id1QMFcU3nohp7KzTkxRbeuGL2QF7wVa5NBMh7+GevnRrRtEImfcaA5n
|
||||||
|
zhDQDfOUg0/5csOCvL0WTIkHEpTEiqd2SVaaGzHvy09mLCDJxDpc9LuTZiDP9/JL
|
||||||
|
vzIcb0Nf0QKBgHnk0nEbFLjZCrrhzwSpej2raa0Ti+5bckqxqlLQRJJNxEGI01MW
|
||||||
|
yjpDyJinY74Jz+lkrEyIrnLtoBGUS9+iS8hI16y0qkl0zMqlh+BDamhC6Kfsns6o
|
||||||
|
L6MmQkY16K80B1FP8V9xfdhmUPmYe0rdhJNOVKJNtMNp2qxS/lNOzEVPAoGACmps
|
||||||
|
zVsHRiQGTM9tWzRVdxp7H8VmBbs0iyF5jUsV0+FDSy54xmUi8D6IOiW3zjPldo96
|
||||||
|
bxKTH4jKTvqCTUKrpfHsXVLUZR2rfv+vR9kmn0ntbuke4g78qn7EY/sqsdyPF7DL
|
||||||
|
jIZcwGQARPufeAMd9a0bf73XjB+17TIyEvAgELECgYBUyPSNDHl0S02Lmc9SJ24S
|
||||||
|
TH58t04sRf4cN7ZQdAgPutmnMVSFC6HCrxlAZ94vsG2wEsp9bwrt0bVj15WQFkWj
|
||||||
|
6aN3uwLNTB0MDkyC9R9DS9Rr2NUMRCJbCXtR4V2E8ueFkEltshTjrccgZPx2fpl3
|
||||||
|
2/tjshevKDJFL+y3pShqKg==
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.awaitility.Awaitility;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.Network;
|
||||||
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for SSL configuration with SNI.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
@Testcontainers(disabledWithoutDocker = true)
|
||||||
|
class SniIntegrationTests {
|
||||||
|
|
||||||
|
private static final Map<String, String> SERVER_START_MESSAGES = Map.ofEntries(Map.entry("netty", "Netty started"),
|
||||||
|
Map.entry("tomcat", "Tomcat initialized"), Map.entry("undertow", "starting server: Undertow"));
|
||||||
|
|
||||||
|
public static final String PRIMARY_SERVER_NAME = "hello.example.com";
|
||||||
|
|
||||||
|
public static final String ALT_SERVER_NAME = "hello-alt.example.com";
|
||||||
|
|
||||||
|
private static final Integer SERVER_PORT = 8443;
|
||||||
|
|
||||||
|
private static final Network SHARED_NETWORK = Network.newNetwork();
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({ "reactive,netty", "reactive,tomcat", "servlet,tomcat", "reactive,undertow", "servlet,undertow" })
|
||||||
|
void home(String webStack, String server) {
|
||||||
|
try (ApplicationContainer serverContainer = new ServerApplicationContainer(webStack, server)) {
|
||||||
|
serverContainer.start();
|
||||||
|
Awaitility.await().atMost(Duration.ofSeconds(60)).until(serverContainer::isRunning);
|
||||||
|
String serverLogs = serverContainer.getLogs();
|
||||||
|
assertThat(serverLogs).contains(SERVER_START_MESSAGES.get(server));
|
||||||
|
try (ApplicationContainer clientContainer = new ClientApplicationContainer()) {
|
||||||
|
clientContainer.start();
|
||||||
|
Awaitility.await().atMost(Duration.ofSeconds(60)).until(() -> !clientContainer.isRunning());
|
||||||
|
String clientLogs = clientContainer.getLogs();
|
||||||
|
assertServerCalledWithName(clientLogs, PRIMARY_SERVER_NAME);
|
||||||
|
assertServerCalledWithName(clientLogs, ALT_SERVER_NAME);
|
||||||
|
clientContainer.stop();
|
||||||
|
}
|
||||||
|
serverContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertServerCalledWithName(String clientLogs, String serverName) {
|
||||||
|
assertThat(clientLogs).contains("Calling server at 'https://" + serverName + ":8443/'")
|
||||||
|
.contains("Hello from https://" + serverName + ":8443/");
|
||||||
|
assertThat(clientLogs).contains("Calling server actuator at 'https://" + serverName + ":8444/actuator/health'")
|
||||||
|
.contains("{\"status\":\"UP\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ClientApplicationContainer extends ApplicationContainer {
|
||||||
|
|
||||||
|
ClientApplicationContainer() {
|
||||||
|
super("spring-boot-sni-client-app", "", PRIMARY_SERVER_NAME, ALT_SERVER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ServerApplicationContainer extends ApplicationContainer {
|
||||||
|
|
||||||
|
ServerApplicationContainer(String webStack, String server) {
|
||||||
|
super("spring-boot-sni-" + webStack + "-app", "-" + server);
|
||||||
|
withNetworkAliases(PRIMARY_SERVER_NAME, ALT_SERVER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ApplicationContainer extends GenericContainer<ApplicationContainer> {
|
||||||
|
|
||||||
|
protected ApplicationContainer(String appName, String fileSuffix, String... entryPointArgs) {
|
||||||
|
super(new ImageFromDockerfile().withFileFromFile("spring-boot.jar", findJarFile(appName, fileSuffix))
|
||||||
|
.withDockerfileFromBuilder((builder) -> builder.from("eclipse-temurin:17-jre-jammy")
|
||||||
|
.add("spring-boot.jar", "/spring-boot.jar")
|
||||||
|
.entryPoint(buildEntryPoint(entryPointArgs))));
|
||||||
|
withExposedPorts(SERVER_PORT);
|
||||||
|
withStartupTimeout(Duration.ofMinutes(2));
|
||||||
|
withStartupAttempts(3);
|
||||||
|
withNetwork(SHARED_NETWORK);
|
||||||
|
withNetworkMode(SHARED_NETWORK.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File findJarFile(String appName, String fileSuffix) {
|
||||||
|
String path = String.format("build/%1$s/build/libs/%1$s%2$s.jar", appName, fileSuffix);
|
||||||
|
File jar = new File(path);
|
||||||
|
Assert.state(jar.isFile(), () -> "Could not find " + path);
|
||||||
|
return jar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildEntryPoint(String... args) {
|
||||||
|
StringBuilder builder = new StringBuilder().append("java").append(" -jar").append(" /spring-boot.jar");
|
||||||
|
for (String arg : args) {
|
||||||
|
builder.append(" ").append(arg);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user