ruby-kafka 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|