Use context class loader when loading auto-configured SSL bundles
Update `SslAutoConfiguration` to the `ApplicationContext` class loader when loading SSL resources. Prior to this commit, the thread context class loader was used to load resources which could be incorrect. Specifically, when using a `ForkJoinPool` the thread context classloader defaults to the JRE `AppClassLoader` which does not include uber jar content. The underlying `JksSslStoreBundle` class and `PemSslStore.load(...)` method have been updated so support using a provided `ResourceLoader`. Fixes gh-42468
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.autoconfigure.ssl;
|
||||
|
||||
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
|
||||
import org.springframework.boot.io.ApplicationResourceLoader;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslManagerBundle;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.boot.ssl.jks.JksSslStoreDetails;
|
||||
import org.springframework.boot.ssl.pem.PemSslStore;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -97,18 +99,31 @@ public final class PropertiesSslBundle implements SslBundle {
|
||||
* @return an {@link SslBundle} instance
|
||||
*/
|
||||
public static SslBundle get(PemSslBundleProperties properties) {
|
||||
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore());
|
||||
return get(properties, new ApplicationResourceLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link SslBundle} for the given {@link PemSslBundleProperties}.
|
||||
* @param properties the source properties
|
||||
* @param resourceLoader the resource loader used to load content
|
||||
* @return an {@link SslBundle} instance
|
||||
* @since 3.3.5
|
||||
*/
|
||||
public static SslBundle get(PemSslBundleProperties properties, ResourceLoader resourceLoader) {
|
||||
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore(), resourceLoader);
|
||||
if (keyStore != null) {
|
||||
keyStore = keyStore.withAlias(properties.getKey().getAlias())
|
||||
.withPassword(properties.getKey().getPassword());
|
||||
}
|
||||
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore());
|
||||
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore(), resourceLoader);
|
||||
SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore);
|
||||
return new PropertiesSslBundle(storeBundle, properties);
|
||||
}
|
||||
|
||||
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) {
|
||||
PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties));
|
||||
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties,
|
||||
ResourceLoader resourceLoader) {
|
||||
PemSslStoreDetails details = asPemSslStoreDetails(properties);
|
||||
PemSslStore pemSslStore = PemSslStore.load(details, resourceLoader);
|
||||
if (properties.isVerifyKeys()) {
|
||||
CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey());
|
||||
Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()),
|
||||
@@ -128,14 +143,25 @@ public final class PropertiesSslBundle implements SslBundle {
|
||||
* @return an {@link SslBundle} instance
|
||||
*/
|
||||
public static SslBundle get(JksSslBundleProperties properties) {
|
||||
SslStoreBundle storeBundle = asSslStoreBundle(properties);
|
||||
return get(properties, new ApplicationResourceLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link SslBundle} for the given {@link JksSslBundleProperties}.
|
||||
* @param properties the source properties
|
||||
* @param resourceLoader the resource loader used to load content
|
||||
* @return an {@link SslBundle} instance
|
||||
* @since 3.3.5
|
||||
*/
|
||||
public static SslBundle get(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
|
||||
SslStoreBundle storeBundle = asSslStoreBundle(properties, resourceLoader);
|
||||
return new PropertiesSslBundle(storeBundle, properties);
|
||||
}
|
||||
|
||||
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) {
|
||||
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
|
||||
JksSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
|
||||
JksSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
|
||||
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails, resourceLoader);
|
||||
}
|
||||
|
||||
private static JksSslStoreDetails asStoreDetails(JksSslBundleProperties.Store properties) {
|
||||
|
||||
@@ -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.
|
||||
@@ -21,10 +21,12 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.io.ApplicationResourceLoader;
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for SSL.
|
||||
@@ -36,9 +38,12 @@ import org.springframework.context.annotation.Bean;
|
||||
@EnableConfigurationProperties(SslProperties.class)
|
||||
public class SslAutoConfiguration {
|
||||
|
||||
private final ApplicationResourceLoader resourceLoader;
|
||||
|
||||
private final SslProperties sslProperties;
|
||||
|
||||
SslAutoConfiguration(SslProperties sslProperties) {
|
||||
SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
|
||||
this.resourceLoader = new ApplicationResourceLoader(resourceLoader.getClassLoader());
|
||||
this.sslProperties = sslProperties;
|
||||
}
|
||||
|
||||
@@ -49,7 +54,7 @@ public class SslAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(FileWatcher fileWatcher) {
|
||||
return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher);
|
||||
return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher, this.resourceLoader);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -21,12 +21,14 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleRegistry;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* A {@link SslBundleRegistrar} that registers SSL bundles based
|
||||
@@ -42,9 +44,12 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar {
|
||||
|
||||
private final FileWatcher fileWatcher;
|
||||
|
||||
SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher) {
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher, ResourceLoader resourceLoader) {
|
||||
this.properties = properties.getBundle();
|
||||
this.fileWatcher = fileWatcher;
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,9 +59,9 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar {
|
||||
}
|
||||
|
||||
private <P extends SslBundleProperties> void registerBundles(SslBundleRegistry registry, Map<String, P> properties,
|
||||
Function<P, SslBundle> bundleFactory, Function<Bundle<P>, Set<Path>> watchedPaths) {
|
||||
BiFunction<P, ResourceLoader, SslBundle> bundleFactory, Function<Bundle<P>, Set<Path>> watchedPaths) {
|
||||
properties.forEach((bundleName, bundleProperties) -> {
|
||||
Supplier<SslBundle> bundleSupplier = () -> bundleFactory.apply(bundleProperties);
|
||||
Supplier<SslBundle> bundleSupplier = () -> bundleFactory.apply(bundleProperties, this.resourceLoader);
|
||||
try {
|
||||
registry.registerBundle(bundleName, bundleSupplier.get());
|
||||
if (bundleProperties.isReloadOnUpdate()) {
|
||||
|
||||
@@ -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.
|
||||
@@ -25,10 +25,15 @@ import java.util.function.Consumer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesSslBundle}.
|
||||
@@ -137,6 +142,22 @@ class PropertiesSslBundleTests {
|
||||
.withMessageContaining("Private key in keystore matches none of the certificates");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithResourceLoader() {
|
||||
PemSslBundleProperties properties = new PemSslBundleProperties();
|
||||
properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
|
||||
properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
|
||||
properties.getKeystore().setVerifyKeys(true);
|
||||
properties.getKey().setAlias("test-alias");
|
||||
ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
|
||||
SslBundle bundle = PropertiesSslBundle.get(properties, resourceLoader);
|
||||
assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias"));
|
||||
then(resourceLoader).should(atLeastOnce())
|
||||
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
|
||||
then(resourceLoader).should(atLeastOnce())
|
||||
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
|
||||
}
|
||||
|
||||
private Consumer<KeyStore> storeContainingCertAndKey(String keyAlias) {
|
||||
return ThrowingConsumer.of((keyStore) -> {
|
||||
assertThat(keyStore).isNotNull();
|
||||
|
||||
@@ -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,7 +23,9 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundleRegistry;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
@@ -31,6 +33,8 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.assertArg;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
/**
|
||||
@@ -44,6 +48,8 @@ class SslPropertiesBundleRegistrarTests {
|
||||
|
||||
private FileWatcher fileWatcher;
|
||||
|
||||
private DefaultResourceLoader resourceLoader;
|
||||
|
||||
private SslProperties properties;
|
||||
|
||||
private SslBundleRegistry registry;
|
||||
@@ -52,7 +58,8 @@ class SslPropertiesBundleRegistrarTests {
|
||||
void setUp() {
|
||||
this.properties = new SslProperties();
|
||||
this.fileWatcher = Mockito.mock(FileWatcher.class);
|
||||
this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher);
|
||||
this.resourceLoader = spy(new DefaultResourceLoader());
|
||||
this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher, this.resourceLoader);
|
||||
this.registry = Mockito.mock(SslBundleRegistry.class);
|
||||
}
|
||||
|
||||
@@ -85,6 +92,21 @@ class SslPropertiesBundleRegistrarTests {
|
||||
.watch(assertArg((set) -> pathEndingWith(set, "rsa-cert.pem", "rsa-key.pem")), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseResourceLoader() {
|
||||
PemSslBundleProperties pem = new PemSslBundleProperties();
|
||||
pem.getTruststore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
|
||||
pem.getTruststore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
|
||||
this.properties.getBundle().getPem().put("bundle1", pem);
|
||||
DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry();
|
||||
this.registrar.registerBundles(registry);
|
||||
registry.getBundle("bundle1").createSslContext();
|
||||
then(this.resourceLoader).should(atLeastOnce())
|
||||
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
|
||||
then(this.resourceLoader).should(atLeastOnce())
|
||||
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfPemKeystoreCertificateIsEmbedded() {
|
||||
PemSslBundleProperties pem = new PemSslBundleProperties();
|
||||
|
||||
Reference in New Issue
Block a user