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-classic-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-image-tests"
|
||||
|
||||
|
@ -2173,6 +2173,10 @@
|
||||
"description": "SSL protocol to use.",
|
||||
"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",
|
||||
"description": "Path to a PEM-encoded SSL certificate authority file."
|
||||
|
@ -337,6 +337,10 @@
|
||||
"description": "SSL protocol to use.",
|
||||
"defaultValue": "TLS"
|
||||
},
|
||||
{
|
||||
"name": "server.ssl.server-name-bundles",
|
||||
"description": "Mapping of host names to SSL bundles for SNI configuration."
|
||||
},
|
||||
{
|
||||
"name": "server.ssl.trust-certificate",
|
||||
"description": "Path to a PEM-encoded SSL certificate authority file."
|
||||
@ -2676,6 +2680,10 @@
|
||||
"description": "SSL protocol to use.",
|
||||
"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",
|
||||
"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
|
||||
|
||||
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:
|
||||
|
||||
[configprops,yaml]
|
||||
@ -193,6 +195,9 @@ server:
|
||||
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:
|
||||
|
||||
[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`.
|
||||
|
||||
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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.rsocket.SocketAcceptor;
|
||||
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.web.embedded.netty.SslServerCustomizer;
|
||||
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.http.client.ReactorResourceFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@ -181,7 +184,8 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -190,7 +194,8 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
||||
tcpServer = tcpServer.runOn(this.resourceFactory.getLoopResources());
|
||||
}
|
||||
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));
|
||||
}
|
||||
@ -199,6 +204,13 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
||||
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() {
|
||||
if (this.address != null) {
|
||||
return new InetSocketAddress(this.address.getHostAddress(), this.port);
|
||||
@ -211,8 +223,9 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
||||
|
||||
private final SslBundle sslBundle;
|
||||
|
||||
private TcpSslServerCustomizer(Ssl.ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
super(null, clientAuth, sslBundle);
|
||||
private TcpSslServerCustomizer(ClientAuth clientAuth, SslBundle sslBundle,
|
||||
Map<String, SslBundle> serverNameSslBundles) {
|
||||
super(null, clientAuth, sslBundle, serverNameSslBundles);
|
||||
this.sslBundle = sslBundle;
|
||||
}
|
||||
|
||||
|
@ -248,6 +248,9 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
* 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 Moritz Halbritter
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
|
||||
@ -172,14 +173,22 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
}
|
||||
|
||||
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(getHttp2(), getSsl().getClientAuth(), getSslBundle());
|
||||
String bundleName = getSsl().getBundle();
|
||||
if (StringUtils.hasText(bundleName)) {
|
||||
getSslBundles().addBundleUpdateHandler(bundleName, customizer::updateSslBundle);
|
||||
}
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(getHttp2(), getSsl().getClientAuth(), getSslBundle(),
|
||||
getServerNameSslBundles());
|
||||
addBundleUpdateHandler(null, getSsl().getBundle(), customizer);
|
||||
getSsl().getServerNameBundles()
|
||||
.forEach((serverNameSslBundle) -> addBundleUpdateHandler(serverNameSslBundle.serverName(),
|
||||
serverNameSslBundle.bundle(), customizer));
|
||||
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() {
|
||||
List<HttpProtocol> protocols = new ArrayList<>();
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.netty;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@ -54,13 +57,17 @@ public class SslServerCustomizer implements NettyServerCustomizer {
|
||||
|
||||
private volatile SslProvider sslProvider;
|
||||
|
||||
private final Map<String, SslProvider> serverNameSslProviders;
|
||||
|
||||
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.clientAuth = Ssl.ClientAuth.map(clientAuth, ClientAuth.NONE, ClientAuth.OPTIONAL, ClientAuth.REQUIRE);
|
||||
this.sslBundle = sslBundle;
|
||||
this.sslProvider = createSslProvider(sslBundle);
|
||||
this.serverNameSslProviders = createServerNameSslProviders(serverNameSslBundles);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -69,14 +76,29 @@ public class SslServerCustomizer implements NettyServerCustomizer {
|
||||
}
|
||||
|
||||
private void applySecurity(SslContextSpec spec) {
|
||||
spec.sslContext(this.sslProvider.getSslContext())
|
||||
.setSniAsyncMappings((domainName, promise) -> promise.setSuccess(this.sslProvider));
|
||||
spec.sslContext(this.sslProvider.getSslContext()).setSniAsyncMappings((domainName, promise) -> {
|
||||
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");
|
||||
this.sslBundle = sslBundle;
|
||||
this.sslProvider = createSslProvider(sslBundle);
|
||||
if (hostName == null) {
|
||||
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) {
|
||||
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.tomcat;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.coyote.ProtocolHandler;
|
||||
@ -56,36 +58,47 @@ class SslConnectorCustomizer {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
void update(SslBundle updatedSslBundle) {
|
||||
this.logger.debug("SSL Bundle has been updated, reloading SSL configuration");
|
||||
customize(updatedSslBundle);
|
||||
void update(String hostName, SslBundle updatedSslBundle) {
|
||||
AbstractHttp11JsseProtocol<?> protocol = (AbstractHttp11JsseProtocol<?>) this.connector.getProtocolHandler();
|
||||
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();
|
||||
Assert.state(handler instanceof AbstractHttp11JsseProtocol,
|
||||
"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.setSecure(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL.
|
||||
* @param sslBundle the SSL bundle
|
||||
* @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);
|
||||
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.setHostName(protocol.getDefaultSSLHostConfigName());
|
||||
sslHostConfig.setHostName(hostName);
|
||||
configureSslClientAuth(sslHostConfig);
|
||||
applySslBundle(sslBundle, protocol, sslHostConfig);
|
||||
applySslBundle(protocol, sslHostConfig, sslBundle);
|
||||
protocol.addSslHostConfig(sslHostConfig, true);
|
||||
}
|
||||
|
||||
private void applySslBundle(SslBundle sslBundle, AbstractHttp11JsseProtocol<?> protocol,
|
||||
SSLHostConfig sslHostConfig) {
|
||||
private void applySslBundle(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
|
||||
SslBundle sslBundle) {
|
||||
SslBundleKey key = sslBundle.getKey();
|
||||
SslStoreBundle stores = sslBundle.getStores();
|
||||
SslOptions options = sslBundle.getOptions();
|
||||
|
@ -61,6 +61,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Brian Clozel
|
||||
* @author HaiTao Zhang
|
||||
* @author Moritz Halbritter
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||
@ -237,10 +238,17 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
|
||||
private void customizeSsl(Connector connector) {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
||||
customizer.customize(getSslBundle());
|
||||
String sslBundleName = getSsl().getBundle();
|
||||
customizer.customize(getSslBundle(), getServerNameSslBundles());
|
||||
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)) {
|
||||
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 Dawid Antecki
|
||||
* @author Moritz Halbritter
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see #setPort(int)
|
||||
* @see #setContextLifecycleListeners(Collection)
|
||||
@ -379,10 +380,17 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
|
||||
private void customizeSsl(Connector connector) {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(logger, connector, getSsl().getClientAuth());
|
||||
customizer.customize(getSslBundle());
|
||||
String sslBundleName = getSsl().getBundle();
|
||||
customizer.customize(getSslBundle(), getServerNameSslBundles());
|
||||
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)) {
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,10 +17,13 @@
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.protocols.ssl.SNIContextMatcher;
|
||||
import io.undertow.protocols.ssl.SNISSLContext;
|
||||
import org.xnio.Options;
|
||||
import org.xnio.Sequence;
|
||||
import org.xnio.SslClientAuthMode;
|
||||
@ -47,18 +50,21 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
|
||||
|
||||
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.address = address;
|
||||
this.clientAuth = clientAuth;
|
||||
this.sslBundle = sslBundle;
|
||||
this.serverNameSslBundles = serverNameSslBundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Undertow.Builder builder) {
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
SSLContext sslContext = this.sslBundle.createSslContext();
|
||||
builder.addHttpsListener(this.port, getListenAddress(), sslContext);
|
||||
builder.addHttpsListener(this.port, getListenAddress(), createSslContext());
|
||||
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, ClientAuth.map(this.clientAuth,
|
||||
SslClientAuthMode.NOT_REQUESTED, SslClientAuthMode.REQUESTED, SslClientAuthMode.REQUIRED));
|
||||
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() {
|
||||
if (this.address == null) {
|
||||
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");
|
||||
* 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.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||
@ -137,7 +138,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
|
||||
@Override
|
||||
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,
|
||||
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
|
||||
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");
|
||||
* 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 Marcos Barbero
|
||||
* @author Eddú Meléndez
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see UndertowServletWebServer
|
||||
*/
|
||||
@ -295,7 +296,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
|
||||
@Override
|
||||
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);
|
||||
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");
|
||||
* 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.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -46,6 +47,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class UndertowWebServerFactoryDelegate {
|
||||
|
||||
@ -143,7 +145,8 @@ class UndertowWebServerFactoryDelegate {
|
||||
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();
|
||||
int port = factory.getPort();
|
||||
Builder builder = Undertow.builder();
|
||||
@ -165,7 +168,8 @@ class UndertowWebServerFactoryDelegate {
|
||||
}
|
||||
Ssl ssl = factory.getSsl();
|
||||
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);
|
||||
}
|
||||
else {
|
||||
|
@ -22,10 +22,13 @@ import java.net.InetAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.server.Ssl.ServerNameSslBundle;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -195,6 +198,13 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
|
||||
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.
|
||||
* @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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.web.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple server-independent abstraction for SSL configuration.
|
||||
*
|
||||
@ -67,6 +70,8 @@ public class Ssl {
|
||||
|
||||
private String protocol = "TLS";
|
||||
|
||||
private List<ServerNameSslBundle> serverNameBundles = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* @param bundle the name of the bundle
|
||||
@ -337,6 +354,9 @@ public class Ssl {
|
||||
return ssl;
|
||||
}
|
||||
|
||||
public record ServerNameSslBundle(String serverName, String bundle) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Client authentication types.
|
||||
*/
|
||||
|
@ -206,7 +206,8 @@ public final class WebServerSslBundle implements SslBundle {
|
||||
private final 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.trustStore = trustStore;
|
||||
this.keyStorePassword = keyStorePassword;
|
||||
|
@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
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.Shutdown;
|
||||
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.servlet.server.AbstractServletWebServerFactory;
|
||||
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.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
@ -283,6 +286,19 @@ class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryT
|
||||
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) {
|
||||
try {
|
||||
return connectionFactory.getSslContextFactory();
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.tomcat;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.commons.logging.Log;
|
||||
@ -76,7 +78,7 @@ class SslConnectorCustomizerTests {
|
||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl));
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
|
||||
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" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl));
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||
@ -107,7 +109,7 @@ class SslConnectorCustomizerTests {
|
||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl));
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||
@ -119,7 +121,7 @@ class SslConnectorCustomizerTests {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
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");
|
||||
}
|
||||
|
||||
@ -133,7 +135,7 @@ class SslConnectorCustomizerTests {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
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");
|
||||
}
|
||||
|
||||
@ -145,7 +147,8 @@ class SslConnectorCustomizerTests {
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
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-----
|
||||
MIIFjjCCA3agAwIBAgIUE7GfX9Wcx8X4/jDHboW5TUSvHMUwDQYJKoZIhvcNAQEL
|
||||
BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDIyNjIwMTIxMFoXDTI1MDIyNTIwMTIxMFow
|
||||
OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNh
|
||||
dGUgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkQq1
|
||||
UWWdc1BuD9sxI4XOrxY9JVITfuejj5hhWg9sjrvov24QjQoTtcOheNzck5MUFcS9
|
||||
9RGB4EJWQy17hA4c7XP3KPG9e9xIrZC11ANVUlFqJSSvv/z46KjnvUDgf0PuQKOG
|
||||
RTWwezNzl0CcJHuLwdJ3yVbtcNwuUh3ououvmmNVtj2dVij2QDYmfPeIwIaAmEhG
|
||||
t8Oe/dwII/vnVD7iDJ8IApxj8Etk5XZ3DOGTc0ky9h1suY+wQg6amgNEkvPFJQFm
|
||||
Ej5G920dq2ox/QD/DCMRxmUaqFs1ozqy3ZWDCB7IYYJ6yzE51GMc8JcsLR2vArqs
|
||||
UJGPAKobdUSkbO0aqQ36+sUc44PGVHtbslJuGyBKVXbJuImhemdGQsLaXeix2XfE
|
||||
jhFFHi/zsTRtkeiSPkFOdcYFx2j9ts7usdNHTHgNPC11QdFij5+aqmSv/UJw8vRT
|
||||
kosswUTPK0K5JRs/bx0KWPWyi34m/pCnzsFXALfxdfE9OIPemcmyFsHMQnCOevNu
|
||||
clf88GNF0w3HpU6MTL1bN3MHc7X/l90HV552GfUY0m4qravRgU3RzzTj9g6GydRz
|
||||
xFflEi34ELpzQ4FMHdxLAyMtb8mMqGGO8JF9j8vaIhFSn75KEl3qNn1wWhLqBTFY
|
||||
ptvE2HCY1gz4GIq1AkCjVmgHIPSt4SrjNsSiDjUCAwEAAaOBiTCBhjAdBgNVHQ4E
|
||||
FgQUxxEN3g2hCxU56J4iHbVmOqVax9UwHwYDVR0jBBgwFoAUxxEN3g2hCxU56J4i
|
||||
HbVmOqVax9UwDwYDVR0TAQH/BAUwAwEB/zAzBgNVHREELDAqghFoZWxsby5leGFt
|
||||
cGxlLmNvbYIVaGVsbG8tYWx0LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IC
|
||||
AQAWowMnIv6eUYLMsJ2tVNy6L2+Wj2Hw/mLb1JlaMRdhiHpHIzNFi+Z7Loq+PXfb
|
||||
R+He2SVbDieAaI5Uj1KiuH/j6DGNnZgoFKeu6qquuxvnURQATZhcRTq0Hpcuj/df
|
||||
mICXqPHWNSd1UhR/qwjiW5osqlxnDOEg5LIzxZ1cK62YLC/em3lwzX76dXMJyRA0
|
||||
mR32QrqZPeJAg/v5+L5WFJAj9BybcjRLKMzqYCCsVNYrYS4XI7Ms68R5Dyd/iQnY
|
||||
UTZChIjr8Yq5vgSK9+NPDh5naEJAlMtn9FQJPFeNW7T2y41uQf525+tXFeF73zWO
|
||||
NktUISrTcG1VmRP3UzWO9taoKDN/vp8iWJ5xNgTNO81NvlZXyjJ3snByVrsTECPv
|
||||
+MiQbv70aFZ5GE3VOLZVFQYyusRcPGvn46vkk/3wmuUySx/YwY0keg5BEW4JI8Jk
|
||||
Ia3PtzQDSu888Z4vIXjOCMPHnFk6BAs4Xy2S2q8+XUgrdhPaLl2Yi0IHljUq2Q1f
|
||||
4bQDJqiIVb3eIYoN/VrcD7v2uy5yKPZdiOLltZFMkb7mcKutYdLOGqAkDNsXaxr1
|
||||
zHByIlKXk4XWiPekrnh5tNqt1K0rPfvAmYBVTcGQXFcl8PBR6WEdvh0MldvYGpRV
|
||||
4jRtDYoYTtRRmVOKUQ9DLTWwv1CIDPe/OFsesQQlodiZhw==
|
||||
-----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