diff --git a/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc b/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc index 0968fc2397..f37cd8194c 100644 --- a/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc +++ b/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc @@ -2,5 +2,29 @@ = AOT and Native Image Support :page-section-summary-toc: 1 -Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. However, for the time being, GraalVM native images are not supported. Implementing native image support is blocked by https://github.com/oracle/graal/issues/5134[graal#5134] and will likely require the completion of the work on https://github.com/graalvm/taming-build-time-initialization[https://github.com/graalvm/taming-build-time-initialization] to be fixed. +Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. As of `4.1.0` it also supports GraalVM native images, however it requires the user to add some workarounds for known GraalVM issues, as described below. + +==== + +IMPORTANT:: +Due to [a bug](https://github.com/oracle/graal/issues/5134) in Graal's `FileSystemProvider` a configuration workaround needs to be added to allow the Config Server to run as a native image. You will need to add the following options to your GraalVM build plugin setup (please refer to https://www.graalvm.org/[GraalVM] Maven or Gradle plugin documentation for more details): + +[source,indent=0] +---- +-H:-AddAllFileSystemProviders +--strict-image-heap +--initialize-at-build-time=org.bouncycastle +--initialize-at-build-time=net.i2p.crypto.eddsa.EdDSASecurityProvider +--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default +--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV +---- + +NOTE:: Adding the additional build time initializations can affect performance, but it still may offer gains as compared to a regular JVM run. Make sure to measure and compare for your application. + +==== + +TIP:: +If you are connecting with your config data backend over SSH, keep in mind that GraalVM requires https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/JCASecurityServices/#provider-registration[security provider registration using `java.security`] + +WARNING: Refresh scope is not supported with native images. If you are going to run your config client application as a native image, make sure to set `spring.cloud.refresh.enabled` property to `false`. diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index e0586f3704..8051f44e57 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -98,13 +98,8 @@ org.bouncycastle - bcpkix-jdk15on - test - - - org.bouncycastle - bcpkix-jdk15on - 1.67 + bcpkix-jdk18on + 1.74 test diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java new file mode 100644 index 0000000000..9017314280 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java @@ -0,0 +1,103 @@ +/* + * 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.config.server.config; + +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.Signature; +import java.util.Set; + +import javax.crypto.KeyAgreement; +import javax.crypto.Mac; + +import org.apache.sshd.common.channel.ChannelListener; +import org.apache.sshd.common.forward.PortForwardingEventListener; +import org.apache.sshd.common.io.nio2.Nio2ServiceFactory; +import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory; +import org.apache.sshd.common.session.SessionListener; +import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar; +import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar; +import org.eclipse.jgit.api.FetchCommand; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.internal.transport.sshd.SshdText; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.cloud.config.environment.PropertyValueDescriptor; +import org.springframework.cloud.config.server.ssh.HostKeyAlgoSupportedValidator; +import org.springframework.cloud.config.server.ssh.HostKeyAndAlgoBothExistValidator; +import org.springframework.cloud.config.server.ssh.KnownHostsFileValidator; +import org.springframework.cloud.config.server.ssh.PrivateKeyValidator; +import org.springframework.cloud.config.server.ssh.SshPropertyValidator; +import org.springframework.util.ClassUtils; + +/** + * A {@link RuntimeHintsRegistrar} implementation that makes types required by Config + * Server available in constrained environments. + * + * @author Olga Maciaszek-Sharma + * @since 4.1.0 + */ +class ConfigServerRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + if (!ClassUtils.isPresent("org.springframework.cloud.config.server.config.ConfigServerConfiguration", + classLoader)) { + return; + } + hints.reflection().registerTypes(Set.of(TypeReference.of(HostKeyAndAlgoBothExistValidator.class), + TypeReference.of(KnownHostsFileValidator.class), TypeReference.of(HostKeyAlgoSupportedValidator.class), + TypeReference.of(PrivateKeyValidator.class), TypeReference.of(SshPropertyValidator.class), + TypeReference.of(PropertyValueDescriptor.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + hints.reflection().registerTypes( + Set.of(TypeReference.of(PropertyValueDescriptor.class), TypeReference.of(Mac.class), + TypeReference.of(KeyAgreement.class), TypeReference.of(KeyPairGenerator.class), + TypeReference.of(KeyFactory.class), TypeReference.of(Signature.class), + TypeReference.of(MessageDigest.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS)); + + // TODO: move over to GraalVM reachability metadata + if (ClassUtils.isPresent("org.apache.sshd.common.SshConstants", classLoader)) { + hints.reflection().registerTypes(Set.of(TypeReference.of(BouncyCastleSecurityProviderRegistrar.class), + TypeReference.of(EdDSASecurityProviderRegistrar.class), TypeReference.of(Nio2ServiceFactory.class), + TypeReference.of(Nio2ServiceFactoryFactory.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + hints.reflection().registerTypes(Set.of(TypeReference.of(PortForwardingEventListener.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); + hints.proxies().registerJdkProxy(TypeReference.of(ChannelListener.class), + TypeReference.of(PortForwardingEventListener.class), TypeReference.of(SessionListener.class)); + } + + // TODO: move over to GraalVM reachability metadata + if (ClassUtils.isPresent("org.eclipse.jgit.api.Git", classLoader)) { + hints.reflection() + .registerTypes(Set.of(TypeReference.of(MergeCommand.FastForwardMode.Merge.class), + TypeReference.of(MergeCommand.ConflictStyle.class), + TypeReference.of(MergeCommand.FastForwardMode.class), TypeReference.of(FetchCommand.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS)); + hints.reflection().registerTypes(Set.of(TypeReference.of(SshdText.class)), hint -> hint + .withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)); + } + } + +} diff --git a/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..35c4ba6b0a --- /dev/null +++ b/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.cloud.config.server.config.ConfigServerRuntimeHints