Retain SSL host config customizations during Tomcat initialization

Applying an SSL bundle to a Tomcat server replaced any `SSLHostConfig`
already registered on the connector for a host name, so an
`SSLHostConfig` could not be configured before Spring Boot applied a
bundle to it.

Reuse any existing `SSLHostConfig` during initial configuration, so an
`SSLHostConfig` registered on the connector before the bundle is
applied retains settings such as `trustManagerClassName`,
`truststoreProvider`, or `truststoreAlgorithm`.

Client authentication settings are only applied when creating a new
`SSLHostConfig`, so the `certificateVerification` of an existing
`SSLHostConfig` is retained rather than being overwritten with the
value derived from the `server.ssl.client-auth` property.

See gh-51466

Signed-off-by: Scott Frederick <scottyfred@gmail.com>
This commit is contained in:
Scott Frederick
2026-08-30 19:54:00 +02:00
committed by Stéphane Nicoll
parent 83ed4b5487
commit 4ca8304959
2 changed files with 45 additions and 10 deletions
@@ -68,13 +68,7 @@ public class SslConnectorCustomizer {
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) this.connector.getProtocolHandler();
String hostName = (serverName != null) ? serverName : protocol.getDefaultSSLHostConfigName();
this.logger.debug("SSL Bundle for host " + hostName + " has been updated, reloading SSL configuration");
SSLHostConfig sslHostConfig = findSslHostConfig(protocol, hostName);
if (sslHostConfig == null) {
addSslHostConfig(protocol, hostName, updatedSslBundle);
return;
}
applySslBundle(protocol, sslHostConfig, updatedSslBundle);
protocol.addSslHostConfig(sslHostConfig, true);
addSslHostConfig(protocol, hostName, updatedSslBundle);
}
public void customize(SslBundle sslBundle, Map<String, SslBundle> serverNameSslBundles) {
@@ -102,9 +96,12 @@ public class SslConnectorCustomizer {
}
private void addSslHostConfig(AbstractHttp11Protocol<?> protocol, String hostName, SslBundle sslBundle) {
SSLHostConfig sslHostConfig = new SSLHostConfig();
sslHostConfig.setHostName(hostName);
configureSslClientAuth(sslHostConfig);
SSLHostConfig sslHostConfig = findSslHostConfig(protocol, hostName);
if (sslHostConfig == null) {
sslHostConfig = new SSLHostConfig();
sslHostConfig.setHostName(hostName);
configureSslClientAuth(sslHostConfig);
}
applySslBundle(protocol, sslHostConfig, sslBundle);
protocol.addSslHostConfig(sslHostConfig, true);
}
@@ -24,6 +24,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfig.CertificateVerification;
import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -158,6 +159,26 @@ class SslConnectorCustomizerTests {
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2");
}
@Test
@WithPackageResources("test.jks")
void customizeRetainsCustomizationsAppliedToExistingSslHostConfig() {
Ssl ssl = new Ssl();
ssl.setKeyPassword("password");
ssl.setKeyStore("classpath:test.jks");
Connector connector = this.tomcat.getConnector();
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
SSLHostConfig existing = new SSLHostConfig();
existing.setHostName(protocol.getDefaultSSLHostConfigName());
existing.setTruststoreProvider(MockPkcs11SecurityProvider.NAME);
protocol.addSslHostConfig(existing);
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
assertThat(protocol.findSslHostConfigs()).hasSize(1);
SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0];
assertThat(sslHostConfig.getTruststoreProvider()).isEqualTo(MockPkcs11SecurityProvider.NAME);
assertThat(sslHostConfig.getCertificates()).hasSize(1);
}
@Test
@WithPackageResources("test.jks")
void updateRetainsCustomizationsAppliedToSslHostConfig() {
@@ -177,6 +198,23 @@ class SslConnectorCustomizerTests {
assertThat(updated.getCertificates()).hasSize(1);
}
@Test
@WithPackageResources("test.jks")
void updateRetainsClientAuthConfiguredOnSslHostConfig() {
Ssl ssl = new Ssl();
ssl.setKeyPassword("password");
ssl.setKeyStore("classpath:test.jks");
Connector connector = this.tomcat.getConnector();
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0];
sslHostConfig.setCertificateVerification("required");
customizer.update(null, WebServerSslBundle.get(ssl));
SSLHostConfig updated = protocol.findSslHostConfigs()[0];
assertThat(updated.getCertificateVerification()).isEqualTo(CertificateVerification.REQUIRED);
}
@Test
@WithPackageResources("test.jks")
void updateAppliesUpdatedBundleToExistingSslHostConfig() throws Exception {