Skip to content

Commit 0832390

Browse files
committed
Adding support for AWS MSK IAM authentication for rdkafka2
Signed-off-by: Andrea Singh <[email protected]>
1 parent f38282e commit 0832390

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

Gemfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@ source 'https://rubygems.org'
22

33
# Specify your gem's dependencies in fluent-plugin-kafka.gemspec
44
gemspec
5-
6-
gem 'rdkafka', ENV['RDKAFKA_VERSION_MIN_RANGE'], ENV['RDKAFKA_VERSION_MAX_RANGE'] if ENV['USE_RDKAFKA']

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ See [Authentication using SASL](https://github.com/zendesk/ruby-kafka#authentica
6161
Set username, password, scram_mechanism and sasl_over_ssl for SASL/Plain or Scram authentication.
6262
See [Authentication using SASL](https://github.com/zendesk/ruby-kafka#authentication-using-sasl) for more details.
6363

64+
##### with MSK IAM Authentication (only for `rdkafka2` output type)
65+
Authentication and authorization with an MSK cluster are facilitated through a base64-encoded signed URL, which is generated by the [aws-msk-iam-sasl-signer-ruby](https://github.com/bruce-szalwinski-he/aws-msk-iam-sasl-signer-ruby) library.
66+
67+
**Configuration Example**
68+
To enable this feature, configure your Fluentd input as follows:
69+
70+
```
71+
<match *>
72+
@type rdkafka2
73+
# Kafka brokers to connect to (typically on port 9098 or 9198 for IAM authentication)
74+
brokers <broker_addresses>
75+
# Topic to write events to
76+
topic_key test-topic-1
77+
default_topic test-topic-1
78+
79+
# AWS Region (required)
80+
aws_msk_region us-east-1
81+
82+
# Use a shared producer for the connection (required)
83+
share_producer true
84+
85+
# MSK IAM authentication settings (required)
86+
rdkafka_options {
87+
"security.protocol": "sasl_ssl",
88+
"sasl.mechanisms": "OAUTHBEARER"
89+
}
90+
</match>
91+
```
92+
With this configuration, Fluentd will handle the token refresh and manage the connection to your MSK cluster using AWS IAM authentication.
93+
6494
### Input plugin (@type 'kafka')
6595

6696
Consume events by single consumer.

fluent-plugin-kafka.gemspec

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ Gem::Specification.new do |gem|
2323
# gems that aren't default gems as of Ruby 3.4
2424
gem.add_dependency("bigdecimal", ["~> 3.1"])
2525

26+
if ENV['USE_RDKAFKA']
27+
gem.add_dependency 'rdkafka', [ENV['RDKAFKA_VERSION_MIN_RANGE'], ENV['RDKAFKA_VERSION_MAX_RANGE']]
28+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0')
29+
gem.add_dependency 'aws-msk-iam-sasl-signer', '~> 0.1.1'
30+
end
31+
end
32+
2633
gem.add_development_dependency "rake", ">= 0.9.2"
2734
gem.add_development_dependency "test-unit", ">= 3.0.8"
2835
gem.add_development_dependency "test-unit-rr", "~> 1.0"

lib/fluent/plugin/out_rdkafka2.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
raise "unable to patch rdkafka."
2121
end
2222

23+
if Gem::Version.create(RUBY_VERSION) >= Gem::Version.create('3.0')
24+
require 'aws-msk-iam-sasl-signer'
25+
end
26+
2327
module Fluent::Plugin
2428
class Fluent::Rdkafka2Output < Output
2529
Fluent::Plugin.register_output('rdkafka2', self)
@@ -100,6 +104,7 @@ class Fluent::Rdkafka2Output < Output
100104
config_param :service_name, :string, :default => nil, :desc => 'Used for sasl.kerberos.service.name'
101105
config_param :unrecoverable_error_codes, :array, :default => ["topic_authorization_failed", "msg_size_too_large"],
102106
:desc => 'Handle some of the error codes should be unrecoverable if specified'
107+
config_param :aws_msk_region, :string, :default => nil, :desc => 'AWS region for MSK'
103108

104109
config_section :buffer do
105110
config_set_default :chunk_keys, ["topic"]
@@ -209,6 +214,10 @@ def add(level, message = nil)
209214
config = build_config
210215
@rdkafka = Rdkafka::Config.new(config)
211216

217+
if config[:"security.protocol"] == "sasl_ssl" && config[:"sasl.mechanisms"] == "OAUTHBEARER"
218+
Rdkafka::Config.oauthbearer_token_refresh_callback = method(:refresh_token)
219+
end
220+
212221
if @default_topic.nil?
213222
if @use_default_for_unknown_topic || @use_default_for_unknown_partition_error
214223
raise Fluent::ConfigError, "default_topic must be set when use_default_for_unknown_topic or use_default_for_unknown_partition_error is true"
@@ -296,9 +305,39 @@ def build_config
296305
config
297306
end
298307

308+
def refresh_token(_config, _client_name)
309+
log.info("+--- Refreshing token")
310+
client = get_producer
311+
# This will happen once upon initialization and is expected to fail, as the producer isnt set yet
312+
# We will set the token manually after creation and after that this refresh method will work
313+
unless client
314+
log.info("Could not get shared client handle, unable to set/refresh token (this is expected one time on startup)")
315+
return
316+
end
317+
signer = AwsMskIamSaslSigner::MSKTokenProvider.new(region: @aws_msk_region)
318+
token = signer.generate_auth_token
319+
320+
if token
321+
client.oauthbearer_set_token(
322+
token: token.token,
323+
lifetime_ms: token.expiration_time_ms,
324+
principal_name: "kafka-cluster"
325+
)
326+
else
327+
client.oauthbearer_set_token_failure(
328+
"Failed to generate token."
329+
)
330+
end
331+
end
332+
299333
def start
300334
if @share_producer
301335
@shared_producer = @rdkafka.producer
336+
log.info("Created shared producer")
337+
if @aws_msk_region
338+
refresh_token(nil, nil)
339+
log.info("Set initial token for shared producer")
340+
end
302341
else
303342
@producers = {}
304343
@producers_mutex = Mutex.new

0 commit comments

Comments
 (0)