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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd754956b907d18ac2a17b3d8f5bc374a8851dea21826277b8941f163403161d
4
- data.tar.gz: 7a1b74d4c3d3f8cfb0772bd6521a8a5c726bc1efc5ade83f814802ce08fb0f24
3
+ metadata.gz: '037679129d871686838235c368d870493439212f9aed0e897c1254fbfd5e4751'
4
+ data.tar.gz: bf61f193e97eb326b62a47b525a865911f1a7307983b6e765c08cc95e368a262
5
5
  SHA512:
6
- metadata.gz: 45038342acd388b7b797b64261addb3bcd90f43fb27d9dfe43a5071f5cb2a5ab6e25df1557fcadd528c5c40056384183cdd427d4fdac66de5cb93c67d246d45f
7
- data.tar.gz: af738b6a58f1cc7d1ee8c0fda72edad9fc94eb21c409f8b48a935a990b563736d30060ea209ef25d11f46ef2252f0d4adae726455c295637c2ef71522473c712
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: wurstmeister/zookeeper
351
- - image: wurstmeister/kafka:2.13-2.7.0
350
+ - image: bitnami/zookeeper
352
351
  environment:
353
- KAFKA_ADVERTISED_HOST_NAME: localhost
354
- KAFKA_ADVERTISED_PORT: 9092
355
- KAFKA_PORT: 9092
356
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
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: wurstmeister/kafka:2.13-2.7.0
360
+ - image: bitnami/kafka:2.7.0
359
361
  environment:
360
- KAFKA_ADVERTISED_HOST_NAME: localhost
361
- KAFKA_ADVERTISED_PORT: 9093
362
- KAFKA_PORT: 9093
363
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
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: wurstmeister/kafka:2.13-2.7.0
367
+ - image: bitnami/kafka:2.7.0
366
368
  environment:
367
- KAFKA_ADVERTISED_HOST_NAME: localhost
368
- KAFKA_ADVERTISED_PORT: 9094
369
- KAFKA_PORT: 9094
370
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kafka
4
- VERSION = "1.4.0"
4
+ VERSION = "1.5.0"
5
5
  end
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.0
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: 2021-08-25 00:00:00.000000000 Z
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