ruby-kafka 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +18 -16
- data/CHANGELOG.md +4 -0
- data/README.md +14 -0
- data/lib/kafka/client.rb +8 -0
- data/lib/kafka/protocol/sasl_handshake_request.rb +1 -1
- data/lib/kafka/sasl/awsmskiam.rb +133 -0
- data/lib/kafka/sasl_authenticator.rb +15 -2
- data/lib/kafka/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '037679129d871686838235c368d870493439212f9aed0e897c1254fbfd5e4751'
|
4
|
+
data.tar.gz: bf61f193e97eb326b62a47b525a865911f1a7307983b6e765c08cc95e368a262
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d53ee98b08cda3c8b64dd44ccb27d16fe6f9c5b65986115ce1db8c6d4fae03543fb9f83dc5765f40e4455e23986c323593fc8b9fc89e14a5dae4ec912a83981
|
7
|
+
data.tar.gz: 48229b285251618f66f55963074285e01de96c71be4f65a2340a4c34e394db494aea76c3f8254075eb5670881a43a2ed51b2874d0c188c94d9bac9d7f27d38df
|
data/.circleci/config.yml
CHANGED
@@ -347,27 +347,29 @@ jobs:
|
|
347
347
|
- image: circleci/ruby:2.5.1-node
|
348
348
|
environment:
|
349
349
|
LOG_LEVEL: DEBUG
|
350
|
-
- image:
|
351
|
-
- image: wurstmeister/kafka:2.13-2.7.0
|
350
|
+
- image: bitnami/zookeeper
|
352
351
|
environment:
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
352
|
+
ALLOW_ANONYMOUS_LOGIN: yes
|
353
|
+
- image: bitnami/kafka:2.7.0
|
354
|
+
environment:
|
355
|
+
ALLOW_PLAINTEXT_LISTENER: yes
|
356
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
357
|
+
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
|
358
|
+
KAFKA_CFG_ZOOKEEPER_CONNECT: localhost:2181
|
357
359
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
358
|
-
- image:
|
360
|
+
- image: bitnami/kafka:2.7.0
|
359
361
|
environment:
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
362
|
+
ALLOW_PLAINTEXT_LISTENER: yes
|
363
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
|
364
|
+
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9093
|
365
|
+
KAFKA_CFG_ZOOKEEPER_CONNECT: localhost:2181
|
364
366
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
365
|
-
- image:
|
367
|
+
- image: bitnami/kafka:2.7.0
|
366
368
|
environment:
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
369
|
+
ALLOW_PLAINTEXT_LISTENER: yes
|
370
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9094
|
371
|
+
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9094
|
372
|
+
KAFKA_CFG_ZOOKEEPER_CONNECT: localhost:2181
|
371
373
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
372
374
|
steps:
|
373
375
|
- checkout
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,10 @@ Changes and additions to the library will be listed here.
|
|
4
4
|
|
5
5
|
## Unreleased
|
6
6
|
|
7
|
+
## 1.5.0
|
8
|
+
- Add support for AWS IAM Authentication to an MSK cluster (#907).
|
9
|
+
- Added session token to the IAM mechanism; necessary for auth via temporary credentials (#937)
|
10
|
+
|
7
11
|
## 1.4.0
|
8
12
|
|
9
13
|
- Refresh a stale cluster's metadata if necessary on `Kafka::Client#deliver_message` (#901).
|
data/README.md
CHANGED
@@ -1121,6 +1121,20 @@ kafka = Kafka.new(
|
|
1121
1121
|
)
|
1122
1122
|
```
|
1123
1123
|
|
1124
|
+
##### AWS MSK (IAM)
|
1125
|
+
In order to authenticate using IAM w/ an AWS MSK cluster, set your access key, secret key, and region when initializing the Kafka client:
|
1126
|
+
|
1127
|
+
```ruby
|
1128
|
+
k = Kafka.new(
|
1129
|
+
["kafka1:9092"],
|
1130
|
+
sasl_aws_msk_iam_access_key_id: 'iam_access_key',
|
1131
|
+
sasl_aws_msk_iam_secret_key_id: 'iam_secret_key',
|
1132
|
+
sasl_aws_msk_iam_aws_region: 'us-west-2',
|
1133
|
+
ssl_ca_certs_from_system: true,
|
1134
|
+
# ...
|
1135
|
+
)
|
1136
|
+
```
|
1137
|
+
|
1124
1138
|
##### PLAIN
|
1125
1139
|
In order to authenticate using PLAIN, you must set your username and password when initializing the Kafka client:
|
1126
1140
|
|
data/lib/kafka/client.rb
CHANGED
@@ -85,6 +85,10 @@ module Kafka
|
|
85
85
|
ssl_client_cert_key_password: nil, ssl_client_cert_chain: nil, sasl_gssapi_principal: nil,
|
86
86
|
sasl_gssapi_keytab: nil, sasl_plain_authzid: '', sasl_plain_username: nil, sasl_plain_password: nil,
|
87
87
|
sasl_scram_username: nil, sasl_scram_password: nil, sasl_scram_mechanism: nil,
|
88
|
+
sasl_aws_msk_iam_access_key_id: nil,
|
89
|
+
sasl_aws_msk_iam_secret_key_id: nil,
|
90
|
+
sasl_aws_msk_iam_aws_region: nil,
|
91
|
+
sasl_aws_msk_iam_session_token: nil,
|
88
92
|
sasl_over_ssl: true, ssl_ca_certs_from_system: false, partitioner: nil, sasl_oauth_token_provider: nil, ssl_verify_hostname: true,
|
89
93
|
resolve_seed_brokers: false)
|
90
94
|
@logger = TaggedLogger.new(logger)
|
@@ -112,6 +116,10 @@ module Kafka
|
|
112
116
|
sasl_scram_username: sasl_scram_username,
|
113
117
|
sasl_scram_password: sasl_scram_password,
|
114
118
|
sasl_scram_mechanism: sasl_scram_mechanism,
|
119
|
+
sasl_aws_msk_iam_access_key_id: sasl_aws_msk_iam_access_key_id,
|
120
|
+
sasl_aws_msk_iam_secret_key_id: sasl_aws_msk_iam_secret_key_id,
|
121
|
+
sasl_aws_msk_iam_aws_region: sasl_aws_msk_iam_aws_region,
|
122
|
+
sasl_aws_msk_iam_session_token: sasl_aws_msk_iam_session_token,
|
115
123
|
sasl_oauth_token_provider: sasl_oauth_token_provider,
|
116
124
|
logger: @logger
|
117
125
|
)
|
@@ -8,7 +8,7 @@ module Kafka
|
|
8
8
|
|
9
9
|
class SaslHandshakeRequest
|
10
10
|
|
11
|
-
SUPPORTED_MECHANISMS = %w(GSSAPI PLAIN SCRAM-SHA-256 SCRAM-SHA-512 OAUTHBEARER)
|
11
|
+
SUPPORTED_MECHANISMS = %w(AWS_MSK_IAM GSSAPI PLAIN SCRAM-SHA-256 SCRAM-SHA-512 OAUTHBEARER)
|
12
12
|
|
13
13
|
def initialize(mechanism)
|
14
14
|
unless SUPPORTED_MECHANISMS.include?(mechanism)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'base64'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Kafka
|
8
|
+
module Sasl
|
9
|
+
class AwsMskIam
|
10
|
+
AWS_MSK_IAM = "AWS_MSK_IAM"
|
11
|
+
|
12
|
+
def initialize(aws_region:, access_key_id:, secret_key_id:, session_token: nil, logger:)
|
13
|
+
@semaphore = Mutex.new
|
14
|
+
|
15
|
+
@aws_region = aws_region
|
16
|
+
@access_key_id = access_key_id
|
17
|
+
@secret_key_id = secret_key_id
|
18
|
+
@session_token = session_token
|
19
|
+
@logger = TaggedLogger.new(logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ident
|
23
|
+
AWS_MSK_IAM
|
24
|
+
end
|
25
|
+
|
26
|
+
def configured?
|
27
|
+
@aws_region && @access_key_id && @secret_key_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticate!(host, encoder, decoder)
|
31
|
+
@logger.debug "Authenticating #{@access_key_id} with SASL #{AWS_MSK_IAM}"
|
32
|
+
|
33
|
+
host_without_port = host.split(':', -1).first
|
34
|
+
|
35
|
+
time_now = Time.now.utc
|
36
|
+
|
37
|
+
msg = authentication_payload(host: host_without_port, time_now: time_now)
|
38
|
+
@logger.debug "Sending first client SASL AWS_MSK_IAM message:"
|
39
|
+
@logger.debug msg
|
40
|
+
encoder.write_bytes(msg)
|
41
|
+
|
42
|
+
begin
|
43
|
+
@server_first_message = decoder.bytes
|
44
|
+
@logger.debug "Received first server SASL AWS_MSK_IAM message: #{@server_first_message}"
|
45
|
+
|
46
|
+
raise Kafka::Error, "SASL AWS_MSK_IAM authentication failed: unknown error" unless @server_first_message
|
47
|
+
rescue Errno::ETIMEDOUT, EOFError => e
|
48
|
+
raise Kafka::Error, "SASL AWS_MSK_IAM authentication failed: #{e.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
@logger.debug "SASL #{AWS_MSK_IAM} authentication successful"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def bin_to_hex(s)
|
57
|
+
s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
|
58
|
+
end
|
59
|
+
|
60
|
+
def digest
|
61
|
+
@digest ||= OpenSSL::Digest::SHA256.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def authentication_payload(host:, time_now:)
|
65
|
+
{
|
66
|
+
'version' => "2020_10_22",
|
67
|
+
'host' => host,
|
68
|
+
'user-agent' => "ruby-kafka",
|
69
|
+
'action' => "kafka-cluster:Connect",
|
70
|
+
'x-amz-algorithm' => "AWS4-HMAC-SHA256",
|
71
|
+
'x-amz-credential' => @access_key_id + "/" + time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request",
|
72
|
+
'x-amz-date' => time_now.strftime("%Y%m%dT%H%M%SZ"),
|
73
|
+
'x-amz-signedheaders' => "host",
|
74
|
+
'x-amz-expires' => "900",
|
75
|
+
'x-amz-security-token' => @session_token,
|
76
|
+
'x-amz-signature' => signature(host: host, time_now: time_now)
|
77
|
+
}.delete_if { |_, v| v.nil? }.to_json
|
78
|
+
end
|
79
|
+
|
80
|
+
def canonical_request(host:, time_now:)
|
81
|
+
"GET\n" +
|
82
|
+
"/\n" +
|
83
|
+
canonical_query_string(time_now: time_now) + "\n" +
|
84
|
+
canonical_headers(host: host) + "\n" +
|
85
|
+
signed_headers + "\n" +
|
86
|
+
hashed_payload
|
87
|
+
end
|
88
|
+
|
89
|
+
def canonical_query_string(time_now:)
|
90
|
+
params = {
|
91
|
+
"Action" => "kafka-cluster:Connect",
|
92
|
+
"X-Amz-Algorithm" => "AWS4-HMAC-SHA256",
|
93
|
+
"X-Amz-Credential" => @access_key_id + "/" + time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request",
|
94
|
+
"X-Amz-Date" => time_now.strftime("%Y%m%dT%H%M%SZ"),
|
95
|
+
"X-Amz-Expires" => "900",
|
96
|
+
"X-Amz-Security-Token" => @session_token,
|
97
|
+
"X-Amz-SignedHeaders" => "host"
|
98
|
+
}.delete_if { |_, v| v.nil? }
|
99
|
+
|
100
|
+
URI.encode_www_form(params)
|
101
|
+
end
|
102
|
+
|
103
|
+
def canonical_headers(host:)
|
104
|
+
"host" + ":" + host + "\n"
|
105
|
+
end
|
106
|
+
|
107
|
+
def signed_headers
|
108
|
+
"host"
|
109
|
+
end
|
110
|
+
|
111
|
+
def hashed_payload
|
112
|
+
bin_to_hex(digest.digest(""))
|
113
|
+
end
|
114
|
+
|
115
|
+
def string_to_sign(host:, time_now:)
|
116
|
+
"AWS4-HMAC-SHA256" + "\n" +
|
117
|
+
time_now.strftime("%Y%m%dT%H%M%SZ") + "\n" +
|
118
|
+
time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request" + "\n" +
|
119
|
+
bin_to_hex(digest.digest(canonical_request(host: host, time_now: time_now)))
|
120
|
+
end
|
121
|
+
|
122
|
+
def signature(host:, time_now:)
|
123
|
+
date_key = OpenSSL::HMAC.digest("SHA256", "AWS4" + @secret_key_id, time_now.strftime("%Y%m%d"))
|
124
|
+
date_region_key = OpenSSL::HMAC.digest("SHA256", date_key, @aws_region)
|
125
|
+
date_region_service_key = OpenSSL::HMAC.digest("SHA256", date_region_key, "kafka-cluster")
|
126
|
+
signing_key = OpenSSL::HMAC.digest("SHA256", date_region_service_key, "aws4_request")
|
127
|
+
signature = bin_to_hex(OpenSSL::HMAC.digest("SHA256", signing_key, string_to_sign(host: host, time_now: time_now)))
|
128
|
+
|
129
|
+
signature
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -4,13 +4,18 @@ require 'kafka/sasl/plain'
|
|
4
4
|
require 'kafka/sasl/gssapi'
|
5
5
|
require 'kafka/sasl/scram'
|
6
6
|
require 'kafka/sasl/oauth'
|
7
|
+
require 'kafka/sasl/awsmskiam'
|
7
8
|
|
8
9
|
module Kafka
|
9
10
|
class SaslAuthenticator
|
10
11
|
def initialize(logger:, sasl_gssapi_principal:, sasl_gssapi_keytab:,
|
11
12
|
sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:,
|
12
13
|
sasl_scram_username:, sasl_scram_password:, sasl_scram_mechanism:,
|
13
|
-
sasl_oauth_token_provider
|
14
|
+
sasl_oauth_token_provider:,
|
15
|
+
sasl_aws_msk_iam_access_key_id:,
|
16
|
+
sasl_aws_msk_iam_secret_key_id:,
|
17
|
+
sasl_aws_msk_iam_aws_region:,
|
18
|
+
sasl_aws_msk_iam_session_token: nil)
|
14
19
|
@logger = TaggedLogger.new(logger)
|
15
20
|
|
16
21
|
@plain = Sasl::Plain.new(
|
@@ -33,12 +38,20 @@ module Kafka
|
|
33
38
|
logger: @logger,
|
34
39
|
)
|
35
40
|
|
41
|
+
@aws_msk_iam = Sasl::AwsMskIam.new(
|
42
|
+
access_key_id: sasl_aws_msk_iam_access_key_id,
|
43
|
+
secret_key_id: sasl_aws_msk_iam_secret_key_id,
|
44
|
+
aws_region: sasl_aws_msk_iam_aws_region,
|
45
|
+
session_token: sasl_aws_msk_iam_session_token,
|
46
|
+
logger: @logger,
|
47
|
+
)
|
48
|
+
|
36
49
|
@oauth = Sasl::OAuth.new(
|
37
50
|
token_provider: sasl_oauth_token_provider,
|
38
51
|
logger: @logger,
|
39
52
|
)
|
40
53
|
|
41
|
-
@mechanism = [@gssapi, @plain, @scram, @oauth].find(&:configured?)
|
54
|
+
@mechanism = [@gssapi, @plain, @scram, @oauth, @aws_msk_iam].find(&:configured?)
|
42
55
|
end
|
43
56
|
|
44
57
|
def enabled?
|
data/lib/kafka/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-kafka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest-crc
|
@@ -476,6 +476,7 @@ files:
|
|
476
476
|
- lib/kafka/protocol/txn_offset_commit_request.rb
|
477
477
|
- lib/kafka/protocol/txn_offset_commit_response.rb
|
478
478
|
- lib/kafka/round_robin_assignment_strategy.rb
|
479
|
+
- lib/kafka/sasl/awsmskiam.rb
|
479
480
|
- lib/kafka/sasl/gssapi.rb
|
480
481
|
- lib/kafka/sasl/oauth.rb
|
481
482
|
- lib/kafka/sasl/plain.rb
|