ruby-kafka-temp-fork 0.0.1

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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +393 -0
  3. data/.github/workflows/stale.yml +19 -0
  4. data/.gitignore +13 -0
  5. data/.readygo +1 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +44 -0
  8. data/.ruby-version +1 -0
  9. data/.yardopts +3 -0
  10. data/CHANGELOG.md +310 -0
  11. data/Gemfile +5 -0
  12. data/ISSUE_TEMPLATE.md +23 -0
  13. data/LICENSE.txt +176 -0
  14. data/Procfile +2 -0
  15. data/README.md +1342 -0
  16. data/Rakefile +8 -0
  17. data/benchmarks/message_encoding.rb +23 -0
  18. data/bin/console +8 -0
  19. data/bin/setup +5 -0
  20. data/docker-compose.yml +39 -0
  21. data/examples/consumer-group.rb +35 -0
  22. data/examples/firehose-consumer.rb +64 -0
  23. data/examples/firehose-producer.rb +54 -0
  24. data/examples/simple-consumer.rb +34 -0
  25. data/examples/simple-producer.rb +42 -0
  26. data/examples/ssl-producer.rb +44 -0
  27. data/lib/kafka.rb +373 -0
  28. data/lib/kafka/async_producer.rb +291 -0
  29. data/lib/kafka/broker.rb +217 -0
  30. data/lib/kafka/broker_info.rb +16 -0
  31. data/lib/kafka/broker_pool.rb +41 -0
  32. data/lib/kafka/broker_uri.rb +43 -0
  33. data/lib/kafka/client.rb +833 -0
  34. data/lib/kafka/cluster.rb +513 -0
  35. data/lib/kafka/compression.rb +45 -0
  36. data/lib/kafka/compressor.rb +86 -0
  37. data/lib/kafka/connection.rb +223 -0
  38. data/lib/kafka/connection_builder.rb +33 -0
  39. data/lib/kafka/consumer.rb +642 -0
  40. data/lib/kafka/consumer_group.rb +231 -0
  41. data/lib/kafka/consumer_group/assignor.rb +63 -0
  42. data/lib/kafka/crc32_hash.rb +15 -0
  43. data/lib/kafka/datadog.rb +420 -0
  44. data/lib/kafka/digest.rb +22 -0
  45. data/lib/kafka/fetch_operation.rb +115 -0
  46. data/lib/kafka/fetched_batch.rb +58 -0
  47. data/lib/kafka/fetched_batch_generator.rb +120 -0
  48. data/lib/kafka/fetched_message.rb +48 -0
  49. data/lib/kafka/fetched_offset_resolver.rb +48 -0
  50. data/lib/kafka/fetcher.rb +224 -0
  51. data/lib/kafka/gzip_codec.rb +34 -0
  52. data/lib/kafka/heartbeat.rb +25 -0
  53. data/lib/kafka/instrumenter.rb +38 -0
  54. data/lib/kafka/interceptors.rb +33 -0
  55. data/lib/kafka/lz4_codec.rb +27 -0
  56. data/lib/kafka/message_buffer.rb +87 -0
  57. data/lib/kafka/murmur2_hash.rb +17 -0
  58. data/lib/kafka/offset_manager.rb +259 -0
  59. data/lib/kafka/partitioner.rb +40 -0
  60. data/lib/kafka/pause.rb +92 -0
  61. data/lib/kafka/pending_message.rb +29 -0
  62. data/lib/kafka/pending_message_queue.rb +41 -0
  63. data/lib/kafka/produce_operation.rb +205 -0
  64. data/lib/kafka/producer.rb +528 -0
  65. data/lib/kafka/prometheus.rb +316 -0
  66. data/lib/kafka/protocol.rb +225 -0
  67. data/lib/kafka/protocol/add_offsets_to_txn_request.rb +29 -0
  68. data/lib/kafka/protocol/add_offsets_to_txn_response.rb +21 -0
  69. data/lib/kafka/protocol/add_partitions_to_txn_request.rb +34 -0
  70. data/lib/kafka/protocol/add_partitions_to_txn_response.rb +47 -0
  71. data/lib/kafka/protocol/alter_configs_request.rb +44 -0
  72. data/lib/kafka/protocol/alter_configs_response.rb +49 -0
  73. data/lib/kafka/protocol/api_versions_request.rb +21 -0
  74. data/lib/kafka/protocol/api_versions_response.rb +53 -0
  75. data/lib/kafka/protocol/consumer_group_protocol.rb +19 -0
  76. data/lib/kafka/protocol/create_partitions_request.rb +42 -0
  77. data/lib/kafka/protocol/create_partitions_response.rb +28 -0
  78. data/lib/kafka/protocol/create_topics_request.rb +45 -0
  79. data/lib/kafka/protocol/create_topics_response.rb +26 -0
  80. data/lib/kafka/protocol/decoder.rb +175 -0
  81. data/lib/kafka/protocol/delete_topics_request.rb +33 -0
  82. data/lib/kafka/protocol/delete_topics_response.rb +26 -0
  83. data/lib/kafka/protocol/describe_configs_request.rb +35 -0
  84. data/lib/kafka/protocol/describe_configs_response.rb +73 -0
  85. data/lib/kafka/protocol/describe_groups_request.rb +27 -0
  86. data/lib/kafka/protocol/describe_groups_response.rb +73 -0
  87. data/lib/kafka/protocol/encoder.rb +184 -0
  88. data/lib/kafka/protocol/end_txn_request.rb +29 -0
  89. data/lib/kafka/protocol/end_txn_response.rb +19 -0
  90. data/lib/kafka/protocol/fetch_request.rb +70 -0
  91. data/lib/kafka/protocol/fetch_response.rb +136 -0
  92. data/lib/kafka/protocol/find_coordinator_request.rb +29 -0
  93. data/lib/kafka/protocol/find_coordinator_response.rb +29 -0
  94. data/lib/kafka/protocol/heartbeat_request.rb +27 -0
  95. data/lib/kafka/protocol/heartbeat_response.rb +17 -0
  96. data/lib/kafka/protocol/init_producer_id_request.rb +26 -0
  97. data/lib/kafka/protocol/init_producer_id_response.rb +27 -0
  98. data/lib/kafka/protocol/join_group_request.rb +47 -0
  99. data/lib/kafka/protocol/join_group_response.rb +41 -0
  100. data/lib/kafka/protocol/leave_group_request.rb +25 -0
  101. data/lib/kafka/protocol/leave_group_response.rb +17 -0
  102. data/lib/kafka/protocol/list_groups_request.rb +23 -0
  103. data/lib/kafka/protocol/list_groups_response.rb +35 -0
  104. data/lib/kafka/protocol/list_offset_request.rb +53 -0
  105. data/lib/kafka/protocol/list_offset_response.rb +89 -0
  106. data/lib/kafka/protocol/member_assignment.rb +42 -0
  107. data/lib/kafka/protocol/message.rb +172 -0
  108. data/lib/kafka/protocol/message_set.rb +55 -0
  109. data/lib/kafka/protocol/metadata_request.rb +31 -0
  110. data/lib/kafka/protocol/metadata_response.rb +185 -0
  111. data/lib/kafka/protocol/offset_commit_request.rb +47 -0
  112. data/lib/kafka/protocol/offset_commit_response.rb +29 -0
  113. data/lib/kafka/protocol/offset_fetch_request.rb +38 -0
  114. data/lib/kafka/protocol/offset_fetch_response.rb +56 -0
  115. data/lib/kafka/protocol/produce_request.rb +94 -0
  116. data/lib/kafka/protocol/produce_response.rb +63 -0
  117. data/lib/kafka/protocol/record.rb +88 -0
  118. data/lib/kafka/protocol/record_batch.rb +223 -0
  119. data/lib/kafka/protocol/request_message.rb +26 -0
  120. data/lib/kafka/protocol/sasl_handshake_request.rb +33 -0
  121. data/lib/kafka/protocol/sasl_handshake_response.rb +28 -0
  122. data/lib/kafka/protocol/sync_group_request.rb +33 -0
  123. data/lib/kafka/protocol/sync_group_response.rb +26 -0
  124. data/lib/kafka/protocol/txn_offset_commit_request.rb +46 -0
  125. data/lib/kafka/protocol/txn_offset_commit_response.rb +47 -0
  126. data/lib/kafka/round_robin_assignment_strategy.rb +52 -0
  127. data/lib/kafka/sasl/gssapi.rb +76 -0
  128. data/lib/kafka/sasl/oauth.rb +64 -0
  129. data/lib/kafka/sasl/plain.rb +39 -0
  130. data/lib/kafka/sasl/scram.rb +180 -0
  131. data/lib/kafka/sasl_authenticator.rb +61 -0
  132. data/lib/kafka/snappy_codec.rb +29 -0
  133. data/lib/kafka/socket_with_timeout.rb +96 -0
  134. data/lib/kafka/ssl_context.rb +66 -0
  135. data/lib/kafka/ssl_socket_with_timeout.rb +188 -0
  136. data/lib/kafka/statsd.rb +296 -0
  137. data/lib/kafka/tagged_logger.rb +77 -0
  138. data/lib/kafka/transaction_manager.rb +306 -0
  139. data/lib/kafka/transaction_state_machine.rb +72 -0
  140. data/lib/kafka/version.rb +5 -0
  141. data/lib/kafka/zstd_codec.rb +27 -0
  142. data/lib/ruby-kafka-temp-fork.rb +5 -0
  143. data/ruby-kafka-temp-fork.gemspec +54 -0
  144. metadata +520 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ module Sasl
5
+ class Plain
6
+ PLAIN_IDENT = "PLAIN"
7
+
8
+ def initialize(logger:, authzid:, username:, password:)
9
+ @logger = TaggedLogger.new(logger)
10
+ @authzid = authzid
11
+ @username = username
12
+ @password = password
13
+ end
14
+
15
+ def ident
16
+ PLAIN_IDENT
17
+ end
18
+
19
+ def configured?
20
+ @authzid && @username && @password
21
+ end
22
+
23
+ def authenticate!(host, encoder, decoder)
24
+ msg = [@authzid, @username, @password].join("\000").force_encoding("utf-8")
25
+
26
+ encoder.write_bytes(msg)
27
+
28
+ begin
29
+ msg = decoder.bytes
30
+ raise Kafka::Error, "SASL PLAIN authentication failed: unknown error" unless msg
31
+ rescue Errno::ETIMEDOUT, EOFError => e
32
+ raise Kafka::Error, "SASL PLAIN authentication failed: #{e.message}"
33
+ end
34
+
35
+ @logger.debug "SASL PLAIN authentication successful."
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'base64'
5
+
6
+ module Kafka
7
+ module Sasl
8
+ class Scram
9
+ MECHANISMS = {
10
+ "sha256" => "SCRAM-SHA-256",
11
+ "sha512" => "SCRAM-SHA-512",
12
+ }.freeze
13
+
14
+ def initialize(username:, password:, mechanism: 'sha256', logger:)
15
+ @semaphore = Mutex.new
16
+ @username = username
17
+ @password = password
18
+ @logger = TaggedLogger.new(logger)
19
+
20
+ if mechanism
21
+ @mechanism = MECHANISMS.fetch(mechanism) do
22
+ raise Kafka::SaslScramError, "SCRAM mechanism #{mechanism} is not supported."
23
+ end
24
+ end
25
+ end
26
+
27
+ def ident
28
+ @mechanism
29
+ end
30
+
31
+ def configured?
32
+ @username && @password && @mechanism
33
+ end
34
+
35
+ def authenticate!(host, encoder, decoder)
36
+ @logger.debug "Authenticating #{@username} with SASL #{@mechanism}"
37
+
38
+ begin
39
+ @semaphore.synchronize do
40
+ msg = first_message
41
+ @logger.debug "Sending first client SASL SCRAM message: #{msg}"
42
+ encoder.write_bytes(msg)
43
+
44
+ @server_first_message = decoder.bytes
45
+ @logger.debug "Received first server SASL SCRAM message: #{@server_first_message}"
46
+
47
+ msg = final_message
48
+ @logger.debug "Sending final client SASL SCRAM message: #{msg}"
49
+ encoder.write_bytes(msg)
50
+
51
+ response = parse_response(decoder.bytes)
52
+ @logger.debug "Received last server SASL SCRAM message: #{response}"
53
+
54
+ raise FailedScramAuthentication, response['e'] if response['e']
55
+ raise FailedScramAuthentication, "Invalid server signature" if response['v'] != server_signature
56
+ end
57
+ rescue EOFError => e
58
+ raise FailedScramAuthentication, e.message
59
+ end
60
+
61
+ @logger.debug "SASL SCRAM authentication successful"
62
+ end
63
+
64
+ private
65
+
66
+ def first_message
67
+ "n,,#{first_message_bare}"
68
+ end
69
+
70
+ def first_message_bare
71
+ "n=#{encoded_username},r=#{nonce}"
72
+ end
73
+
74
+ def final_message_without_proof
75
+ "c=biws,r=#{rnonce}"
76
+ end
77
+
78
+ def final_message
79
+ "#{final_message_without_proof},p=#{client_proof}"
80
+ end
81
+
82
+ def server_data
83
+ parse_response(@server_first_message)
84
+ end
85
+
86
+ def rnonce
87
+ server_data['r']
88
+ end
89
+
90
+ def salt
91
+ Base64.strict_decode64(server_data['s'])
92
+ end
93
+
94
+ def iterations
95
+ server_data['i'].to_i
96
+ end
97
+
98
+ def auth_message
99
+ [first_message_bare, @server_first_message, final_message_without_proof].join(',')
100
+ end
101
+
102
+ def salted_password
103
+ hi(@password, salt, iterations)
104
+ end
105
+
106
+ def client_key
107
+ hmac(salted_password, 'Client Key')
108
+ end
109
+
110
+ def stored_key
111
+ h(client_key)
112
+ end
113
+
114
+ def server_key
115
+ hmac(salted_password, 'Server Key')
116
+ end
117
+
118
+ def client_signature
119
+ hmac(stored_key, auth_message)
120
+ end
121
+
122
+ def server_signature
123
+ Base64.strict_encode64(hmac(server_key, auth_message))
124
+ end
125
+
126
+ def client_proof
127
+ Base64.strict_encode64(xor(client_key, client_signature))
128
+ end
129
+
130
+ def h(str)
131
+ digest.digest(str)
132
+ end
133
+
134
+ def hi(str, salt, iterations)
135
+ OpenSSL::PKCS5.pbkdf2_hmac(
136
+ str,
137
+ salt,
138
+ iterations,
139
+ digest.size,
140
+ digest
141
+ )
142
+ end
143
+
144
+ def hmac(data, key)
145
+ OpenSSL::HMAC.digest(digest, data, key)
146
+ end
147
+
148
+ def xor(first, second)
149
+ first.bytes.zip(second.bytes).map { |(a, b)| (a ^ b).chr }.join('')
150
+ end
151
+
152
+ def parse_response(data)
153
+ data.split(',').map { |s| s.split('=', 2) }.to_h
154
+ end
155
+
156
+ def encoded_username
157
+ safe_str(@username.encode(Encoding::UTF_8))
158
+ end
159
+
160
+ def nonce
161
+ @nonce ||= SecureRandom.urlsafe_base64(32)
162
+ end
163
+
164
+ def digest
165
+ @digest ||= case @mechanism
166
+ when 'SCRAM-SHA-256'
167
+ OpenSSL::Digest::SHA256.new
168
+ when 'SCRAM-SHA-512'
169
+ OpenSSL::Digest::SHA512.new
170
+ else
171
+ raise ArgumentError, "Unknown SASL mechanism '#{@mechanism}'"
172
+ end
173
+ end
174
+
175
+ def safe_str(val)
176
+ val.gsub('=', '=3D').gsub(',', '=2C')
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kafka/sasl/plain'
4
+ require 'kafka/sasl/gssapi'
5
+ require 'kafka/sasl/scram'
6
+ require 'kafka/sasl/oauth'
7
+
8
+ module Kafka
9
+ class SaslAuthenticator
10
+ def initialize(logger:, sasl_gssapi_principal:, sasl_gssapi_keytab:,
11
+ sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:,
12
+ sasl_scram_username:, sasl_scram_password:, sasl_scram_mechanism:,
13
+ sasl_oauth_token_provider:)
14
+ @logger = TaggedLogger.new(logger)
15
+
16
+ @plain = Sasl::Plain.new(
17
+ authzid: sasl_plain_authzid,
18
+ username: sasl_plain_username,
19
+ password: sasl_plain_password,
20
+ logger: @logger,
21
+ )
22
+
23
+ @gssapi = Sasl::Gssapi.new(
24
+ principal: sasl_gssapi_principal,
25
+ keytab: sasl_gssapi_keytab,
26
+ logger: @logger,
27
+ )
28
+
29
+ @scram = Sasl::Scram.new(
30
+ username: sasl_scram_username,
31
+ password: sasl_scram_password,
32
+ mechanism: sasl_scram_mechanism,
33
+ logger: @logger,
34
+ )
35
+
36
+ @oauth = Sasl::OAuth.new(
37
+ token_provider: sasl_oauth_token_provider,
38
+ logger: @logger,
39
+ )
40
+
41
+ @mechanism = [@gssapi, @plain, @scram, @oauth].find(&:configured?)
42
+ end
43
+
44
+ def enabled?
45
+ !@mechanism.nil?
46
+ end
47
+
48
+ def authenticate!(connection)
49
+ return unless enabled?
50
+
51
+ ident = @mechanism.ident
52
+ response = connection.send_request(Kafka::Protocol::SaslHandshakeRequest.new(ident))
53
+
54
+ unless response.error_code == 0 && response.enabled_mechanisms.include?(ident)
55
+ raise Kafka::Error, "#{ident} is not supported."
56
+ end
57
+
58
+ @mechanism.authenticate!(connection.to_s, connection.encoder, connection.decoder)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ class SnappyCodec
5
+ def codec_id
6
+ 2
7
+ end
8
+
9
+ def produce_api_min_version
10
+ 0
11
+ end
12
+
13
+ def load
14
+ require "snappy"
15
+ rescue LoadError
16
+ raise LoadError,
17
+ "Using snappy compression requires adding a dependency on the `snappy` gem to your Gemfile."
18
+ end
19
+
20
+ def compress(data)
21
+ Snappy.deflate(data)
22
+ end
23
+
24
+ def decompress(data)
25
+ buffer = StringIO.new(data)
26
+ Snappy::Reader.new(buffer).read
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Kafka
6
+
7
+ # Opens sockets in a non-blocking fashion, ensuring that we're not stalling
8
+ # for long periods of time.
9
+ #
10
+ # It's possible to set timeouts for connecting to the server, for reading data,
11
+ # and for writing data. Whenever a timeout is exceeded, Errno::ETIMEDOUT is
12
+ # raised.
13
+ #
14
+ class SocketWithTimeout
15
+
16
+ # Opens a socket.
17
+ #
18
+ # @param host [String]
19
+ # @param port [Integer]
20
+ # @param connect_timeout [Integer] the connection timeout, in seconds.
21
+ # @param timeout [Integer] the read and write timeout, in seconds.
22
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
23
+ def initialize(host, port, connect_timeout: nil, timeout: nil)
24
+ addr = Socket.getaddrinfo(host, nil)
25
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
26
+
27
+ @timeout = timeout
28
+
29
+ @socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
30
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
31
+
32
+ begin
33
+ # Initiate the socket connection in the background. If it doesn't fail
34
+ # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
35
+ # indicating the connection is in progress.
36
+ @socket.connect_nonblock(sockaddr)
37
+ rescue IO::WaitWritable
38
+ # IO.select will block until the socket is writable or the timeout
39
+ # is exceeded, whichever comes first.
40
+ unless IO.select(nil, [@socket], nil, connect_timeout)
41
+ # IO.select returns nil when the socket is not ready before timeout
42
+ # seconds have elapsed
43
+ @socket.close
44
+ raise Errno::ETIMEDOUT
45
+ end
46
+
47
+ begin
48
+ # Verify there is now a good connection.
49
+ @socket.connect_nonblock(sockaddr)
50
+ rescue Errno::EISCONN
51
+ # The socket is connected, we're good!
52
+ end
53
+ end
54
+ end
55
+
56
+ # Reads bytes from the socket, possible with a timeout.
57
+ #
58
+ # @param num_bytes [Integer] the number of bytes to read.
59
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
60
+ # @return [String] the data that was read from the socket.
61
+ def read(num_bytes)
62
+ unless IO.select([@socket], nil, nil, @timeout)
63
+ raise Errno::ETIMEDOUT
64
+ end
65
+
66
+ @socket.read(num_bytes)
67
+ rescue IO::EAGAINWaitReadable
68
+ retry
69
+ end
70
+
71
+ # Writes bytes to the socket, possible with a timeout.
72
+ #
73
+ # @param bytes [String] the data that should be written to the socket.
74
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
75
+ # @return [Integer] the number of bytes written.
76
+ def write(bytes)
77
+ unless IO.select(nil, [@socket], nil, @timeout)
78
+ raise Errno::ETIMEDOUT
79
+ end
80
+
81
+ @socket.write(bytes)
82
+ end
83
+
84
+ def close
85
+ @socket.close
86
+ end
87
+
88
+ def closed?
89
+ @socket.closed?
90
+ end
91
+
92
+ def set_encoding(encoding)
93
+ @socket.set_encoding(encoding)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Kafka
6
+ module SslContext
7
+ CLIENT_CERT_DELIMITER = "\n-----END CERTIFICATE-----\n"
8
+
9
+ def self.build(ca_cert_file_path: nil, ca_cert: nil, client_cert: nil, client_cert_key: nil, client_cert_key_password: nil, client_cert_chain: nil, ca_certs_from_system: nil, verify_hostname: true)
10
+ return nil unless ca_cert_file_path || ca_cert || client_cert || client_cert_key || client_cert_key_password || client_cert_chain || ca_certs_from_system
11
+
12
+ ssl_context = OpenSSL::SSL::SSLContext.new
13
+
14
+ if client_cert && client_cert_key
15
+ if client_cert_key_password
16
+ cert_key = OpenSSL::PKey.read(client_cert_key, client_cert_key_password)
17
+ else
18
+ cert_key = OpenSSL::PKey.read(client_cert_key)
19
+ end
20
+ context_params = {
21
+ cert: OpenSSL::X509::Certificate.new(client_cert),
22
+ key: cert_key
23
+ }
24
+ if client_cert_chain
25
+ certs = []
26
+ client_cert_chain.split(CLIENT_CERT_DELIMITER).each do |cert|
27
+ cert += CLIENT_CERT_DELIMITER
28
+ certs << OpenSSL::X509::Certificate.new(cert)
29
+ end
30
+ context_params[:extra_chain_cert] = certs
31
+ end
32
+ ssl_context.set_params(context_params)
33
+ elsif client_cert && !client_cert_key
34
+ raise ArgumentError, "Kafka client initialized with `ssl_client_cert` but no `ssl_client_cert_key`. Please provide both."
35
+ elsif !client_cert && client_cert_key
36
+ raise ArgumentError, "Kafka client initialized with `ssl_client_cert_key`, but no `ssl_client_cert`. Please provide both."
37
+ elsif client_cert_chain && !client_cert
38
+ raise ArgumentError, "Kafka client initialized with `ssl_client_cert_chain`, but no `ssl_client_cert`. Please provide cert, key and chain."
39
+ elsif client_cert_chain && !client_cert_key
40
+ raise ArgumentError, "Kafka client initialized with `ssl_client_cert_chain`, but no `ssl_client_cert_key`. Please provide cert, key and chain."
41
+ elsif client_cert_key_password && !client_cert_key
42
+ raise ArgumentError, "Kafka client initialized with `ssl_client_cert_key_password`, but no `ssl_client_cert_key`. Please provide both."
43
+ end
44
+
45
+ if ca_cert || ca_cert_file_path || ca_certs_from_system
46
+ store = OpenSSL::X509::Store.new
47
+ Array(ca_cert).each do |cert|
48
+ store.add_cert(OpenSSL::X509::Certificate.new(cert))
49
+ end
50
+ Array(ca_cert_file_path).each do |cert_file_path|
51
+ store.add_file(cert_file_path)
52
+ end
53
+ if ca_certs_from_system
54
+ store.set_default_paths
55
+ end
56
+ ssl_context.cert_store = store
57
+ end
58
+
59
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
60
+ # Verify certificate hostname if supported (ruby >= 2.4.0)
61
+ ssl_context.verify_hostname = verify_hostname if ssl_context.respond_to?(:verify_hostname=)
62
+
63
+ ssl_context
64
+ end
65
+ end
66
+ end