diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ae93d03a..ee799ca0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -98,8 +98,8 @@ jobs:
- name: Confirm bench works
run: dotnet run --project tests/Temporalio.SimpleBench/Temporalio.SimpleBench.csproj -- --workflow-count 5 --max-cached-workflows 100 --max-concurrent 100
- - name: Test cloud
- # Only supported in non-fork runs, since secrets are not available in forks
+ - name: Test cloud (mTLS)
+ # Only supported in non-fork runs, since secrets are not available in forks.
if: ${{ matrix.cloudTestTarget && (github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-dotnet') }}
env:
TEMPORAL_TEST_CLIENT_TARGET_HOST: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }}.tmprl.cloud:7233
@@ -108,6 +108,16 @@ jobs:
TEMPORAL_TEST_CLIENT_KEY: ${{ secrets.TEMPORAL_CLIENT_KEY }}
run: dotnet run --project tests/Temporalio.Tests -- -verbose -method "*.ExecuteWorkflowAsync_Simple_Succeeds"
+ - name: Test cloud (API key)
+ # Only supported in non-fork runs, since secrets are not available in forks.
+ # Uses API key auth to test auto-TLS feature (TLS auto-enabled when API key provided).
+ if: ${{ matrix.cloudTestTarget && (github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-dotnet') }}
+ env:
+ TEMPORAL_TEST_CLIENT_TARGET_HOST: us-west-2.aws.api.temporal.io:7233
+ TEMPORAL_TEST_CLIENT_NAMESPACE: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }}
+ TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }}
+ run: dotnet run --project tests/Temporalio.Tests -- -verbose -method "*.ExecuteWorkflowAsync_Simple_Succeeds"
+
- name: Test cloud operations client
# Only supported in non-fork runs, since secrets are not available in forks
if: ${{ matrix.cloudTestTarget && (github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-dotnet') }}
diff --git a/src/Temporalio/Bridge/OptionsExtensions.cs b/src/Temporalio/Bridge/OptionsExtensions.cs
index 2ad8cd19..3b9214eb 100644
--- a/src/Temporalio/Bridge/OptionsExtensions.cs
+++ b/src/Temporalio/Bridge/OptionsExtensions.cs
@@ -241,7 +241,19 @@ public static unsafe Interop.TemporalCoreClientOptions ToInteropOptions(
{
throw new ArgumentException("Identity missing from options.");
}
- var scheme = options.Tls == null ? "http" : "https";
+
+ // Auto-enable TLS when API key is provided and TLS is not explicitly disabled
+ var tls = options.Tls;
+ if (tls?.Disabled == true)
+ {
+ tls = null;
+ }
+ else if (tls == null && !string.IsNullOrEmpty(options.ApiKey))
+ {
+ tls = new Temporalio.Client.TlsOptions();
+ }
+
+ var scheme = tls == null ? "http" : "https";
return new Interop.TemporalCoreClientOptions()
{
target_url = scope.ByteArray($"{scheme}://{options.TargetHost}"),
@@ -251,7 +263,7 @@ public static unsafe Interop.TemporalCoreClientOptions ToInteropOptions(
api_key = scope.ByteArray(options.ApiKey),
identity = scope.ByteArray(options.Identity),
tls_options =
- options.Tls == null ? null : scope.Pointer(options.Tls.ToInteropOptions(scope)),
+ tls == null ? null : scope.Pointer(tls.ToInteropOptions(scope)),
retry_options =
options.RpcRetry == null
? null
diff --git a/src/Temporalio/Client/TemporalConnectionOptions.cs b/src/Temporalio/Client/TemporalConnectionOptions.cs
index 5ab5016f..733ec85f 100644
--- a/src/Temporalio/Client/TemporalConnectionOptions.cs
+++ b/src/Temporalio/Client/TemporalConnectionOptions.cs
@@ -38,7 +38,9 @@ public TemporalConnectionOptions()
/// Gets or sets the TLS options for connection.
///
///
- /// This must be set, even to a default instance, to do any TLS connection.
+ /// This must be set, even to a default instance, to do any TLS connection. If not set and
+ /// is provided, TLS will be automatically enabled with default options.
+ /// To explicitly disable TLS, set instance with set to true.
///
public TlsOptions? Tls { get; set; }
diff --git a/src/Temporalio/Client/TlsOptions.cs b/src/Temporalio/Client/TlsOptions.cs
index 9cc1caf8..38565a65 100644
--- a/src/Temporalio/Client/TlsOptions.cs
+++ b/src/Temporalio/Client/TlsOptions.cs
@@ -34,6 +34,15 @@ public class TlsOptions : ICloneable
///
public byte[]? ClientPrivateKey { get; set; }
+ ///
+ /// Gets or sets a value indicating whether TLS should be explicitly disabled.
+ ///
+ ///
+ /// When set to true, TLS will be disabled even when an API key is provided (which normally
+ /// auto-enables TLS).
+ ///
+ public bool Disabled { get; set; }
+
///
/// Create a shallow copy of these options.
///
diff --git a/tests/Temporalio.Tests/Client/TemporalConnectionOptionsTests.cs b/tests/Temporalio.Tests/Client/TemporalConnectionOptionsTests.cs
new file mode 100644
index 00000000..e5314b64
--- /dev/null
+++ b/tests/Temporalio.Tests/Client/TemporalConnectionOptionsTests.cs
@@ -0,0 +1,72 @@
+namespace Temporalio.Tests.Client;
+
+using Temporalio.Bridge;
+using Temporalio.Client;
+using Xunit;
+
+public unsafe class TemporalConnectionOptionsTests
+{
+ [Fact]
+ public void ToInteropOptions_AutoEnablesTls_WhenApiKeyProvidedAndTlsNotSet()
+ {
+ var options = new TemporalConnectionOptions("localhost:7233")
+ {
+ ApiKey = "test-api-key",
+ Identity = "test-identity",
+ };
+
+ using var scope = new Scope();
+ var interopOptions = options.ToInteropOptions(scope);
+
+ // TLS should be auto-enabled when API key is provided and TLS not set
+ Assert.True(interopOptions.tls_options != null);
+ }
+
+ [Fact]
+ public void ToInteropOptions_TlsDisabled_WhenExplicitlyDisabledWithApiKey()
+ {
+ var options = new TemporalConnectionOptions("localhost:7233")
+ {
+ ApiKey = "test-api-key",
+ Identity = "test-identity",
+ Tls = new TlsOptions { Disabled = true },
+ };
+
+ using var scope = new Scope();
+ var interopOptions = options.ToInteropOptions(scope);
+
+ // TLS should remain disabled when explicitly disabled, even with API key
+ Assert.True(interopOptions.tls_options == null);
+ }
+
+ [Fact]
+ public void ToInteropOptions_TlsDisabled_WhenNoApiKeyAndTlsNotSet()
+ {
+ var options = new TemporalConnectionOptions("localhost:7233")
+ {
+ Identity = "test-identity",
+ };
+
+ using var scope = new Scope();
+ var interopOptions = options.ToInteropOptions(scope);
+
+ // TLS should be disabled when no API key and TLS not set
+ Assert.True(interopOptions.tls_options == null);
+ }
+
+ [Fact]
+ public void ToInteropOptions_TlsEnabled_WhenExplicitlySet()
+ {
+ var options = new TemporalConnectionOptions("localhost:7233")
+ {
+ Identity = "test-identity",
+ Tls = new TlsOptions(),
+ };
+
+ using var scope = new Scope();
+ var interopOptions = options.ToInteropOptions(scope);
+
+ // TLS should be enabled when explicitly set
+ Assert.True(interopOptions.tls_options != null);
+ }
+}
diff --git a/tests/Temporalio.Tests/Temporalio.Tests.csproj b/tests/Temporalio.Tests/Temporalio.Tests.csproj
index b76421cd..96534999 100644
--- a/tests/Temporalio.Tests/Temporalio.Tests.csproj
+++ b/tests/Temporalio.Tests/Temporalio.Tests.csproj
@@ -1,6 +1,7 @@
+ true
true
false
enable
diff --git a/tests/Temporalio.Tests/WorkflowEnvironment.cs b/tests/Temporalio.Tests/WorkflowEnvironment.cs
index 82dced9a..43653835 100644
--- a/tests/Temporalio.Tests/WorkflowEnvironment.cs
+++ b/tests/Temporalio.Tests/WorkflowEnvironment.cs
@@ -40,7 +40,8 @@ public async Task InitializeAsync()
};
var clientCert = Environment.GetEnvironmentVariable("TEMPORAL_TEST_CLIENT_CERT");
var clientKey = Environment.GetEnvironmentVariable("TEMPORAL_TEST_CLIENT_KEY");
- if ((clientCert == null) != (clientKey == null))
+ var apiKey = Environment.GetEnvironmentVariable("TEMPORAL_CLIENT_CLOUD_API_KEY");
+ if (clientCert == null != (clientKey == null))
{
throw new InvalidOperationException("Must have both cert/key or neither");
}
@@ -52,6 +53,11 @@ public async Task InitializeAsync()
ClientPrivateKey = System.Text.Encoding.ASCII.GetBytes(clientKey),
};
}
+ if (apiKey != null)
+ {
+ // API key auto-enables TLS when Tls is not set
+ options.ApiKey = apiKey;
+ }
env = new(await TemporalClient.ConnectAsync(options));
}
else