From c812217cf5451ffd734bac8be828aea4f3bda9e3 Mon Sep 17 00:00:00 2001 From: Jeff Trent Date: Thu, 21 Sep 2023 23:11:56 -0400 Subject: [PATCH] Oci Tls Manager Integration --- bom/pom.xml | 11 +- integrations/oci/pom.xml | 1 + integrations/oci/tls-certificates/pom.xml | 111 +-- .../DefaultOciCertificatesDownloader.java | 17 +- .../DefaultOciCertificatesTlsManager.java | 99 ++- ...aultOciCertificatesTlsManagerProvider.java | 6 +- .../DefaultOciPrivateKeyDownloader.java | 15 +- .../tls/certificates/InjectionServices.java | 70 ++ .../oci/tls/certificates/LifecycleHook.java | 6 +- .../OciCertificatesTlsManager.java | 13 +- .../OciCertificatesTlsManagerConfig.java | 630 ++++++++++++++++++ ...CertificatesTlsManagerConfigBlueprint.java | 11 +- .../spi/OciCertificatesDownloader.java | 4 +- .../spi/OciPrivateKeyDownloader.java | 4 +- .../src/main/java/module-info.java | 23 +- .../OciCertificatesTlsManagerTest.java | 15 +- .../TestOciCertificatesDownloader.java | 22 +- .../TestOciPrivateKeyDownloader.java | 26 +- .../tls/certificates/TestingCdiExtension.java | 1 - ...rations.oci.tls.certificates.LifecycleHook | 17 + ...certificates.spi.OciCertificatesDownloader | 17 + ...s.certificates.spi.OciPrivateKeyDownloader | 17 + .../src/test/resources/application.yaml | 4 +- .../webserver/ConfiguredTlsManager.java | 70 +- .../java/io/helidon/webserver/TlsManager.java | 2 +- .../io/helidon/webserver/WebServerTls.java | 17 +- 26 files changed, 1023 insertions(+), 206 deletions(-) create mode 100644 integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/InjectionServices.java create mode 100644 integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfig.java create mode 100644 integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.LifecycleHook create mode 100644 integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader create mode 100644 integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader diff --git a/bom/pom.xml b/bom/pom.xml index 329e31b6e07..8448b9a6152 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -922,12 +922,11 @@ helidon-integrations-oci-metrics ${helidon.version} - - - - - - + + io.helidon.integrations.oci + helidon-integrations-oci-tls-certificates + ${helidon.version} + io.helidon.integrations.oci.metrics helidon-integrations-oci-metrics-cdi diff --git a/integrations/oci/pom.xml b/integrations/oci/pom.xml index d861f2864b3..42d11e79518 100644 --- a/integrations/oci/pom.xml +++ b/integrations/oci/pom.xml @@ -36,5 +36,6 @@ metrics oci-secrets-config-source sdk + tls-certificates diff --git a/integrations/oci/tls-certificates/pom.xml b/integrations/oci/tls-certificates/pom.xml index 5c6652e9bae..c3953ff5f91 100644 --- a/integrations/oci/tls-certificates/pom.xml +++ b/integrations/oci/tls-certificates/pom.xml @@ -23,7 +23,7 @@ io.helidon.integrations.oci helidon-integrations-oci-project - 4.0.0-SNAPSHOT + 3.2.3-SNAPSHOT 4.0.0 @@ -58,7 +58,7 @@ jakarta.inject jakarta.inject-api - true + jakarta.annotation @@ -66,21 +66,16 @@ true - io.helidon.config - helidon-config-metadata - true - - - io.helidon.builder - helidon-builder-api + io.helidon.webserver + helidon-webserver io.helidon.common - helidon-common-tls + helidon-common-key-util io.helidon.common - helidon-common-key-util + helidon-common-service-loader io.helidon.integrations.oci.sdk @@ -90,17 +85,25 @@ io.helidon.fault-tolerance helidon-fault-tolerance + + io.helidon.scheduling + helidon-scheduling + io.helidon.config helidon-config-yaml - io.helidon.scheduling - helidon-scheduling + io.helidon.config + helidon-config-metadata + provided + true - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime + io.helidon.config + helidon-config-metadata-processor + provided + true @@ -145,84 +148,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - - - default-testCompile - - testCompile - - - true - - -Ainject.ignoreUnsupportedAnnotations=true - - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.common.processor - helidon-common-processor-helidon-copyright - ${helidon.version} - - - - - - default-compile - - compile - - - true - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.common.processor - helidon-common-processor-helidon-copyright - ${helidon.version} - - - - - - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - io.helidon.common.processor - helidon-common-processor-helidon-copyright - ${helidon.version} - - - maven-surefire-plugin diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java index 1c29ab89e3c..063eb9ed0d6 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Objects; +import io.helidon.common.Prioritized; import io.helidon.common.pki.PemReader; import io.helidon.integrations.oci.sdk.runtime.OciExtension; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; @@ -43,7 +44,16 @@ * Implementation of the {@link OciCertificatesDownloader} that will use OCI's Certificates Service to download certs. */ @Singleton -class DefaultOciCertificatesDownloader implements OciCertificatesDownloader { +public class DefaultOciCertificatesDownloader implements OciCertificatesDownloader, Prioritized { + + /** + * Service loader based constructor. + * + * @deprecated this is a Java ServiceLoader implementation and the constructor should not be used directly + */ + @Deprecated + public DefaultOciCertificatesDownloader() { + } @Override public Certificates loadCertificates(String certOcid) { @@ -121,4 +131,9 @@ static String toVersion(String eTag, return String.valueOf(Arrays.hashCode(certs)); } + + @Override + public int priority() { + return DEFAULT_PRIORITY; + } } diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java index 4428dce8389..c5ca46b77d9 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java @@ -16,12 +16,17 @@ package io.helidon.integrations.oci.tls.certificates; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.util.Arrays; +import java.util.Base64; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Executors; @@ -33,18 +38,17 @@ import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import io.helidon.common.tls.ConfiguredTlsManager; -import io.helidon.common.tls.TlsConfig; -import io.helidon.config.Config; import io.helidon.faulttolerance.Async; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; import io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; +import io.helidon.webserver.ConfiguredTlsManager; +import io.helidon.webserver.WebServerTls; import jakarta.inject.Provider; +import static io.helidon.integrations.oci.tls.certificates.InjectionServices.Services; +import static io.helidon.integrations.oci.tls.certificates.InjectionServices.realizedServices; + /** * The default implementation (service loader and provider-driven) implementation of {@link OciCertificatesTlsManager}. * @@ -62,7 +66,9 @@ class DefaultOciCertificatesTlsManager extends ConfiguredTlsManager implements O private Provider certDownloader; private ScheduledExecutorService asyncExecutor; private Async async; - private TlsConfig tlsConfig; + private WebServerTls tlsConfig; + private volatile X509KeyManager keyManager; + private volatile X509TrustManager trustManager; DefaultOciCertificatesTlsManager(OciCertificatesTlsManagerConfig cfg) { this(cfg, "@default", null); @@ -70,20 +76,20 @@ class DefaultOciCertificatesTlsManager extends ConfiguredTlsManager implements O DefaultOciCertificatesTlsManager(OciCertificatesTlsManagerConfig cfg, String name, - io.helidon.common.config.Config config) { + io.helidon.config.Config config) { super(name, TYPE); this.cfg = Objects.requireNonNull(cfg); // if config changes then will do a reload - if (config instanceof Config watchableConfig) { - watchableConfig.onChange(this::config); + if (config != null) { + config.onChange(this::config); } } @Override // TlsManager - public void init(TlsConfig tls) { + public void init(WebServerTls tls) { this.tlsConfig = tls; - Services services = InjectionServices.realizedServices(); + Services services = realizedServices(); this.pkDownloader = services.lookupFirst(OciPrivateKeyDownloader.class); this.certDownloader = services.lookupFirst(OciCertificatesDownloader.class); this.asyncExecutor = Executors.newSingleThreadScheduledExecutor(); @@ -93,7 +99,7 @@ public void init(TlsConfig tls) { loadContext(true); // register for any available graceful shutdown events - Optional> shutdownHook = services.lookupFirst(LifecycleHook.class, false); + Optional> shutdownHook = services.lookupFirst(LifecycleHook.class, false); shutdownHook.ifPresent(sp -> sp.get().registerShutdownConsumer(this::shutdown)); // now schedule for reload checking @@ -108,6 +114,16 @@ public void init(TlsConfig tls) { OciCertificatesTlsManagerConfig.class.getSimpleName() + " scheduled: " + taskIntervalDescription); } + @Override // TlsManager + public Optional keyManager() { + return Optional.ofNullable(keyManager); + } + + @Override // TlsManager + public Optional trustManager() { + return Optional.ofNullable(trustManager); + } + private void shutdown(Object event) { try { LOGGER.log(System.Logger.Level.DEBUG, "Shutting down"); @@ -117,8 +133,8 @@ private void shutdown(Object event) { } } - @Override // RuntimeType - public OciCertificatesTlsManagerConfig prototype() { +// @Override // RuntimeType + OciCertificatesTlsManagerConfig prototype() { return cfg; } @@ -134,7 +150,7 @@ private void maybeReload() { * * @param config the new config */ - void config(io.helidon.common.config.Config config) { + void config(io.helidon.config.Config config) { Objects.requireNonNull(config); maybeReload(); } @@ -143,6 +159,7 @@ void config(io.helidon.common.config.Config config) { * Will download new certificates, and if those are determined to be changed will affect the reload of the new key and trust * managers. * + * @param initialLoad flag indicating whether this is the initial loading of config * @return true if a reload occurred */ boolean loadContext(boolean initialLoad) { @@ -189,10 +206,13 @@ boolean loadContext(boolean initialLoad) { throw new RuntimeException("Unable to find X.509 trust manager in download: " + cfg.certOcid()); } + this.keyManager = keyManager.get(); + this.trustManager = trustManager.get(); + if (initialLoad) { - initSslContext(tlsConfig, secureRandom, kmf.getKeyManagers(), tmf.getTrustManagers()); + initSslContext(tlsConfig, kmf.getKeyManagers(), tmf.getTrustManagers()); } else { - reload(keyManager, trustManager); + reload(tlsConfig, kmf.getKeyManagers(), tmf.getTrustManagers()); } return true; @@ -201,4 +221,47 @@ boolean loadContext(boolean initialLoad) { } } + private KeyManagerFactory buildKmf(WebServerTls target, + SecureRandom secureRandom, + PrivateKey privateKey, + Certificate[] certificates) { + byte[] passwordBytes = new byte[64]; + secureRandom.nextBytes(passwordBytes); + char[] password = Base64.getEncoder().encodeToString(passwordBytes).toCharArray(); + + try { + KeyStore ks = internalKeystore(target); + ks.setKeyEntry("key", + privateKey, + password, + certificates); + + KeyManagerFactory kmf = kmf(target); + kmf.init(ks, password); + return kmf; + } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Invalid configuration for key management factory, cannot create factory", e); + } + } + + KeyManagerFactory kmf(WebServerTls tlsConfig) { + try { + String algorithm = KeyManagerFactory.getDefaultAlgorithm(); + return KeyManagerFactory.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Invalid configuration of key manager factory.", e); + } + } + + private KeyStore internalKeystore(WebServerTls tlsConfig) { + try { + String type = KeyStore.getDefaultType(); + KeyStore ks = KeyStore.getInstance(type); + ks.load(null, null); + return ks; + } catch (GeneralSecurityException | IOException e) { + throw new IllegalArgumentException("Invalid configuration of internal keystores", e); + } + } + } diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java index 1ac5dff8d89..d3199204eb2 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java @@ -16,9 +16,9 @@ package io.helidon.integrations.oci.tls.certificates; -import io.helidon.common.config.Config; -import io.helidon.common.tls.TlsManager; -import io.helidon.common.tls.spi.TlsManagerProvider; +import io.helidon.config.Config; +import io.helidon.webserver.TlsManager; +import io.helidon.webserver.spi.TlsManagerProvider; /** * The service provider for {@link OciCertificatesTlsManager}. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java index 3c3b08d4003..43a5548e8d1 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java @@ -34,6 +34,7 @@ import javax.crypto.spec.PSource; import javax.crypto.spec.SecretKeySpec; +import io.helidon.common.Prioritized; import io.helidon.integrations.oci.sdk.runtime.OciExtension; import io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; @@ -47,11 +48,17 @@ * Implementation of the {@link OciPrivateKeyDownloader} that will use OCI's KMS to export a key. */ @Singleton -class DefaultOciPrivateKeyDownloader implements OciPrivateKeyDownloader { +public class DefaultOciPrivateKeyDownloader implements OciPrivateKeyDownloader, Prioritized { private final PrivateKey wrappingPrivateKey; private final String wrappingPublicKeyPem; - DefaultOciPrivateKeyDownloader() { + /** + * Service loader based constructor. + * + * @deprecated this is a Java ServiceLoader implementation and the constructor should not be used directly + */ + @Deprecated + public DefaultOciPrivateKeyDownloader() { try { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048); @@ -123,4 +130,8 @@ byte[] decryptAesKey(byte[] in) throws GeneralSecurityException { return decrypt.doFinal(in); } + @Override + public int priority() { + return DEFAULT_PRIORITY; + } } diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/InjectionServices.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/InjectionServices.java new file mode 100644 index 00000000000..7041b2f4272 --- /dev/null +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/InjectionServices.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.integrations.oci.tls.certificates; + +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; + +import io.helidon.common.serviceloader.HelidonServiceLoader; + +import jakarta.inject.Provider; + +class InjectionServices { + + private InjectionServices() { + } + + static Services realizedServices() { + return new Services(); + } + + static class Services { + public Provider lookupFirst(Class serviceProviderType) { + return lookupFirst(serviceProviderType, true).orElseThrow(); + } + + public Optional> lookupFirst(Class serviceProviderType, + boolean expected) { + List exemplarServices = + HelidonServiceLoader.create(ServiceLoader.load(serviceProviderType)).asList(); + if (exemplarServices.isEmpty()) { + if (expected) { + throw new IllegalStateException("Expected to find a service provider of type: " + serviceProviderType); + } + + return Optional.empty(); + } + + return Optional.of(new SingletonProvider<>(exemplarServices.get(0))); + } + } + + static class SingletonProvider implements Provider { + private final T instance; + + SingletonProvider(T instance) { + this.instance = instance; + } + + @Override + public T get() { + return instance; + } + } + +} diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java index 9dc6f078242..f9d12ccab47 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java @@ -18,11 +18,7 @@ import java.util.function.Consumer; -import io.helidon.inject.api.Contract; - -// consider making this public and relocating this to somewhere under common or inject -// it is here to ensure proper shutdown (decoupled from mp) and presenting non-intermittent test failures -@Contract +//@Contract interface LifecycleHook { void registerStartupConsumer(Consumer consumer); diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManager.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManager.java index b9d3cb07104..c895a549a78 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManager.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManager.java @@ -18,17 +18,16 @@ import java.util.function.Consumer; -import io.helidon.builder.api.RuntimeType; -import io.helidon.common.config.Config; -import io.helidon.common.tls.TlsManager; +import io.helidon.config.Config; +import io.helidon.webserver.TlsManager; /** - * The OCI Certificates contract of {@link io.helidon.common.tls.TlsManager}. The implementation should load/create - * {@link io.helidon.common.tls.Tls} instances from integrating to the certificates stored remotely in OCI's + * The OCI Certificates contract of {@link io.helidon.webserver.TlsManager}. The implementation should load/create + * {@link javax.net.ssl.SSLContext} instances from integrating to the certificates stored remotely in OCI's * Certificates Service, and then allow for a scheduled update check of the Tls instance for changes. */ -@RuntimeType.PrototypedBy(OciCertificatesTlsManagerConfig.class) -public interface OciCertificatesTlsManager extends TlsManager, RuntimeType.Api { +//@RuntimeType.PrototypedBy(OciCertificatesTlsManagerConfig.class) +public interface OciCertificatesTlsManager extends TlsManager /*, RuntimeType.Api*/ { /** * Creates a default {@link OciCertificatesTlsManager} instance. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfig.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfig.java new file mode 100644 index 00000000000..c98e1bfab09 --- /dev/null +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfig.java @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.integrations.oci.tls.certificates; + +import java.net.URI; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.Errors; +import io.helidon.config.Config; + +/** + * Blueprint configuration for {@link OciCertificatesTlsManager}. + * + * @see #builder() + * @see #create() + */ +public interface OciCertificatesTlsManagerConfig extends OciCertificatesTlsManagerConfigBlueprint /*, Prototype.Api*/ { + + /** + * Create a new fluent API builder to customize configuration. + * + * @return a new builder + */ + static OciCertificatesTlsManagerConfig.Builder builder() { + return new OciCertificatesTlsManagerConfig.Builder(); + } + + /** + * Create a new fluent API builder from an existing instance. + * + * @param instance an existing instance used as a base for the builder + * @return a builder based on an instance + */ + static OciCertificatesTlsManagerConfig.Builder builder(OciCertificatesTlsManagerConfig instance) { + return OciCertificatesTlsManagerConfig.builder().from(instance); + } + + /** + * Create a new instance from configuration. + * + * @param config used to configure the new instance + * @return a new instance configured from configuration + */ + static OciCertificatesTlsManagerConfig create(Config config) { + return OciCertificatesTlsManagerConfig.builder().config(config).buildPrototype(); + } + + /** + * Create a new instance with default values. + * + * @return a new instance + */ + static OciCertificatesTlsManagerConfig create() { + return OciCertificatesTlsManagerConfig.builder().buildPrototype(); + } + + /** + * Fluent API builder base for {@link OciCertificatesTlsManager}. + * + * @param type of the builder extending this abstract builder + * @param type of the prototype interface + */ + abstract class BuilderBase, + PROTOTYPE extends OciCertificatesTlsManagerConfig> { + private Config config; + private String caOcid; + private String certOcid; + private String compartmentOcid; + private String keyOcid; + private String schedule; + private Supplier keyPassword; + private URI vaultCryptoEndpoint; + private URI vaultManagementEndpoint; + + /** + * Protected to support extensibility. + */ + protected BuilderBase() { + } + + BUILDER self() { + return (BUILDER) this; + } + + /** + * Update this builder from an existing prototype instance. + * + * @param prototype existing prototype to update this builder from + * @return updated builder instance + */ + public BUILDER from(OciCertificatesTlsManagerConfig prototype) { + schedule(prototype.schedule()); + vaultCryptoEndpoint(prototype.vaultCryptoEndpoint()); + vaultManagementEndpoint(prototype.vaultManagementEndpoint()); + compartmentOcid(prototype.compartmentOcid()); + caOcid(prototype.caOcid()); + certOcid(prototype.certOcid()); + keyOcid(prototype.keyOcid()); + keyPassword(prototype.keyPassword()); + return self(); + } + + /** + * Update this builder from an existing prototype builder instance. + * + * @param builder existing builder prototype to update this builder from + * @return updated builder instance + */ + public BUILDER from(OciCertificatesTlsManagerConfig.BuilderBase builder) { + builder.schedule().ifPresent(this::schedule); + builder.vaultCryptoEndpoint().ifPresent(this::vaultCryptoEndpoint); + builder.vaultManagementEndpoint().ifPresent(this::vaultManagementEndpoint); + builder.compartmentOcid().ifPresent(this::compartmentOcid); + builder.caOcid().ifPresent(this::caOcid); + builder.certOcid().ifPresent(this::certOcid); + builder.keyOcid().ifPresent(this::keyOcid); + builder.keyPassword().ifPresent(this::keyPassword); + return self(); + } + + /** + * Update builder from configuration (node of this type). + * If a value is present in configuration, it would override currently configured values. + * + * @param config configuration instance used to obtain values to update this builder + * @return updated builder instance + */ + public BUILDER config(Config config) { + Objects.requireNonNull(config); + this.config = config; + config.get("schedule").as(String.class).ifPresent(this::schedule); + config.get("vault-crypto-endpoint").as(URI.class).ifPresent(this::vaultCryptoEndpoint); + config.get("vault-management-endpoint").as(URI.class).ifPresent(this::vaultManagementEndpoint); + config.get("compartment-ocid").as(String.class).ifPresent(this::compartmentOcid); + config.get("ca-ocid").as(String.class).ifPresent(this::caOcid); + config.get("cert-ocid").as(String.class).ifPresent(this::certOcid); + config.get("key-ocid").as(String.class).ifPresent(this::keyOcid); + keyPassword(config.get("key-password").asString().as(String::toCharArray).supplier()); + return self(); + } + + /** + * The schedule for trigger a reload check, testing whether there is a new {@link javax.net.ssl.SSLContext} instance + * available. + * + * @param schedule the schedule for reload + * @return updated builder instance + * @see #schedule() + */ + public BUILDER schedule(String schedule) { + Objects.requireNonNull(schedule); + this.schedule = schedule; + return self(); + } + + /** + * The address to use for the OCI Key Management Service / Vault crypto usage. + * Each OCI Vault has public crypto and management endpoints. We need to specify the crypto endpoint of the vault we are + * rotating the private keys in. The implementation expects both client and server to store the private key in the same + * vault. + * + * @param vaultCryptoEndpoint the address for the key management service / vault crypto usage + * @return updated builder instance + * @see #vaultCryptoEndpoint() + */ + public BUILDER vaultCryptoEndpoint(URI vaultCryptoEndpoint) { + Objects.requireNonNull(vaultCryptoEndpoint); + this.vaultCryptoEndpoint = vaultCryptoEndpoint; + return self(); + } + + /** + * Clear existing value of this property. + * + * @return updated builder instance + * @see #vaultManagementEndpoint() + */ + public BUILDER clearVaultManagementEndpoint() { + this.vaultManagementEndpoint = null; + return self(); + } + + /** + * The address to use for the OCI Key Management Service / Vault management usage. + * The crypto endpoint of the vault we are rotating the private keys in. + * + * @param vaultManagementEndpoint the address for the key management service / vault management usage + * @return updated builder instance + * @see #vaultManagementEndpoint() + */ + public BUILDER vaultManagementEndpoint(URI vaultManagementEndpoint) { + Objects.requireNonNull(vaultManagementEndpoint); + this.vaultManagementEndpoint = vaultManagementEndpoint; + return self(); + } + + /** + * Clear existing value of this property. + * + * @return updated builder instance + * @see #compartmentOcid() + */ + public BUILDER clearCompartmentOcid() { + this.compartmentOcid = null; + return self(); + } + + /** + * The OCID of the compartment the services are in. + * + * @param compartmentOcid the compartment OCID + * @return updated builder instance + * @see #compartmentOcid() + */ + public BUILDER compartmentOcid(String compartmentOcid) { + Objects.requireNonNull(compartmentOcid); + this.compartmentOcid = compartmentOcid; + return self(); + } + + /** + * The Certificate Authority OCID. + * + * @param caOcid certificate authority OCID + * @return updated builder instance + * @see #caOcid() + */ + public BUILDER caOcid(String caOcid) { + Objects.requireNonNull(caOcid); + this.caOcid = caOcid; + return self(); + } + + /** + * The Certificate OCID. + * + * @param certOcid certificate OCID + * @return updated builder instance + * @see #certOcid() + */ + public BUILDER certOcid(String certOcid) { + Objects.requireNonNull(certOcid); + this.certOcid = certOcid; + return self(); + } + + /** + * The Key OCID. + * + * @param keyOcid key OCID + * @return updated builder instance + * @see #keyOcid() + */ + public BUILDER keyOcid(String keyOcid) { + Objects.requireNonNull(keyOcid); + this.keyOcid = keyOcid; + return self(); + } + + /** + * The Key password. + * + * @param keyPassword key password + * @return updated builder instance + * @see #keyPassword() + */ + public BUILDER keyPassword(Supplier keyPassword) { + Objects.requireNonNull(keyPassword); + this.keyPassword = keyPassword::get; + return self(); + } + + /** + * The Key password. + * + * @param keyPassword key password + * @return updated builder instance + * @see #keyPassword() + */ + public BUILDER keyPassword(char[] keyPassword) { + Objects.requireNonNull(keyPassword); + this.keyPassword = () -> keyPassword; + return self(); + } + + /** + * The Key password. + * + * @param keyPassword key password + * @return updated builder instance + * @see #keyPassword() + */ + public BUILDER keyPassword(String keyPassword) { + Objects.requireNonNull(keyPassword); + this.keyPassword = () -> keyPassword.toCharArray(); + return self(); + } + + /** + * The schedule for trigger a reload check, testing whether there is a new {@link javax.net.ssl.SSLContext} instance + * available. + * + * @return the schedule + */ + public Optional schedule() { + return Optional.ofNullable(schedule); + } + + /** + * The address to use for the OCI Key Management Service / Vault crypto usage. + * Each OCI Vault has public crypto and management endpoints. We need to specify the crypto endpoint of the vault we are + * rotating the private keys in. The implementation expects both client and server to store the private key in the same + * vault. + * + * @return the vault crypto endpoint + */ + public Optional vaultCryptoEndpoint() { + return Optional.ofNullable(vaultCryptoEndpoint); + } + + /** + * The address to use for the OCI Key Management Service / Vault management usage. + * The crypto endpoint of the vault we are rotating the private keys in. + * + * @return the vault management endpoint + */ + public Optional vaultManagementEndpoint() { + return Optional.ofNullable(vaultManagementEndpoint); + } + + /** + * The OCID of the compartment the services are in. + * + * @return the compartment ocid + */ + public Optional compartmentOcid() { + return Optional.ofNullable(compartmentOcid); + } + + /** + * The Certificate Authority OCID. + * + * @return the ca ocid + */ + public Optional caOcid() { + return Optional.ofNullable(caOcid); + } + + /** + * The Certificate OCID. + * + * @return the cert ocid + */ + public Optional certOcid() { + return Optional.ofNullable(certOcid); + } + + /** + * The Key OCID. + * + * @return the key ocid + */ + public Optional keyOcid() { + return Optional.ofNullable(keyOcid); + } + + /** + * The Key password. + * + * @return the key password + */ + public Optional> keyPassword() { + return Optional.ofNullable(keyPassword); + } + + /** + * If this instance was configured, this would be the config instance used. + * + * @return config node used to configure this builder, or empty if not configured + */ + public Optional config() { + return Optional.ofNullable(config); + } + + @Override + public String toString() { + return "OciCertificatesTlsManagerConfigBuilder{" + + "schedule=" + schedule + "," + + "vaultCryptoEndpoint=" + vaultCryptoEndpoint + "," + + "vaultManagementEndpoint=" + vaultManagementEndpoint + "," + + "compartmentOcid=" + compartmentOcid + "," + + "caOcid=" + caOcid + "," + + "certOcid=" + certOcid + "," + + "keyOcid=" + keyOcid + "," + + "keyPassword=" + (keyPassword == null ? "null" : "****") + + "}"; + } + + /** + * Handles providers and decorators. + */ + protected void preBuildPrototype() { + } + + /** + * Validates required properties. + */ + protected void validatePrototype() { + Errors.Collector collector = Errors.collector(); + if (schedule == null) { + collector.fatal(getClass(), "Property \"schedule\" must not be null, but not set"); + } + if (vaultCryptoEndpoint == null) { + collector.fatal(getClass(), "Property \"vault-crypto-endpoint\" must not be null, but not set"); + } + if (caOcid == null) { + collector.fatal(getClass(), "Property \"ca-ocid\" must not be null, but not set"); + } + if (certOcid == null) { + collector.fatal(getClass(), "Property \"cert-ocid\" must not be null, but not set"); + } + if (keyOcid == null) { + collector.fatal(getClass(), "Property \"key-ocid\" must not be null, but not set"); + } + if (keyPassword == null) { + collector.fatal(getClass(), "Property \"key-password\" must not be null, but not set"); + } + collector.collect().checkValid(); + } + + /** + * The address to use for the OCI Key Management Service / Vault management usage. + * The crypto endpoint of the vault we are rotating the private keys in. + * + * @param vaultManagementEndpoint the address for the key management service / vault management usage + * @return updated builder instance + * @see #vaultManagementEndpoint() + */ + BUILDER vaultManagementEndpoint(Optional vaultManagementEndpoint) { + Objects.requireNonNull(vaultManagementEndpoint); + this.vaultManagementEndpoint = vaultManagementEndpoint.orElse(null); + return self(); + } + + /** + * The OCID of the compartment the services are in. + * + * @param compartmentOcid the compartment OCID + * @return updated builder instance + * @see #compartmentOcid() + */ + BUILDER compartmentOcid(Optional compartmentOcid) { + Objects.requireNonNull(compartmentOcid); + this.compartmentOcid = compartmentOcid.orElse(null); + return self(); + } + + /** + * Generated implementation of the prototype, can be extended by descendant prototype implementations. + */ + protected static class OciCertificatesTlsManagerConfigImpl + implements OciCertificatesTlsManagerConfig, Supplier { + private final Optional compartmentOcid; + private final Optional vaultManagementEndpoint; + private final String caOcid; + private final String certOcid; + private final String keyOcid; + private final String schedule; + private final Supplier keyPassword; + private final URI vaultCryptoEndpoint; + + /** + * Create an instance providing a builder. + * + * @param builder extending builder base of this prototype + */ + protected OciCertificatesTlsManagerConfigImpl(OciCertificatesTlsManagerConfig.BuilderBase builder) { + this.schedule = builder.schedule().get(); + this.vaultCryptoEndpoint = builder.vaultCryptoEndpoint().get(); + this.vaultManagementEndpoint = builder.vaultManagementEndpoint(); + this.compartmentOcid = builder.compartmentOcid(); + this.caOcid = builder.caOcid().get(); + this.certOcid = builder.certOcid().get(); + this.keyOcid = builder.keyOcid().get(); + this.keyPassword = builder.keyPassword().get(); + } + + // @Override + public OciCertificatesTlsManager build() { + return OciCertificatesTlsManager.create(this); + } + + @Override + public OciCertificatesTlsManager get() { + return build(); + } + + @Override + public String schedule() { + return schedule; + } + + @Override + public URI vaultCryptoEndpoint() { + return vaultCryptoEndpoint; + } + + @Override + public Optional vaultManagementEndpoint() { + return vaultManagementEndpoint; + } + + @Override + public Optional compartmentOcid() { + return compartmentOcid; + } + + @Override + public String caOcid() { + return caOcid; + } + + @Override + public String certOcid() { + return certOcid; + } + + @Override + public String keyOcid() { + return keyOcid; + } + + @Override + public Supplier keyPassword() { + return keyPassword; + } + + @Override + public String toString() { + return "OciCertificatesTlsManagerConfig{" + + "schedule=" + schedule + "," + + "vaultCryptoEndpoint=" + vaultCryptoEndpoint + "," + + "vaultManagementEndpoint=" + vaultManagementEndpoint + "," + + "compartmentOcid=" + compartmentOcid + "," + + "caOcid=" + caOcid + "," + + "certOcid=" + certOcid + "," + + "keyOcid=" + keyOcid + "," + + "keyPassword=" + (keyPassword == null ? "null" : "****") + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof OciCertificatesTlsManagerConfig other)) { + return false; + } + return Objects.equals(schedule, other.schedule()) + && Objects.equals(vaultCryptoEndpoint, other.vaultCryptoEndpoint()) + && Objects.equals(vaultManagementEndpoint, other.vaultManagementEndpoint()) + && Objects.equals(compartmentOcid, other.compartmentOcid()) + && Objects.equals(caOcid, other.caOcid()) + && Objects.equals(certOcid, other.certOcid()) + && Objects.equals(keyOcid, other.keyOcid()) + && Objects.equals(keyPassword, other.keyPassword()); + } + + @Override + public int hashCode() { + return Objects.hash(schedule, vaultCryptoEndpoint, vaultManagementEndpoint, compartmentOcid, caOcid, certOcid, + keyOcid, keyPassword); + } + + } + + } + + /** + * Fluent API builder for {@link OciCertificatesTlsManager}. + */ + class Builder extends OciCertificatesTlsManagerConfig + .BuilderBase + implements io.helidon.common.Builder { + + private Builder() { + } + + /** + * Build the config instance. + * + * @return the built config instance + */ + // @Override + public OciCertificatesTlsManagerConfig buildPrototype() { + preBuildPrototype(); + validatePrototype(); + return new OciCertificatesTlsManagerConfigImpl(this); + } + + /** + * Build the instance. + * + * @return the built instance + */ + // @Override + public OciCertificatesTlsManager build() { + return OciCertificatesTlsManager.create(this.buildPrototype()); + } + + } + +} diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java index c9393f6f1c9..1471e7b2ba9 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java @@ -20,20 +20,17 @@ import java.util.Optional; import java.util.function.Supplier; -import io.helidon.builder.api.Prototype; -import io.helidon.config.metadata.Configured; import io.helidon.config.metadata.ConfiguredOption; /** * Blueprint configuration for {@link OciCertificatesTlsManager}. */ -@Prototype.Blueprint -@Configured -interface OciCertificatesTlsManagerConfigBlueprint extends Prototype.Factory { +//@Prototype.Blueprint +//@Configured +interface OciCertificatesTlsManagerConfigBlueprint /*extends Prototype.Factory*/ { /** - * The schedule for trigger a reload check, testing whether there is a new {@link io.helidon.common.tls.Tls} instance - * available. + * The schedule for trigger a reload check, testing whether there is a new certificate available. * * @return the schedule for reload */ diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java index b47e62b8f58..3a3660d603f 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java @@ -19,12 +19,10 @@ import java.security.cert.X509Certificate; import java.util.Objects; -import io.helidon.inject.api.Contract; - /** * The contract used for downloading certificates from OCI. */ -@Contract +//@Contract public interface OciCertificatesDownloader { /** diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java index 5e28d389a06..4280d5911a1 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java @@ -19,12 +19,10 @@ import java.net.URI; import java.security.PrivateKey; -import io.helidon.inject.api.Contract; - /** * The contract used for downloading private keys from OCI. */ -@Contract +//@Contract public interface OciPrivateKeyDownloader { /** diff --git a/integrations/oci/tls-certificates/src/main/java/module-info.java b/integrations/oci/tls-certificates/src/main/java/module-info.java index c57615445be..9bb52c1c6ff 100644 --- a/integrations/oci/tls-certificates/src/main/java/module-info.java +++ b/integrations/oci/tls-certificates/src/main/java/module-info.java @@ -18,35 +18,34 @@ * Helidon Integrations of OCI Certificates Service. */ module io.helidon.integrations.oci.tls.certificates { - requires static io.helidon.config.metadata; requires static jakarta.annotation; - requires static jakarta.inject; + requires static io.helidon.config.metadata; - requires io.helidon.builder.api; - requires io.helidon.common; - requires io.helidon.common.config; requires io.helidon.common.pki; - requires io.helidon.common.tls; + requires io.helidon.common.serviceloader; requires io.helidon.config; requires io.helidon.faulttolerance; requires io.helidon.integrations.oci.sdk.runtime; - requires io.helidon.inject.api; - requires io.helidon.inject.runtime; requires io.helidon.scheduling; + requires io.helidon.webserver; + + requires jakarta.inject; requires oci.java.sdk.common; requires oci.java.sdk.certificates; requires oci.java.sdk.keymanagement; - uses io.helidon.common.tls.spi.TlsManagerProvider; + uses io.helidon.webserver.spi.TlsManagerProvider; uses io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; uses io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; exports io.helidon.integrations.oci.tls.certificates; exports io.helidon.integrations.oci.tls.certificates.spi; - provides io.helidon.common.tls.spi.TlsManagerProvider + provides io.helidon.webserver.spi.TlsManagerProvider with io.helidon.integrations.oci.tls.certificates.DefaultOciCertificatesTlsManagerProvider; - provides io.helidon.inject.api.ModuleComponent - with io.helidon.integrations.oci.tls.certificates.Injection$$Module; + provides io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader + with io.helidon.integrations.oci.tls.certificates.DefaultOciCertificatesDownloader; + provides io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader + with io.helidon.integrations.oci.tls.certificates.DefaultOciPrivateKeyDownloader; } diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java index 26845908d94..e90408da891 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java @@ -20,16 +20,16 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import io.helidon.common.tls.Tls; import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.Services; import io.helidon.microprofile.server.Server; +import io.helidon.webserver.WebServerTls; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static io.helidon.integrations.oci.tls.certificates.InjectionServices.Services; +import static io.helidon.integrations.oci.tls.certificates.InjectionServices.realizedServices; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -47,11 +47,8 @@ void reset() { } @Test - // left here since the repeat ensured the integrity the async code where container startup & shutdown showed some side affects - // that were tricky to reproduce. - // @RepeatedTest(10) void serverRuntime() throws Exception { - Services services = InjectionServices.realizedServices(); + Services services = realizedServices(); LifecycleHook lifecycleHook = services.lookupFirst(LifecycleHook.class).get(); CountDownLatch startup = new CountDownLatch(1); @@ -122,8 +119,8 @@ void configIsMonitoredForChange() throws Exception { pkDownloadCountBaseLine0, equalTo(0)); - Tls tls = Tls.create(tlsConfig); - assertThat(tls.prototype().manager(), + WebServerTls tls = WebServerTls.create(tlsConfig); + assertThat(tls.manager(), instanceOf(DefaultOciCertificatesTlsManager.class)); int certDownloadCountBaseline = TestOciCertificatesDownloader.callCount_loadCertificates; diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java index fe5dd448ddc..9ef1e955297 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java @@ -23,20 +23,34 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; -import io.helidon.common.Weight; -import io.helidon.common.Weighted; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; import jakarta.inject.Singleton; +/** + * For testing. + */ @Singleton -@Weight(Weighted.DEFAULT_WEIGHT + 1) -class TestOciCertificatesDownloader extends DefaultOciCertificatesDownloader { +public class TestOciCertificatesDownloader extends DefaultOciCertificatesDownloader { static String version = "1"; static int callCount_loadCertificates; static int callCount_loadCACertificate; + /** + * Service loader based constructor. + * + * @deprecated this is a Java ServiceLoader implementation and the constructor should not be used directly + */ + @Deprecated + public TestOciCertificatesDownloader() { + } + + @Override + public int priority() { + return DEFAULT_PRIORITY - 1; + } + @Override public Certificates loadCertificates(String certOcid) { callCount_loadCertificates++; diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java index a0383ac4438..e543b98c71f 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java @@ -20,19 +20,33 @@ import java.security.PrivateKey; import java.util.Objects; -import io.helidon.common.Weight; -import io.helidon.common.Weighted; -import io.helidon.common.pki.Keys; +import io.helidon.common.pki.KeyConfig; import io.helidon.config.Config; import jakarta.inject.Singleton; +/** + * For testing. + */ @Singleton -@Weight(Weighted.DEFAULT_WEIGHT + 1) -class TestOciPrivateKeyDownloader extends DefaultOciPrivateKeyDownloader { +public class TestOciPrivateKeyDownloader extends DefaultOciPrivateKeyDownloader { static int callCount; + /** + * Service loader based constructor. + * + * @deprecated this is a Java ServiceLoader implementation and the constructor should not be used directly + */ + @Deprecated + public TestOciPrivateKeyDownloader() { + } + + @Override + public int priority() { + return DEFAULT_PRIORITY - 1; + } + @Override public PrivateKey loadKey(String keyOcid, URI vaultCryptoEndpoint) { @@ -45,7 +59,7 @@ public PrivateKey loadKey(String keyOcid, Objects.requireNonNull(keyOcid); Objects.requireNonNull(vaultCryptoEndpoint); - Keys keys = Keys.builder() + KeyConfig keys = KeyConfig.fullBuilder() .config(Config.create().get("test-keys")) .build(); return keys.privateKey().orElseThrow(); diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java index 5f2d27751b4..5ad60a1876f 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java @@ -34,7 +34,6 @@ @Singleton @SuppressWarnings("unused") -// consider relocating this to somewhere under inject public class TestingCdiExtension implements Extension, LifecycleHook { static volatile boolean shutdownCalled; static final AtomicInteger running = new AtomicInteger(); diff --git a/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.LifecycleHook b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.LifecycleHook new file mode 100644 index 00000000000..1b0d9f6e73b --- /dev/null +++ b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.LifecycleHook @@ -0,0 +1,17 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +io.helidon.integrations.oci.tls.certificates.TestingCdiExtension diff --git a/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader new file mode 100644 index 00000000000..2bee91a4a0e --- /dev/null +++ b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader @@ -0,0 +1,17 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +io.helidon.integrations.oci.tls.certificates.TestOciCertificatesDownloader diff --git a/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader new file mode 100644 index 00000000000..86abc7bd561 --- /dev/null +++ b/integrations/oci/tls-certificates/src/test/resources/META-INF/services/io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader @@ -0,0 +1,17 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +io.helidon.integrations.oci.tls.certificates.TestOciPrivateKeyDownloader diff --git a/integrations/oci/tls-certificates/src/test/resources/application.yaml b/integrations/oci/tls-certificates/src/test/resources/application.yaml index a578025b23e..31c5d6219de 100644 --- a/integrations/oci/tls-certificates/src/test/resources/application.yaml +++ b/integrations/oci/tls-certificates/src/test/resources/application.yaml @@ -30,8 +30,8 @@ server: tls: manager: oci-certificates-tls-manager: - # Download mTls context every 30 seconds - schedule: "0/30 * * * * ? *" + # Re-download mTls context never + schedule: "0 * * * * ? 2099" # Each OCI Vault has public crypto and management endpoints vault-crypto-endpoint: ${VAULT_CRYPTO_ENDPOINT} # Certification Authority in OCI we have signed rotated certificates with diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java b/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java index a4ef01b8973..2116065977f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java @@ -125,12 +125,10 @@ public void init(WebServerTls tlsConfig) { } try { - SecureRandom secureRandom = secureRandom(tlsConfig); KeyManagerFactory kmf = buildKmf(tlsConfig.privateKeyConfig()); TrustManagerFactory tmf = buildTmf(tlsConfig); initSslContext(tlsConfig, - secureRandom, kmf.getKeyManagers(), tmf.getTrustManagers()); } catch (IOException | GeneralSecurityException e) { @@ -142,37 +140,41 @@ public void init(WebServerTls tlsConfig) { * Initialize and set the SSL context given the provided configuration. * * @param tlsConfig the tls config - * @param secureRandom the secure random instance * @param keyManagers the key managers * @param trustManagers the trust managers */ protected void initSslContext(WebServerTls tlsConfig, - SecureRandom secureRandom, KeyManager[] keyManagers, TrustManager[] trustManagers) { try { // Initialize the SSLContext to work with our key managers. SSLContext sslContext = SSLContext.getInstance(tlsConfig.protocol()); - sslContext.init(keyManagers, trustManagers, secureRandom); - - SSLSessionContext serverSessionContext = sslContext.getServerSessionContext(); - if (serverSessionContext != null) { - int sessionCacheSize = tlsConfig.sessionCacheSize(); - if (sessionCacheSize > 0) { - serverSessionContext.setSessionCacheSize(sessionCacheSize); - } - int sessionTimeoutSecs = tlsConfig.sessionTimeoutSeconds(); - if (sessionTimeoutSecs > 0) { - serverSessionContext.setSessionTimeout(sessionTimeoutSecs); - } - } + sslContext.init(keyManagers, trustManagers, secureRandom(tlsConfig)); - this.sslContext = sslContext; + configureAndSet(tlsConfig, sslContext); } catch (GeneralSecurityException e) { throw new IllegalArgumentException("Failed to create SSLContext", e); } } + /** + * Called when there is a new {@link SSLContext}. + * + * @param tlsConfig the tls config + * @param keyManagers the key managers + * @param trustManagers the trust managers + * @deprecated this method will removed in a future release. + */ + @Deprecated + protected void reload(WebServerTls tlsConfig, + KeyManager[] keyManagers, + TrustManager[] trustManagers) { + initSslContext(tlsConfig, keyManagers, trustManagers); + + // notify subscribers + sslContextConsumers.forEach(c -> c.accept(sslContext)); + } + /** * Load secure random. * @@ -192,6 +194,36 @@ protected TrustManagerFactory trustAllTmf() { return new TrustAllManagerFactory(); } + /** + * Create a new trust manager factory based on the configuration (i.e., the algorithm and provider). + * + * @param tlsConfig TLS config + * @return a new trust manager factory + */ + protected TrustManagerFactory createTmf(WebServerTls tlsConfig) { + try { + return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("Unable to create trust manager factory", e); + } + } + + private void configureAndSet(WebServerTls tlsConfig, + SSLContext sslContext) { + SSLSessionContext serverSessionContext = sslContext.getServerSessionContext(); + if (serverSessionContext != null) { + int sessionCacheSize = tlsConfig.sessionCacheSize(); + if (sessionCacheSize > 0) { + serverSessionContext.setSessionCacheSize(sessionCacheSize); + } + int sessionTimeoutSecs = tlsConfig.sessionTimeoutSeconds(); + if (sessionTimeoutSecs > 0) { + serverSessionContext.setSessionTimeout(sessionTimeoutSecs); + } + } + this.sslContext = sslContext; + } + private KeyManagerFactory buildKmf(KeyConfig privateKeyConfig) throws IOException, GeneralSecurityException { String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); if (algorithm == null) { @@ -239,7 +271,7 @@ private TrustManagerFactory buildTmf(WebServerTls tlsConfig) i++; } - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory tmf = createTmf(tlsConfig); tmf.init(ks); return tmf; } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/TlsManager.java b/webserver/webserver/src/main/java/io/helidon/webserver/TlsManager.java index b732504a8a7..2339ac2f5e7 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/TlsManager.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/TlsManager.java @@ -44,7 +44,7 @@ public interface TlsManager { * Callers can subscribe to updates to be notified when the SSL context changes. * * @param sslContextConsumer the consumer that will receive the new/update context after it has been reloaded by the manager - * @deprecated This feature will not be carried forward into future releases. + * @deprecated this method will removed in a future release. */ @Deprecated void subscribe(Consumer sslContextConsumer); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/WebServerTls.java b/webserver/webserver/src/main/java/io/helidon/webserver/WebServerTls.java index d0c899fb063..5057c774fe8 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/WebServerTls.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/WebServerTls.java @@ -106,6 +106,18 @@ public TlsManager manager() { return tlsManager; } + /** + * Trust any certificate provided by the other side of communication. + *

+ * This is a dangerous setting: if set to {@code true}, any certificate will be accepted, throwing away + * most of the security advantages of TLS. NEVER do this in production. + * + * @return whether to trust all certificates, do not use in production + */ + public boolean trustAll() { + return trustAll; + } + Collection enabledTlsProtocols() { return enabledTlsProtocols; } @@ -122,10 +134,6 @@ SSLContext sslContext() { return manager().sslContext(); } - boolean trustAll() { - return trustAll; - } - KeyConfig privateKeyConfig() { return privateKeyConfig; } @@ -259,6 +267,7 @@ public Builder config(Config config) { */ @ConfiguredOption(provider = true) public Builder tlsManager(TlsManager tlsManager) { + this.enabled = true; this.tlsManager = Objects.requireNonNull(tlsManager); return this; }