From 121c846f705ace52714db820ec4243ee6eae6365 Mon Sep 17 00:00:00 2001 From: Melloware Date: Tue, 19 Nov 2024 09:29:46 -0500 Subject: [PATCH] Fix #126: Allow API Key usage for Temporal Cloud (#128) --- .../ROOT/pages/includes/quarkus-temporal.adoc | 17 +++++++ .../quarkus-temporal_quarkus.temporal.adoc | 17 +++++++ docs/modules/ROOT/pages/index.adoc | 32 +++++++++++-- .../WorkflowServiceStubsRecorder.java | 47 +++++++++++++------ .../config/ConnectionRuntimeConfig.java | 8 ++++ 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/docs/modules/ROOT/pages/includes/quarkus-temporal.adoc b/docs/modules/ROOT/pages/includes/quarkus-temporal.adoc index cd3a8e4..2521fb4 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-temporal.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-temporal.adoc @@ -731,6 +731,23 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a| [[quarkus-temporal_quarkus-temporal-connection-api-key]] [.property-path]##link:#quarkus-temporal_quarkus-temporal-connection-api-key[`quarkus.temporal.connection.api-key`]## + +[.description] +-- +Temporal Cloud API key is a unique identity linked to role-based access control (RBAC) settings to ensure secure and appropriate access. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_TEMPORAL_CONNECTION_API_KEY+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_TEMPORAL_CONNECTION_API_KEY+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + a| [[quarkus-temporal_quarkus-temporal-connection-rpc-retry-initial-interval]] [.property-path]##link:#quarkus-temporal_quarkus-temporal-connection-rpc-retry-initial-interval[`quarkus.temporal.connection.rpc-retry.initial-interval`]## [.description] diff --git a/docs/modules/ROOT/pages/includes/quarkus-temporal_quarkus.temporal.adoc b/docs/modules/ROOT/pages/includes/quarkus-temporal_quarkus.temporal.adoc index cd3a8e4..2521fb4 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-temporal_quarkus.temporal.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-temporal_quarkus.temporal.adoc @@ -731,6 +731,23 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a| [[quarkus-temporal_quarkus-temporal-connection-api-key]] [.property-path]##link:#quarkus-temporal_quarkus-temporal-connection-api-key[`quarkus.temporal.connection.api-key`]## + +[.description] +-- +Temporal Cloud API key is a unique identity linked to role-based access control (RBAC) settings to ensure secure and appropriate access. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_TEMPORAL_CONNECTION_API_KEY+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_TEMPORAL_CONNECTION_API_KEY+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + a| [[quarkus-temporal_quarkus-temporal-connection-rpc-retry-initial-interval]] [.property-path]##link:#quarkus-temporal_quarkus-temporal-connection-rpc-retry-initial-interval[`quarkus.temporal.connection.rpc-retry.initial-interval`]## [.description] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 3186762..ffcd24c 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -477,24 +477,46 @@ public class TestWorkflowClientInterceptor implements WorkflowClientInterceptor == Temporal Cloud -You may be using the Temporal Cloud offering instead of self-hosting. Each Temporal Cloud service Client has four prerequisites. +You may be using the Temporal Cloud offering instead of self-hosting. Temporal Cloud supports both TLS and API key authentication. -* The full Namespace ID from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page -* The gRPC endpoint from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page +=== TLS Authentication + +You must provide your own CA certificates. These certificates are needed to create a Namespace, which are in turn used to grant Temporal Clients and Workers access to it. You will need: + +* The full Namespace ID from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page such as `.` +* The gRPC endpoint from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page such as `..tmprl.cloud:7233` * Your mTLS private key * Your mTLS x509 Certificate -To configure with Quarkus Temporal: +To configure with Quarkus Temporal TLS: [source,properties] ---- quarkus.temporal.namespace=your-namespace.123def -quarkus.temporal.connection=your-namespace.123def.tmprl.cloud:7233 +quarkus.temporal.connection.target=your-namespace.123def.tmprl.cloud:7233 quarkus.temporal.connection.mtls.client-cert-path=/your-temporal-x509.cert quarkus.temporal.connection.mtls.client-key-path=/your-temporal.key quarkus.temporal.connection.mtls.password=Passw0rd ---- +=== API Key Authentication + +Each Temporal Cloud API key is a unique identity linked to role-based access control (RBAC) settings to ensure secure and appropriate access. + +You will need: + +* The full Namespace ID from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page such as `.` +* The gRPC endpoint from the https://cloud.temporal.io/namespaces[Cloud Namespace] details page such as `..api.temporal.io:7233.` +* Your API key + +To configure with Quarkus Temporal API key: + +[source,properties] +---- +quarkus.temporal.namespace=. +quarkus.temporal.connection.target=..api.temporal.io:7233 +quarkus.temporal.connection.api-key= +---- [[extension-configuration-reference]] == Extension Configuration Reference diff --git a/extension/runtime/src/main/java/io/quarkiverse/temporal/WorkflowServiceStubsRecorder.java b/extension/runtime/src/main/java/io/quarkiverse/temporal/WorkflowServiceStubsRecorder.java index e35f12a..e9e5161 100644 --- a/extension/runtime/src/main/java/io/quarkiverse/temporal/WorkflowServiceStubsRecorder.java +++ b/extension/runtime/src/main/java/io/quarkiverse/temporal/WorkflowServiceStubsRecorder.java @@ -15,6 +15,8 @@ import io.grpc.Channel; import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.stub.MetadataUtils; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; import io.quarkiverse.temporal.config.ConnectionRuntimeConfig; @@ -26,6 +28,7 @@ import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.runtime.util.ClassPathUtils; +import io.temporal.authorization.AuthorizationGrpcMetadataProvider; import io.temporal.common.reporter.MicrometerClientStatsReporter; import io.temporal.serviceclient.RpcRetryOptions; import io.temporal.serviceclient.SimpleSslContextBuilder; @@ -69,21 +72,35 @@ WorkflowServiceStubsOptions createWorkflowServiceStubsOptions( .setTarget(connection.target()) .setEnableHttps(connection.enableHttps()); - MTLSRuntimeConfig mtls = connection.mtls(); - - if (mtls.clientCertPath().isPresent() != mtls.clientKeyPath().isPresent()) { - throw new ConfigurationException("Both client cert and key must be provided"); - } + // API KEY + if (connection.apiKey().isPresent()) { + // Create a Metadata object with the Temporal namespace header key. + Metadata.Key key = Metadata.Key.of("temporal-namespace", Metadata.ASCII_STRING_MARSHALLER); + Metadata metadata = new Metadata(); + metadata.put(key, runtimeConfig.namespace()); + builder.setChannelInitializer( + (channel) -> { + channel.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata)); + }); + builder.addGrpcMetadataProvider(new AuthorizationGrpcMetadataProvider(() -> "Bearer " + connection.apiKey())); + } else { + // Mutual Transport Layer Security + MTLSRuntimeConfig mtls = connection.mtls(); + + if (mtls.clientCertPath().isPresent() != mtls.clientKeyPath().isPresent()) { + throw new ConfigurationException("Both client cert and key must be provided"); + } - if (mtls.clientCertPath().isPresent() && mtls.clientKeyPath().isPresent()) { - try { - SimpleSslContextBuilder sslContextBuilder = SimpleSslContextBuilder.forPKCS8( - read(mtls.clientCertPath().get()), - read(mtls.clientKeyPath().get())); - mtls.password().ifPresent(sslContextBuilder::setKeyPassword); - builder.setSslContext(sslContextBuilder.build()); - } catch (SSLException e) { - throw new ConfigurationException("Failed to create SSL context", e); + if (mtls.clientCertPath().isPresent() && mtls.clientKeyPath().isPresent()) { + try { + SimpleSslContextBuilder sslContextBuilder = SimpleSslContextBuilder.forPKCS8( + read(mtls.clientCertPath().get()), + read(mtls.clientKeyPath().get())); + mtls.password().ifPresent(sslContextBuilder::setKeyPassword); + builder.setSslContext(sslContextBuilder.build()); + } catch (SSLException e) { + throw new ConfigurationException("Failed to create SSL context", e); + } } } @@ -166,4 +183,4 @@ static InputStream read(Path path) { throw new ConfigurationException("Client cert or key file not found", e); } } -} +} \ No newline at end of file diff --git a/extension/runtime/src/main/java/io/quarkiverse/temporal/config/ConnectionRuntimeConfig.java b/extension/runtime/src/main/java/io/quarkiverse/temporal/config/ConnectionRuntimeConfig.java index 07282aa..a80c56d 100644 --- a/extension/runtime/src/main/java/io/quarkiverse/temporal/config/ConnectionRuntimeConfig.java +++ b/extension/runtime/src/main/java/io/quarkiverse/temporal/config/ConnectionRuntimeConfig.java @@ -1,5 +1,7 @@ package io.quarkiverse.temporal.config; +import java.util.Optional; + import io.grpc.ManagedChannelBuilder; import io.grpc.NameResolver; import io.quarkus.runtime.annotations.ConfigGroup; @@ -22,6 +24,12 @@ public interface ConnectionRuntimeConfig { @WithDefault("false") Boolean enableHttps(); + /** + * Temporal Cloud API key is a unique identity linked to role-based access control (RBAC) settings to ensure secure and + * appropriate access. + */ + Optional apiKey(); + /** * Rpc Retry Options. */