From 80c837e0982071aacbde031ac9d75f4fde103c28 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 24 Jun 2022 18:59:53 +0100 Subject: [PATCH] Integrate Vertx HTTP with CredentialsProvider --- extensions/vertx-http/runtime/pom.xml | 4 ++ .../vertx/http/runtime/CertificateConfig.java | 70 +++++++++++++++++-- .../vertx/http/runtime/VertxHttpRecorder.java | 35 ++++++++-- .../it/bouncycastle/SecretProvider.java | 23 ++++++ .../src/main/resources/application.properties | 6 +- 5 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 integration-tests/bouncycastle-jsse/src/main/java/io/quarkus/it/bouncycastle/SecretProvider.java diff --git a/extensions/vertx-http/runtime/pom.xml b/extensions/vertx-http/runtime/pom.xml index 78f46b89d5822f..bc08421ca9738d 100644 --- a/extensions/vertx-http/runtime/pom.xml +++ b/extensions/vertx-http/runtime/pom.xml @@ -21,6 +21,10 @@ io.quarkus quarkus-security-runtime-spi + + io.quarkus + quarkus-credentials + io.quarkus quarkus-mutiny diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CertificateConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CertificateConfig.java index 544588de231a80..063528021af584 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CertificateConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CertificateConfig.java @@ -6,6 +6,8 @@ import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.configuration.TrimmedStringConverter; /** * A certificate configuration. Either the certificate and key files must be given, or a key store must be given. @@ -14,6 +16,28 @@ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class CertificateConfig { + /** + * The {@linkplain CredentialsProvider}} name. + * If this property is configured then a matching 'CredentialsProvider' will be used + * to get the keystore, keystore key and truststore passwords unless these passwords have already been configured. + * + * Please note that using MicroProfile {@linkplain ConfigSource} which is directly supported by Quarkus Configuration + * should be preferred unless using `CredentialsProvider` provides for some additional security and dynamism. + */ + @ConfigItem + @ConvertWith(TrimmedStringConverter.class) + public Optional credentialsProvider = Optional.empty(); + + /** + * The credentials provider bean name. + *

+ * It is the {@code @Named} value of the credentials provider bean. It is used to discriminate if multiple + * CredentialsProvider beans are available. Not necessary if there is only one credentials provider available. + */ + @ConfigItem + @ConvertWith(TrimmedStringConverter.class) + public Optional credentialsProviderName = Optional.empty(); + /** * The file path to a server certificate or certificate chain in PEM format. * @@ -69,10 +93,23 @@ public class CertificateConfig { public Optional keyStoreProvider; /** - * A parameter to specify the password of the key store file. If not given, the default ("password") is used. + * A parameter to specify the password of the key store file. If not given, and if it can not be retrieved from + * {@linkplain CredentialsProvider}, then the default ("password") is used. + * + * @see {@link #credentialsProvider} */ - @ConfigItem(defaultValue = "password") - public String keyStorePassword; + @ConfigItem(defaultValueDocumentation = "password") + public Optional keyStorePassword; + + /** + * A parameter to specify a {@linkplain CredentialsProvider} property key which can be used to get the password of the key + * store file + * from {@linkplain CredentialsProvider}. + * + * @see {@link #credentialsProvider} + */ + @ConfigItem + public Optional keyStorePasswordKey; /** * An optional parameter to select a specific key in the key store. When SNI is disabled, if the key store contains multiple @@ -82,11 +119,23 @@ public class CertificateConfig { public Optional keyStoreKeyAlias; /** - * An optional parameter to define the password for the key, in case it's different from {@link #keyStorePassword}. + * An optional parameter to define the password for the key, in case it's different from {@link #keyStorePassword} + * If not given then it may be retrieved from {@linkplain CredentialsProvider}. + * + * @see {@link #credentialsProvider}. */ @ConfigItem public Optional keyStoreKeyPassword; + /** + * A parameter to specify a {@linkplain CredentialsProvider} property key which can be used to get the password for the key + * from {@linkplain CredentialsProvider}. + * + * @see {@link #credentialsProvider} + */ + @ConfigItem + public Optional keyStoreKeyPasswordKey; + /** * An optional trust store which holds the certificate information of the certificates to trust. */ @@ -109,10 +158,23 @@ public class CertificateConfig { /** * A parameter to specify the password of the trust store file. + * If not given then it may be retrieved from {@linkplain CredentialsProvider}. + * + * @see {@link #credentialsProvider}. */ @ConfigItem public Optional trustStorePassword; + /** + * A parameter to specify a {@linkplain CredentialsProvider} property key which can be used to get the password of the trust + * store file + * from {@linkplain CredentialsProvider}. + * + * @see {@link #credentialsProvider} + */ + @ConfigItem + public Optional trustStorePasswordKey; + /** * An optional parameter to trust only one specific certificate in the trust store (instead of trusting all certificates in * the store). diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index 1eafc0a9a09a44..10b81f2e655d1f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -49,6 +49,8 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.bootstrap.runner.Timing; +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.dev.spi.DevModeType; import io.quarkus.dev.spi.HotReplacementContext; import io.quarkus.netty.runtime.virtual.VirtualAddress; @@ -781,10 +783,22 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC certificates.add(certFile.get()); } + // credentials provider + Map credentials = Map.of(); + if (sslConfig.certificate.credentialsProvider.isPresent()) { + String beanName = sslConfig.certificate.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = sslConfig.certificate.credentialsProvider.get(); + credentials = credentialsProvider.getCredentials(name); + } final Optional keyStoreFile = sslConfig.certificate.keyStoreFile; - final String keystorePassword = sslConfig.certificate.keyStorePassword; + final Optional keyStorePassword = getCredential(sslConfig.certificate.keyStorePassword, credentials, + sslConfig.certificate.keyStorePasswordKey); + final Optional keyStoreKeyPassword = getCredential(sslConfig.certificate.keyStoreKeyPassword, credentials, + sslConfig.certificate.keyStoreKeyPasswordKey); final Optional trustStoreFile = sslConfig.certificate.trustStoreFile; - final Optional trustStorePassword = sslConfig.certificate.trustStorePassword; + final Optional trustStorePassword = getCredential(sslConfig.certificate.trustStorePassword, credentials, + sslConfig.certificate.trustStorePasswordKey); final HttpServerOptions serverOptions = new HttpServerOptions(); //ssl @@ -801,11 +815,11 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC } else if (keyStoreFile.isPresent()) { KeyStoreOptions options = createKeyStoreOptions( keyStoreFile.get(), - keystorePassword, + keyStorePassword.orElse("password"), sslConfig.certificate.keyStoreFileType, sslConfig.certificate.keyStoreProvider, sslConfig.certificate.keyStoreKeyAlias, - sslConfig.certificate.keyStoreKeyPassword); + keyStoreKeyPassword); serverOptions.setKeyCertOptions(options); } else { return null; @@ -846,6 +860,19 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC return serverOptions; } + private static Optional getCredential(Optional password, Map credentials, + Optional passwordKey) { + if (password.isPresent()) { + return password; + } + + if (passwordKey.isPresent()) { + return Optional.ofNullable(credentials.get(passwordKey.get())); + } else { + return Optional.empty(); + } + } + private static void applyCommonOptions(HttpServerOptions httpServerOptions, HttpBuildTimeConfig buildTimeConfig, HttpConfiguration httpConfiguration, diff --git a/integration-tests/bouncycastle-jsse/src/main/java/io/quarkus/it/bouncycastle/SecretProvider.java b/integration-tests/bouncycastle-jsse/src/main/java/io/quarkus/it/bouncycastle/SecretProvider.java new file mode 100644 index 00000000000000..9eed67c40298d2 --- /dev/null +++ b/integration-tests/bouncycastle-jsse/src/main/java/io/quarkus/it/bouncycastle/SecretProvider.java @@ -0,0 +1,23 @@ +package io.quarkus.it.bouncycastle; + +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.credentials.CredentialsProvider; + +@ApplicationScoped +@Unremovable +public class SecretProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + Map creds = new HashMap<>(); + creds.put("keystore-password", "password"); + creds.put("truststore-password", "password"); + return creds; + } + +} diff --git a/integration-tests/bouncycastle-jsse/src/main/resources/application.properties b/integration-tests/bouncycastle-jsse/src/main/resources/application.properties index 4c0d0e07a2f405..40f0793cd8e70f 100644 --- a/integration-tests/bouncycastle-jsse/src/main/resources/application.properties +++ b/integration-tests/bouncycastle-jsse/src/main/resources/application.properties @@ -1,9 +1,11 @@ quarkus.security.security-providers=BCJSSE quarkus.http.ssl.certificate.key-store-file=server-keystore.jks -quarkus.http.ssl.certificate.key-store-password=password +quarkus.http.ssl.certificate.key-store-password-key=key-store-password quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks -quarkus.http.ssl.certificate.trust-store-password=password +quarkus.http.ssl.certificate.trust-store-password-key=truststore-password +quarkus.http.ssl.certificate.credentials-provider=custom + quarkus.http.ssl.client-auth=REQUIRED quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks