Skip to content

Commit 1baffcb

Browse files
committed
Add mTLS sample.
Closes #7.
1 parent 4621d03 commit 1baffcb

6 files changed

Lines changed: 279 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Prerequisites:
2121
<!-- Keep this list in alphabetical order -->
2222
* [activity_simple](activity_simple) - Simple workflow that calls two activities.
2323
* [activity_worker](activity_worker) - Use Ruby activities from a workflow in another language.
24+
* [client_mtls](client_mtls) - Demonstrates how to use mutual TLS (mTLS) authentication with the Temporal Ruby SDK.
2425
* [coinbase_ruby](coinbase_ruby) - Demonstrate interoperability with the
2526
[Coinbase Ruby SDK](https://github.com/coinbase/temporal-ruby).
2627
* [context_propagation](context_propagation) - Use interceptors to propagate thread/fiber local data from clients

client_mtls/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Client mTLS Sample
2+
3+
This sample demonstrates how to use mutual TLS (mTLS) authentication with the Temporal Ruby SDK.
4+
5+
## Overview
6+
7+
mTLS (mutual Transport Layer Security) provides secure, encrypted communication where both client and server authenticate each other. This is required to connect to Temporal Cloud, or to a self-hosted Temporal deployment that is secured with TLS.
8+
9+
The sample includes:
10+
11+
- A simple workflow that executes an activity
12+
- A worker and starter that accept certificate parameters for mTLS authentication
13+
- Command-line options to configure connection parameters
14+
15+
## Prerequisites
16+
17+
Before running this sample, you'll need:
18+
19+
1. A Temporal server with mTLS enabled
20+
2. TLS certificates:
21+
- Client certificate and private key
22+
- Server root CA certificate (optional, depending on your setup)
23+
24+
## Running the Sample
25+
26+
### 1. Start the Worker
27+
28+
```bash
29+
ruby worker.rb \
30+
--client-cert /path/to/client.pem \
31+
--client-key /path/to/client.key \
32+
[--server-root-ca-cert /path/to/ca.pem] \
33+
[--target-host your-temporal-server:7233] \
34+
[--namespace your-namespace] \
35+
[--task-queue custom-task-queue]
36+
```
37+
38+
### 2. Execute the Workflow
39+
40+
In a separate terminal:
41+
42+
```bash
43+
ruby starter.rb \
44+
--client-cert /path/to/client.pem \
45+
--client-key /path/to/client.key \
46+
[--server-root-ca-cert /path/to/ca.pem] \
47+
[--target-host your-temporal-server:7233] \
48+
[--namespace your-namespace] \
49+
[--task-queue custom-task-queue]
50+
```
51+
52+
## Common Configurations
53+
54+
### Temporal Cloud with mTLS
55+
56+
When connecting to Temporal Cloud:
57+
58+
- **Address**: Use the mTLS endpoint from Temporal Cloud (e.g., `namespace.tmprl.cloud:7233`)
59+
- **Namespace**: Include the account identifier suffix (e.g., `my-namespace.abc123`)
60+
- **Server Root CA Certificate**: Not typically needed as Temporal Cloud uses well-known Root CAs
61+
62+
### Self-Hosted Temporal with mTLS
63+
64+
For a self-hosted Temporal cluster:
65+
66+
- **Server Root CA Certificate**: Required if your server uses a certificate signed by a private CA
67+
- You'll need both client certificate and key files
68+
69+
## Notes on Certificate Files
70+
71+
Certificate and key files should be in PEM format. The client certificate file may include the full certificate chain if needed.
72+
73+
## Troubleshooting
74+
75+
- If you see TLS handshake errors, verify your certificate paths are correct
76+
- Make sure certificates haven't expired
77+
- For Temporal Cloud, confirm you're using the correct endpoint and namespace

client_mtls/my_activities.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
require 'temporalio/activity'
4+
5+
module ClientMtls
6+
module Activities
7+
# Simple activity definition following SDK patterns
8+
class ComposeGreeting < Temporalio::Activity::Definition
9+
def execute(greeting, name)
10+
"#{greeting}, #{name}!"
11+
end
12+
end
13+
end
14+
end

client_mtls/my_workflow.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'my_activities'
4+
require 'temporalio/workflow'
5+
6+
module ClientMtls
7+
class GreetingWorkflow < Temporalio::Workflow::Definition
8+
def execute(name)
9+
# Execute activity and return result
10+
Temporalio::Workflow.execute_activity(
11+
Activities::ComposeGreeting,
12+
'Hello',
13+
name,
14+
start_to_close_timeout: 10
15+
)
16+
end
17+
end
18+
end

client_mtls/starter.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'my_workflow'
4+
require 'optparse'
5+
require 'temporalio/client'
6+
7+
# Define options parser to handle certificate paths and other parameters
8+
options = {
9+
target_host: 'localhost:7233',
10+
namespace: 'default',
11+
task_queue: 'mtls-task-queue',
12+
server_root_ca_cert: nil,
13+
client_cert: nil,
14+
client_key: nil
15+
}
16+
17+
OptionParser.new do |opts|
18+
opts.banner = 'Usage: starter.rb [options]'
19+
20+
opts.on('--target-host HOST', 'Host:port for the server (default: localhost:7233)') do |v|
21+
options[:target_host] = v
22+
end
23+
24+
opts.on('--namespace NAMESPACE', 'Namespace to use (default: default)') do |v|
25+
options[:namespace] = v
26+
end
27+
28+
opts.on('--task-queue QUEUE', 'Task queue to use (default: mtls-task-queue)') do |v|
29+
options[:task_queue] = v
30+
end
31+
32+
opts.on('--server-root-ca-cert PATH', 'Path to the server root CA certificate') do |v|
33+
options[:server_root_ca_cert] = v
34+
end
35+
36+
opts.on('--client-cert PATH', 'Path to the client certificate (required for mTLS)') do |v|
37+
options[:client_cert] = v
38+
end
39+
40+
opts.on('--client-key PATH', 'Path to the client key (required for mTLS)') do |v|
41+
options[:client_key] = v
42+
end
43+
end.parse!
44+
45+
# Check for required certificates for mTLS
46+
if options[:client_cert].nil? || options[:client_key].nil?
47+
puts 'Error: Client certificate and key are required for mTLS'
48+
puts 'Usage: ruby starter.rb --client-cert PATH --client-key PATH'
49+
exit 1
50+
end
51+
52+
puts "Connecting to Temporal Server at #{options[:target_host]} with mTLS..."
53+
puts "Using namespace: #{options[:namespace]}"
54+
55+
# Create client with mTLS configuration
56+
tls_options = Temporalio::Client::Connection::TLSOptions.new(
57+
client_cert: File.read(options[:client_cert]),
58+
client_private_key: File.read(options[:client_key])
59+
)
60+
61+
# Add server root CA cert if provided
62+
if options[:server_root_ca_cert]
63+
tls_options = tls_options.with(server_root_ca_cert: File.read(options[:server_root_ca_cert]))
64+
end
65+
66+
# Connect to Temporal server
67+
client = Temporalio::Client.connect(
68+
options[:target_host],
69+
options[:namespace],
70+
tls: tls_options
71+
)
72+
73+
# Execute the workflow
74+
puts 'Starting workflow with mTLS...'
75+
handle = client.start_workflow(
76+
ClientMtls::GreetingWorkflow,
77+
'World',
78+
id: "mtls-workflow-#{Time.now.to_i}",
79+
task_queue: options[:task_queue]
80+
)
81+
82+
puts "Started workflow. WorkflowID: #{handle.id}"
83+
84+
# Wait for workflow completion
85+
result = handle.result
86+
puts "Workflow completed with result: #{result}"

client_mtls/worker.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'my_activities'
4+
require_relative 'my_workflow'
5+
require 'optparse'
6+
require 'temporalio/client'
7+
require 'temporalio/worker'
8+
9+
# Define options parser to handle certificate paths and other parameters
10+
options = {
11+
target_host: 'localhost:7233',
12+
namespace: 'default',
13+
task_queue: 'mtls-task-queue',
14+
server_root_ca_cert: nil,
15+
client_cert: nil,
16+
client_key: nil
17+
}
18+
19+
OptionParser.new do |opts|
20+
opts.banner = 'Usage: worker.rb [options]'
21+
22+
opts.on('--target-host HOST', 'Host:port for the server (default: localhost:7233)') do |v|
23+
options[:target_host] = v
24+
end
25+
26+
opts.on('--namespace NAMESPACE', 'Namespace to use (default: default)') do |v|
27+
options[:namespace] = v
28+
end
29+
30+
opts.on('--task-queue QUEUE', 'Task queue to use (default: mtls-task-queue)') do |v|
31+
options[:task_queue] = v
32+
end
33+
34+
opts.on('--server-root-ca-cert PATH', 'Path to the server root CA certificate') do |v|
35+
options[:server_root_ca_cert] = v
36+
end
37+
38+
opts.on('--client-cert PATH', 'Path to the client certificate (required for mTLS)') do |v|
39+
options[:client_cert] = v
40+
end
41+
42+
opts.on('--client-key PATH', 'Path to the client key (required for mTLS)') do |v|
43+
options[:client_key] = v
44+
end
45+
end.parse!
46+
47+
# Check for required certificates for mTLS
48+
if options[:client_cert].nil? || options[:client_key].nil?
49+
puts 'Error: Client certificate and key are required for mTLS'
50+
puts 'Usage: ruby worker.rb --client-cert PATH --client-key PATH'
51+
exit 1
52+
end
53+
54+
puts "Connecting to Temporal Server at #{options[:target_host]} with mTLS..."
55+
puts "Using namespace: #{options[:namespace]}"
56+
57+
# Create client with mTLS configuration
58+
tls_options = Temporalio::Client::Connection::TLSOptions.new(
59+
client_cert: File.read(options[:client_cert]),
60+
client_private_key: File.read(options[:client_key])
61+
)
62+
63+
# Add server root CA cert if provided
64+
if options[:server_root_ca_cert]
65+
tls_options = tls_options.with(server_root_ca_cert: File.read(options[:server_root_ca_cert]))
66+
end
67+
68+
# Connect to Temporal server
69+
client = Temporalio::Client.connect(
70+
options[:target_host],
71+
options[:namespace],
72+
tls: tls_options
73+
)
74+
75+
# Start worker with activities and workflows registered
76+
puts 'Starting worker connected with mTLS...'
77+
puts "Task queue: #{options[:task_queue]}"
78+
Temporalio::Worker.new(
79+
client:,
80+
task_queue: options[:task_queue],
81+
workflows: [ClientMtls::GreetingWorkflow],
82+
activities: [ClientMtls::Activities::ComposeGreeting]
83+
).run(shutdown_signals: ['SIGINT'])

0 commit comments

Comments
 (0)