diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java index 0333618bb..85a3b1f56 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java @@ -84,11 +84,11 @@ */ class ClientAuthenticationFactory { - private static final boolean GOOGLE_CREDENTIAL_AVAILABLE = ClassUtils.isPresent( + private static final boolean GOOGLE_CREDENTIAL_PRESENT = ClassUtils.isPresent( "com.google.api.client.googleapis.auth.oauth2.GoogleCredential", ClientAuthenticationFactory.class.getClassLoader()); - private static final boolean GOOGLE_CREDENTIALS_AVAILABLE = ClassUtils + private static final boolean GOOGLE_CREDENTIALS_PRESENT = ClassUtils .isPresent("com.google.auth.oauth2.GoogleCredentials", ClientAuthenticationFactory.class.getClassLoader()); private final VaultProperties vaultProperties; @@ -347,11 +347,11 @@ private ClientAuthentication gcpGceAuthentication(VaultProperties vaultPropertie private ClientAuthentication gcpIamAuthentication(VaultProperties vaultProperties) { - if (GOOGLE_CREDENTIAL_AVAILABLE) { + if (GOOGLE_CREDENTIAL_PRESENT) { return GcpIamAuthenticationFactory.create(vaultProperties, this.restOperations); } - if (GOOGLE_CREDENTIALS_AVAILABLE) { + if (GOOGLE_CREDENTIALS_PRESENT) { return GcpIamCredentialsAuthenticationFactory.create(vaultProperties, this.restOperations); } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java index 22a342040..0b390c8a2 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java @@ -245,12 +245,12 @@ public TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler) { this(taskScheduler, true); } - TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet) { + public TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet) { this.taskScheduler = taskScheduler; this.acceptAfterPropertiesSet = acceptAfterPropertiesSet; } - ThreadPoolTaskScheduler getTaskScheduler() { + public ThreadPoolTaskScheduler getTaskScheduler() { return this.taskScheduler; } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLoader.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLoader.java index c3f92c17f..70a88b7eb 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLoader.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLoader.java @@ -27,8 +27,8 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; - import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.BootstrapContext; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.BootstrapRegistryInitializer; @@ -62,6 +62,7 @@ import org.springframework.vault.client.VaultEndpointProvider; import org.springframework.vault.client.WebClientBuilder; import org.springframework.vault.client.WebClientFactory; +import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper; import org.springframework.vault.core.ReactiveVaultTemplate; import org.springframework.vault.core.VaultTemplate; import org.springframework.vault.core.env.LeaseAwareVaultPropertySource; @@ -70,8 +71,6 @@ import org.springframework.vault.core.lease.event.LeaseErrorListener; import org.springframework.web.client.RestTemplate; -import static org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper; - /** * {@link ConfigDataLoader} for Vault for {@link VaultConfigLocation}. This class * materializes {@link PropertySource property sources} by using Vault and @@ -94,13 +93,13 @@ public class VaultConfigDataLoader implements ConfigDataLoader void registerIfAbsent(ConfigurableBootstrapContext bootstrap, String GenericApplicationContext gac = (GenericApplicationContext) event.getApplicationContext(); + ConfigurableListableBeanFactory factory = gac.getBeanFactory(); + + if (factory.containsSingleton(beanName) || factory.containsBeanDefinition(beanName)) { + return; + } + contextCustomizer.accept(gac); T instance = event.getBootstrapContext().get(instanceType); - gac.registerBean(beanName, instanceType, () -> instance); + factory.registerSingleton(beanName, instance); }); } @@ -389,7 +394,6 @@ static void reconfigureLogger(Class type, DeferredLogFactory logFactory) { field.setAccessible(true); field.set(null, logFactory.getLog(type)); - }, VaultConfigDataLoader::isUpdateableLogField); } @@ -399,7 +403,6 @@ static void reconfigureLogger(Object object, DeferredLogFactory logFactory) { field.setAccessible(true); field.set(object, logFactory.getLog(object.getClass())); - }, VaultConfigDataLoader::isUpdateableLogField); } @@ -419,7 +422,7 @@ static Class forName(String name) { /** * Support class to register imperative infrastructure bootstrap instances and beans. - * + *

* Mirrors {@link VaultAutoConfiguration}. */ static class ImperativeInfrastructure { diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java index 5cc02ce14..2531a7bcc 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java @@ -36,7 +36,6 @@ import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.vault.core.util.PropertyTransformer; import org.springframework.vault.core.util.PropertyTransformers; @@ -201,17 +200,17 @@ private static VaultKeyValueBackendProperties getKeyValueProperties(ConfigDataLo private static List findDescriptors(Binder binder) { - List descriptorClasses = new ArrayList<>(); - descriptorClasses.addAll(SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptor.class, + List descriptorsOrFactories = new ArrayList<>(); + descriptorsOrFactories.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptor.class, VaultConfigDataLocationResolver.class.getClassLoader())); - descriptorClasses.addAll(SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptorFactory.class, + descriptorsOrFactories.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptorFactory.class, VaultConfigDataLocationResolver.class.getClassLoader())); - List descriptors = new ArrayList<>(descriptorClasses.size()); + List descriptors = new ArrayList<>(descriptorsOrFactories.size()); - for (String className : descriptorClasses) { + for (Object descriptorOrFactory : descriptorsOrFactories) { - Class descriptorClass = loadClass(className); + Class descriptorClass = descriptorOrFactory.getClass(); MergedAnnotations annotations = MergedAnnotations.from(descriptorClass); if (annotations.isPresent(ConfigurationProperties.class)) { @@ -228,12 +227,13 @@ else if (hydratedDescriptor instanceof VaultSecretBackendDescriptor) { else { throw new IllegalStateException(String.format( "Descriptor %s is neither implements VaultSecretBackendDescriptorFactory nor VaultSecretBackendDescriptor", - className)); + ClassUtils.getQualifiedName(descriptorOrFactory.getClass()))); } } else { - throw new IllegalStateException(String.format( - "VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties", className)); + throw new IllegalStateException( + String.format("VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties", + ClassUtils.getQualifiedName(descriptorOrFactory.getClass()))); } } @@ -246,17 +246,4 @@ private static List loadClass(String className) { - try { - return ClassUtils.forName(className, VaultConfigDataLocationResolver.class.getClassLoader()); - } - catch (ReflectiveOperationException e) { - ReflectionUtils.rethrowRuntimeException(e); - - // should never happen. - return null; - } - } - } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultRuntimeHints.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultRuntimeHints.java new file mode 100644 index 000000000..7ea30695b --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultRuntimeHints.java @@ -0,0 +1,114 @@ +/* + * Copyright 2018-2023 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.cloud.vault.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.vault.authentication.LifecycleAwareSessionManager; +import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport; +import org.springframework.vault.authentication.ReactiveLifecycleAwareSessionManager; +import org.springframework.vault.authentication.SimpleSessionManager; +import org.springframework.vault.client.ClientHttpRequestFactoryFactory; +import org.springframework.vault.core.env.LeaseAwareVaultPropertySource; + +/** + * Runtime hints for Spring Cloud Vault usage with native images. + * + * @author Mark Paluch + */ +class VaultRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + + ReflectionHints reflection = hints.reflection(); + + // reflection required for ConfigDataLoader, early logging capture + reflection.registerTypes(Arrays.asList(SimpleSessionManager.class, LifecycleAwareSessionManager.class, + LifecycleAwareSessionManagerSupport.class, ClientHttpRequestFactoryFactory.class, + org.springframework.vault.core.env.VaultPropertySource.class, LeaseAwareVaultPropertySource.class) + .stream().map(TypeReference::of).collect(Collectors.toList()), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS)); + + reflection.registerTypes( + Arrays.asList(VaultKeyValueBackendProperties.class).stream().map(TypeReference::of) + .collect(Collectors.toList()), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS, + MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + + reflection.registerType( + TypeReference.of("org.springframework.vault.core.lease.SecretLeaseContainer$LeaseRenewalScheduler"), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS)); + + reflection.registerType( + TypeReference.of("org.springframework.vault.core.lease.SecretLeaseEventPublisher$LoggingErrorListener"), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS)); + + reflection.registerType(TypeReference + .of("org.springframework.cloud.vault.config.VaultReactiveConfiguration$ReactiveSessionManagerAdapter"), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS)); + + if (VaultConfigDataLoader.webclientPresent && VaultConfigDataLoader.reactorPresent) { + reflection + .registerTypes( + Arrays.asList(ReactiveLifecycleAwareSessionManager.class).stream().map(TypeReference::of) + .collect(Collectors.toList()), + builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS)); + } + + // presence checks + reflection.registerTypeIfPresent(classLoader, "reactor.core.publisher.Flux", MemberCategory.PUBLIC_CLASSES); + reflection.registerTypeIfPresent(classLoader, "org.springframework.web.reactive.function.client.WebClient", + MemberCategory.PUBLIC_CLASSES); + + reflection.registerTypeIfPresent(classLoader, "org.bouncycastle.crypto.signers.PSSSigner", + MemberCategory.PUBLIC_CLASSES); + reflection.registerTypeIfPresent(classLoader, "com.google.api.client.googleapis.auth.oauth2.GoogleCredential", + MemberCategory.PUBLIC_CLASSES); + reflection.registerTypeIfPresent(classLoader, "com.google.auth.oauth2.GoogleCredentials", + MemberCategory.PUBLIC_CLASSES); + + // reflection for pluggable config properties bindings + List pluggableDescriptors = new ArrayList<>(); + + pluggableDescriptors + .addAll(SpringFactoriesLoader.loadFactories(SecretBackendMetadataFactory.class, classLoader)); + pluggableDescriptors + .addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptor.class, classLoader)); + pluggableDescriptors + .addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptorFactory.class, classLoader)); + + List pluggableDescriptorReferences = pluggableDescriptors.stream().map(Object::getClass) + .map(TypeReference::of).collect(Collectors.toList()); + + reflection.registerTypes(pluggableDescriptorReferences, builder -> { + builder.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, + MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS); + }); + } + +} diff --git a/spring-cloud-vault-config/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-vault-config/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..6c6e410c5 --- /dev/null +++ b/spring-cloud-vault-config/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.cloud.vault.config.VaultRuntimeHints diff --git a/spring-cloud-vault-dependencies/pom.xml b/spring-cloud-vault-dependencies/pom.xml index 3420b449b..2830230b6 100644 --- a/spring-cloud-vault-dependencies/pom.xml +++ b/spring-cloud-vault-dependencies/pom.xml @@ -20,7 +20,7 @@ Spring Cloud Vault Dependencies - 3.0.0 + 3.0.1-SNAPSHOT