Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gRPC credentials #186

Merged
merged 5 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ jobs:
- POSTGRES_USER=postgres
- POSTGRES_PWD=temporal
- POSTGRES_SEEDS=postgres
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml

environment:
- TEMPORAL_HOST=temporal
Expand Down
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Temporal.configure do |config|
config.port = 7233
config.namespace = 'ruby-samples'
config.task_queue = 'hello-world'
config.credentials = :this_channel_is_insecure
end

begin
Expand Down Expand Up @@ -114,6 +115,57 @@ curl -O https://raw.githubusercontent.com/temporalio/docker-compose/main/docker-
docker-compose up
```

## Using Credentials

### SSL

In many production deployments you will end up connecting to your Temporal Services via SSL. In this
case you must read the public certificate of the CA that issued your Temporal server's SSL certificate and create
an instance of [gRPC Channel Credentials](https://grpc.io/docs/guides/auth/#with-server-authentication-ssltls-1).

Configure your Temporal connection:

```ruby
Temporal.configure do |config|
config.host = 'localhost'
config.port = 7233
config.namespace = 'ruby-samples'
config.task_queue = 'hello-world'
config.credentials = GRPC::Core::ChannelCredentials.new(root_cert, client_key, client_chain)
end
```

### OAuth2 Token

Use gRPC Call Credentials to add OAuth2 token to gRPC calls:

```ruby
Temporal.configure do |config|
config.host = 'localhost'
config.port = 7233
config.namespace = 'ruby-samples'
config.task_queue = 'hello-world'
config.credentials = GRPC::Core::CallCredentials.new(updater_proc)
end
```
`updater_proc` should be a method that returns `proc`. See an example of `updater_proc` in [googleauth](https://www.rubydoc.info/gems/googleauth/0.1.0/Signet/OAuth2/Client) library.

### Combining Credentials

To configure both SSL and OAuth2 token cedentials use `compose` method:

```ruby
Temporal.configure do |config|
config.host = 'localhost'
config.port = 7233
config.namespace = 'ruby-samples'
config.task_queue = 'hello-world'
config.credentials = GRPC::Core::ChannelCredentials.new(root_cert, client_key, client_chain).compose(
GRPC::Core::CallCredentials.new(token.updater_proc)
)
end
```

## Workflows

A workflow is defined using pure Ruby code, however it should contain only a high-level
Expand Down
8 changes: 5 additions & 3 deletions lib/temporal/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

module Temporal
class Configuration
Connection = Struct.new(:type, :host, :port, keyword_init: true)
Connection = Struct.new(:type, :host, :port, :credentials, keyword_init: true)
Execution = Struct.new(:namespace, :task_queue, :timeouts, :headers, keyword_init: true)

attr_reader :timeouts, :error_handlers
attr_writer :converter
attr_accessor :connection_type, :host, :port, :logger, :metrics_adapter, :namespace, :task_queue, :headers
attr_accessor :connection_type, :host, :port, :credentials, :logger, :metrics_adapter, :namespace, :task_queue, :headers

# See https://docs.temporal.io/blog/activity-timeouts/ for general docs.
# We want an infinite execution timeout for cron schedules and other perpetual workflows.
Expand Down Expand Up @@ -53,6 +53,7 @@ def initialize
@headers = DEFAULT_HEADERS
@converter = DEFAULT_CONVERTER
@error_handlers = []
@credentials = :this_channel_is_insecure
end

def on_error(&block)
Expand All @@ -79,7 +80,8 @@ def for_connection
Connection.new(
type: connection_type,
host: host,
port: port
port: port,
credentials: credentials
).freeze
end

Expand Down
3 changes: 2 additions & 1 deletion lib/temporal/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ def self.generate(configuration)
connection_class = CLIENT_TYPES_MAP[configuration.type]
host = configuration.host
port = configuration.port
credentials = configuration.credentials

hostname = `hostname`
thread_id = Thread.current.object_id
identity = "#{thread_id}@#{hostname}"

connection_class.new(host, port, identity)
connection_class.new(host, port, identity, credentials)
end
end
end
7 changes: 4 additions & 3 deletions lib/temporal/connection/grpc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ class GRPC
max_page_size: 100
}.freeze

def initialize(host, port, identity, options = {})
def initialize(host, port, identity, credentials, options = {})
@url = "#{host}:#{port}"
@identity = identity
@credentials = credentials
@poll = true
@poll_mutex = Mutex.new
@poll_request = nil
Expand Down Expand Up @@ -536,12 +537,12 @@ def cancel_polling_request

private

attr_reader :url, :identity, :options, :poll_mutex, :poll_request
attr_reader :url, :identity, :credentials, :options, :poll_mutex, :poll_request

def client
@client ||= Temporal::Api::WorkflowService::V1::WorkflowService::Stub.new(
url,
:this_channel_is_insecure,
credentials,
timeout: 60
)
end
Expand Down
58 changes: 58 additions & 0 deletions spec/unit/lib/temporal/connection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
describe Temporal::Connection do
subject { described_class.generate(config.for_connection) }

let(:connection_type) { :grpc }
let(:credentials) { nil }
let(:config) do
config = Temporal::Configuration.new
config.connection_type = connection_type
config.credentials = credentials if credentials
config
end

context 'insecure' do
let(:credentials) { :this_channel_is_insecure }

it 'generates a grpc connection' do
expect(subject).to be_kind_of(Temporal::Connection::GRPC)
expect(subject.send(:identity)).not_to be_nil
expect(subject.send(:credentials)).to eq(:this_channel_is_insecure)
end
end

context 'ssl' do
let(:credentials) { GRPC::Core::ChannelCredentials.new }

it 'generates a grpc connection' do
expect(subject).to be_kind_of(Temporal::Connection::GRPC)
expect(subject.send(:identity)).not_to be_nil
expect(subject.send(:credentials)).to be_kind_of(GRPC::Core::ChannelCredentials)
end
end

context 'oauth2' do
let(:credentials) { GRPC::Core::CallCredentials.new(proc { { authorization: 'token' } }) }

it 'generates a grpc connection' do
expect(subject).to be_kind_of(Temporal::Connection::GRPC)
expect(subject.send(:identity)).not_to be_nil
expect(subject.send(:credentials)).to be_kind_of(GRPC::Core::CallCredentials)
end
end

context 'ssl + oauth2' do
let(:credentials) do
GRPC::Core::ChannelCredentials.new.compose(
GRPC::Core::CallCredentials.new(
proc { { authorization: 'token' } }
)
)
end

it 'generates a grpc connection' do
expect(subject).to be_kind_of(Temporal::Connection::GRPC)
expect(subject.send(:identity)).not_to be_nil
expect(subject.send(:credentials)).to be_kind_of(GRPC::Core::ChannelCredentials)
end
end
end
2 changes: 1 addition & 1 deletion spec/unit/lib/temporal/grpc_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
let(:run_id) { SecureRandom.uuid }
let(:now) { Time.now}

subject { Temporal::Connection::GRPC.new(nil, nil, identity) }
subject { Temporal::Connection::GRPC.new(nil, nil, identity, :this_channel_is_insecure) }

class TestDeserializer
extend Temporal::Concerns::Payloads
Expand Down