bunny 1.3.0 → 2.17.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 +5 -5
- data/.github/ISSUE_TEMPLATE.md +18 -0
- data/.gitignore +7 -1
- data/.rspec +1 -3
- data/.travis.yml +21 -14
- data/CONTRIBUTING.md +132 -0
- data/ChangeLog.md +887 -1
- data/Gemfile +13 -13
- data/LICENSE +1 -1
- data/README.md +46 -60
- data/Rakefile +54 -0
- data/bunny.gemspec +5 -11
- data/docker-compose.yml +28 -0
- data/docker/Dockerfile +24 -0
- data/docker/apt/preferences.d/erlang +3 -0
- data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
- data/docker/docker-entrypoint.sh +26 -0
- data/docker/rabbitmq.conf +29 -0
- data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
- data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
- data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
- data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
- data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
- data/examples/connection/channel_level_exception.rb +1 -9
- data/examples/connection/disabled_automatic_recovery.rb +1 -1
- data/examples/connection/heartbeat.rb +1 -1
- data/examples/consumers/high_and_low_priority.rb +1 -1
- data/examples/guides/extensions/alternate_exchange.rb +2 -0
- data/examples/guides/extensions/basic_nack.rb +1 -1
- data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
- data/examples/guides/getting_started/hello_world.rb +2 -0
- data/examples/guides/getting_started/weathr.rb +2 -0
- data/examples/guides/queues/one_off_consumer.rb +2 -0
- data/examples/guides/queues/redeliveries.rb +4 -2
- data/lib/bunny.rb +8 -4
- data/lib/bunny/channel.rb +268 -153
- data/lib/bunny/channel_id_allocator.rb +6 -4
- data/lib/bunny/concurrent/continuation_queue.rb +34 -13
- data/lib/bunny/consumer_work_pool.rb +34 -6
- data/lib/bunny/cruby/socket.rb +48 -21
- data/lib/bunny/cruby/ssl_socket.rb +65 -4
- data/lib/bunny/exceptions.rb +25 -4
- data/lib/bunny/exchange.rb +24 -28
- data/lib/bunny/get_response.rb +1 -1
- data/lib/bunny/heartbeat_sender.rb +3 -2
- data/lib/bunny/jruby/socket.rb +23 -6
- data/lib/bunny/jruby/ssl_socket.rb +5 -0
- data/lib/bunny/queue.rb +31 -22
- data/lib/bunny/reader_loop.rb +31 -18
- data/lib/bunny/session.rb +448 -159
- data/lib/bunny/test_kit.rb +14 -0
- data/lib/bunny/timeout.rb +1 -12
- data/lib/bunny/transport.rb +205 -98
- data/lib/bunny/version.rb +1 -1
- data/repl +1 -1
- data/spec/config/enabled_plugins +1 -0
- data/spec/config/rabbitmq.conf +13 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +175 -16
- data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
- data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
- data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
- data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
- data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
- data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
- data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
- data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
- data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
- data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
- data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
- data/spec/higher_level_api/integration/connection_recovery_spec.rb +424 -221
- data/spec/higher_level_api/integration/connection_spec.rb +300 -126
- data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
- data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +34 -11
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
- data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
- data/spec/higher_level_api/integration/heartbeat_spec.rb +26 -8
- data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
- data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +156 -42
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
- data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
- data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
- data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
- data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
- data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
- data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
- data/spec/higher_level_api/integration/tls_connection_spec.rb +224 -89
- data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
- data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
- data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
- data/spec/issues/issue100_spec.rb +11 -11
- data/spec/issues/issue141_spec.rb +13 -14
- data/spec/issues/issue202_spec.rb +1 -1
- data/spec/issues/issue224_spec.rb +40 -0
- data/spec/issues/issue465_spec.rb +32 -0
- data/spec/issues/issue549_spec.rb +30 -0
- data/spec/issues/issue78_spec.rb +21 -24
- data/spec/issues/issue83_spec.rb +5 -6
- data/spec/issues/issue97_spec.rb +44 -45
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
- data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
- data/spec/spec_helper.rb +8 -26
- data/spec/stress/channel_close_stress_spec.rb +64 -0
- data/spec/stress/channel_open_stress_spec.rb +15 -9
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
- data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
- data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
- data/spec/stress/connection_open_close_spec.rb +9 -9
- data/spec/stress/merry_go_round_spec.rb +105 -0
- data/spec/tls/client_key.pem +49 -25
- data/spec/tls/generate-server-cert.sh +8 -0
- data/spec/tls/server-openssl.cnf +10 -0
- data/spec/tls/server.csr +16 -0
- data/spec/tls/server_key.pem +49 -25
- data/spec/toxiproxy_helper.rb +28 -0
- data/spec/unit/bunny_spec.rb +5 -5
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
- data/spec/unit/concurrent/condition_spec.rb +8 -8
- data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
- data/spec/unit/exchange_recovery_spec.rb +39 -0
- data/spec/unit/version_delivery_tag_spec.rb +3 -3
- metadata +65 -47
- data/.ruby-version +0 -1
- data/lib/bunny/compatibility.rb +0 -24
- data/lib/bunny/system_timer.rb +0 -20
- data/spec/compatibility/queue_declare_spec.rb +0 -44
- data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
- data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
- data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
- data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
- data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
- data/spec/stress/long_running_consumer_spec.rb +0 -83
- data/spec/tls/cacert.pem +0 -18
- data/spec/tls/client_cert.pem +0 -18
- data/spec/tls/server_cert.pem +0 -18
- data/spec/unit/system_timer_spec.rb +0 -10
data/lib/bunny/test_kit.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
2
5
|
module Bunny
|
3
6
|
# Unit, integration and stress testing toolkit
|
4
7
|
class TestKit
|
5
8
|
class << self
|
6
9
|
|
10
|
+
def poll_while(timeout = 60, &probe)
|
11
|
+
Timeout.timeout(timeout) {
|
12
|
+
sleep 0.1 while probe.call
|
13
|
+
}
|
14
|
+
end
|
15
|
+
def poll_until(timeout = 60, &probe)
|
16
|
+
Timeout.timeout(timeout) {
|
17
|
+
sleep 0.1 until probe.call
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
7
21
|
# @return [Integer] Random integer in the range of [a, b]
|
8
22
|
# @api private
|
9
23
|
def random_in_range(a, b)
|
data/lib/bunny/timeout.rb
CHANGED
@@ -1,16 +1,5 @@
|
|
1
1
|
module Bunny
|
2
|
-
|
3
|
-
# Ruby 1.8) and SystemTimer (the gem)
|
4
|
-
Timeout = if RUBY_VERSION < "1.9"
|
5
|
-
begin
|
6
|
-
require "bunny/system_timer"
|
7
|
-
Bunny::SystemTimer
|
8
|
-
rescue LoadError
|
9
|
-
Timeout
|
10
|
-
end
|
11
|
-
else
|
12
|
-
Timeout
|
13
|
-
end
|
2
|
+
Timeout = ::Timeout
|
14
3
|
|
15
4
|
# Backwards compatibility
|
16
5
|
# @private
|
data/lib/bunny/transport.rb
CHANGED
@@ -4,7 +4,7 @@ require "monitor"
|
|
4
4
|
|
5
5
|
begin
|
6
6
|
require "openssl"
|
7
|
-
rescue LoadError =>
|
7
|
+
rescue LoadError => _le
|
8
8
|
$stderr.puts "Could not load OpenSSL"
|
9
9
|
end
|
10
10
|
|
@@ -20,18 +20,22 @@ module Bunny
|
|
20
20
|
#
|
21
21
|
|
22
22
|
# Default TCP connection timeout
|
23
|
-
DEFAULT_CONNECTION_TIMEOUT =
|
24
|
-
# Default TLS protocol version to use.
|
25
|
-
# Currently SSLv3, same as in RabbitMQ Java client
|
26
|
-
DEFAULT_TLS_PROTOCOL = "SSLv3"
|
23
|
+
DEFAULT_CONNECTION_TIMEOUT = 30.0
|
27
24
|
|
25
|
+
DEFAULT_READ_TIMEOUT = 30.0
|
26
|
+
DEFAULT_WRITE_TIMEOUT = 30.0
|
28
27
|
|
29
|
-
attr_reader :session, :host, :port, :socket, :connect_timeout, :
|
30
|
-
attr_reader :tls_context
|
28
|
+
attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
|
29
|
+
attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
|
30
|
+
|
31
|
+
def read_timeout=(v)
|
32
|
+
@read_timeout = v
|
33
|
+
@read_timeout = nil if @read_timeout == 0
|
34
|
+
end
|
31
35
|
|
32
36
|
def initialize(session, host, port, opts)
|
33
37
|
@session = session
|
34
|
-
@
|
38
|
+
@session_error_handler = opts[:session_error_handler]
|
35
39
|
@host = host
|
36
40
|
@port = port
|
37
41
|
@opts = opts
|
@@ -39,14 +43,22 @@ module Bunny
|
|
39
43
|
@logger = session.logger
|
40
44
|
@tls_enabled = tls_enabled?(opts)
|
41
45
|
|
42
|
-
@
|
43
|
-
@
|
46
|
+
@read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
|
47
|
+
@read_timeout = nil if @read_timeout == 0
|
48
|
+
|
49
|
+
@write_timeout = opts[:socket_timeout] # Backwards compatability
|
50
|
+
|
51
|
+
@write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
|
52
|
+
@write_timeout = nil if @write_timeout == 0
|
53
|
+
|
44
54
|
@connect_timeout = self.timeout_from(opts)
|
45
55
|
@connect_timeout = nil if @connect_timeout == 0
|
46
|
-
@disconnect_timeout = @
|
56
|
+
@disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
|
47
57
|
|
48
58
|
@writes_mutex = @session.mutex_impl.new
|
49
59
|
|
60
|
+
@socket = nil
|
61
|
+
|
50
62
|
prepare_tls_context(opts) if @tls_enabled
|
51
63
|
end
|
52
64
|
|
@@ -70,9 +82,30 @@ module Bunny
|
|
70
82
|
|
71
83
|
|
72
84
|
def connect
|
73
|
-
if
|
74
|
-
|
75
|
-
|
85
|
+
if uses_tls?
|
86
|
+
begin
|
87
|
+
@socket.connect
|
88
|
+
rescue OpenSSL::SSL::SSLError => e
|
89
|
+
@logger.error { "TLS connection failed: #{e.message}" }
|
90
|
+
raise e
|
91
|
+
end
|
92
|
+
|
93
|
+
log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
|
94
|
+
log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
|
95
|
+
|
96
|
+
begin
|
97
|
+
@socket.post_connection_check(host) if @verify_peer
|
98
|
+
rescue OpenSSL::SSL::SSLError => e
|
99
|
+
@logger.error do
|
100
|
+
msg = "Peer verification of target server failed: #{e.message}. "
|
101
|
+
msg += "Target hostname: #{hostname}, see peer certificate chain details below."
|
102
|
+
msg
|
103
|
+
end
|
104
|
+
log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
|
105
|
+
log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
|
106
|
+
|
107
|
+
raise e
|
108
|
+
end
|
76
109
|
|
77
110
|
@status = :connected
|
78
111
|
|
@@ -83,7 +116,7 @@ module Bunny
|
|
83
116
|
end
|
84
117
|
|
85
118
|
def connected?
|
86
|
-
:
|
119
|
+
:connected == @status && open?
|
87
120
|
end
|
88
121
|
|
89
122
|
def configure_socket(&block)
|
@@ -94,50 +127,68 @@ module Bunny
|
|
94
127
|
block.call(@tls_context) if @tls_context
|
95
128
|
end
|
96
129
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@writes_mutex.synchronize { @socket.write(data) }
|
107
|
-
@socket.flush
|
130
|
+
if defined?(JRUBY_VERSION)
|
131
|
+
# Writes data to the socket.
|
132
|
+
def write(data)
|
133
|
+
return write_without_timeout(data) unless @write_timeout
|
134
|
+
|
135
|
+
begin
|
136
|
+
if open?
|
137
|
+
@writes_mutex.synchronize do
|
138
|
+
@socket.write(data)
|
108
139
|
end
|
109
140
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
141
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
142
|
+
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
143
|
+
close
|
144
|
+
@status = :not_connected
|
145
|
+
|
146
|
+
if @session.automatically_recover?
|
147
|
+
@session.handle_network_failure(e)
|
148
|
+
else
|
149
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
114
150
|
end
|
115
151
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
152
|
+
end
|
153
|
+
else
|
154
|
+
# Writes data to the socket. If read/write timeout was specified the operation will return after that
|
155
|
+
# amount of time has elapsed waiting for the socket.
|
156
|
+
def write(data)
|
157
|
+
return write_without_timeout(data) unless @write_timeout
|
120
158
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
159
|
+
begin
|
160
|
+
if open?
|
161
|
+
@writes_mutex.synchronize do
|
162
|
+
@socket.write_nonblock_fully(data, @write_timeout)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
166
|
+
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
167
|
+
close
|
168
|
+
@status = :not_connected
|
169
|
+
|
170
|
+
if @session.automatically_recover?
|
171
|
+
@session.handle_network_failure(e)
|
172
|
+
else
|
173
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
174
|
+
end
|
125
175
|
end
|
126
176
|
end
|
127
177
|
end
|
128
178
|
|
129
179
|
# Writes data to the socket without timeout checks
|
130
|
-
def write_without_timeout(data)
|
180
|
+
def write_without_timeout(data, raise_exceptions = false)
|
131
181
|
begin
|
132
182
|
@writes_mutex.synchronize { @socket.write(data) }
|
133
183
|
@socket.flush
|
134
184
|
rescue SystemCallError, Bunny::ConnectionError, IOError => e
|
135
185
|
close
|
186
|
+
raise e if raise_exceptions
|
136
187
|
|
137
188
|
if @session.automatically_recover?
|
138
189
|
@session.handle_network_failure(e)
|
139
190
|
else
|
140
|
-
@
|
191
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
141
192
|
end
|
142
193
|
end
|
143
194
|
end
|
@@ -183,8 +234,20 @@ module Bunny
|
|
183
234
|
@socket.flush if @socket
|
184
235
|
end
|
185
236
|
|
186
|
-
def read_fully(
|
187
|
-
|
237
|
+
def read_fully(count)
|
238
|
+
begin
|
239
|
+
@socket.read_fully(count, @read_timeout)
|
240
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
241
|
+
@logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
|
242
|
+
close
|
243
|
+
@status = :not_connected
|
244
|
+
|
245
|
+
if @session.automatically_recover?
|
246
|
+
raise
|
247
|
+
else
|
248
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
249
|
+
end
|
250
|
+
end
|
188
251
|
end
|
189
252
|
|
190
253
|
def read_ready?(timeout = nil)
|
@@ -192,22 +255,17 @@ module Bunny
|
|
192
255
|
io && io[0].include?(@socket)
|
193
256
|
end
|
194
257
|
|
195
|
-
|
196
258
|
# Exposed primarily for Bunny::Channel
|
197
259
|
# @private
|
198
260
|
def read_next_frame(opts = {})
|
199
|
-
header
|
200
|
-
# TODO: network issues here will sometimes cause
|
201
|
-
# the socket method return an empty string. We need to log
|
202
|
-
# and handle this better.
|
203
|
-
# type, channel, size = begin
|
204
|
-
# AMQ::Protocol::Frame.decode_header(header)
|
205
|
-
# rescue AMQ::Protocol::EmptyResponseError => e
|
206
|
-
# puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
|
207
|
-
# end
|
261
|
+
header = read_fully(7)
|
208
262
|
type, channel, size = AMQ::Protocol::Frame.decode_header(header)
|
209
|
-
payload
|
210
|
-
|
263
|
+
payload = if size > 0
|
264
|
+
read_fully(size)
|
265
|
+
else
|
266
|
+
''
|
267
|
+
end
|
268
|
+
frame_end = read_fully(1)
|
211
269
|
|
212
270
|
# 1) the size is miscalculated
|
213
271
|
if payload.bytesize != size
|
@@ -223,10 +281,10 @@ module Bunny
|
|
223
281
|
def self.reacheable?(host, port, timeout)
|
224
282
|
begin
|
225
283
|
s = Bunny::SocketImpl.open(host, port,
|
226
|
-
:
|
284
|
+
:connect_timeout => timeout)
|
227
285
|
|
228
286
|
true
|
229
|
-
rescue SocketError, Timeout::Error =>
|
287
|
+
rescue SocketError, Timeout::Error => _e
|
230
288
|
false
|
231
289
|
ensure
|
232
290
|
s.close if s
|
@@ -241,7 +299,7 @@ module Bunny
|
|
241
299
|
begin
|
242
300
|
@socket = Bunny::SocketImpl.open(@host, @port,
|
243
301
|
:keepalive => @opts[:keepalive],
|
244
|
-
:
|
302
|
+
:connect_timeout => @connect_timeout)
|
245
303
|
rescue StandardError, ClientTimeout => e
|
246
304
|
@status = :not_connected
|
247
305
|
raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
|
@@ -255,7 +313,7 @@ module Bunny
|
|
255
313
|
end
|
256
314
|
|
257
315
|
def post_initialize_socket
|
258
|
-
@socket = if uses_tls?
|
316
|
+
@socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
|
259
317
|
wrap_in_tls_socket(@socket)
|
260
318
|
else
|
261
319
|
@socket
|
@@ -265,23 +323,27 @@ module Bunny
|
|
265
323
|
protected
|
266
324
|
|
267
325
|
def tls_enabled?(opts)
|
268
|
-
return opts[:tls] unless opts[:tls].nil?
|
269
|
-
return opts[:ssl] unless opts[:ssl].nil?
|
326
|
+
return !!opts[:tls] unless opts[:tls].nil?
|
327
|
+
return !!opts[:ssl] unless opts[:ssl].nil?
|
270
328
|
(opts[:port] == AMQ::Protocol::TLS_PORT) || false
|
271
329
|
end
|
272
330
|
|
331
|
+
def tls_ca_certificates_paths_from(opts)
|
332
|
+
Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
|
333
|
+
end
|
334
|
+
|
273
335
|
def tls_certificate_path_from(opts)
|
274
|
-
opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
336
|
+
opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
275
337
|
end
|
276
338
|
|
277
339
|
def tls_key_path_from(opts)
|
278
|
-
opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
340
|
+
opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
279
341
|
end
|
280
342
|
|
281
343
|
def tls_certificate_from(opts)
|
282
344
|
begin
|
283
345
|
read_client_certificate!
|
284
|
-
rescue MissingTLSCertificateFile =>
|
346
|
+
rescue MissingTLSCertificateFile => _e
|
285
347
|
inline_client_certificate_from(opts)
|
286
348
|
end
|
287
349
|
end
|
@@ -289,14 +351,37 @@ module Bunny
|
|
289
351
|
def tls_key_from(opts)
|
290
352
|
begin
|
291
353
|
read_client_key!
|
292
|
-
rescue MissingTLSKeyFile =>
|
354
|
+
rescue MissingTLSKeyFile => _e
|
293
355
|
inline_client_key_from(opts)
|
294
356
|
end
|
295
357
|
end
|
296
358
|
|
359
|
+
def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
|
360
|
+
exts = peer_cert.extensions.map { |x| x.value }
|
361
|
+
# Subject Alternative Names
|
362
|
+
sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
|
363
|
+
|
364
|
+
msg = "#{prefix} subject: #{peer_cert.subject}, "
|
365
|
+
msg += "subject alternative names: #{sans.join(', ')}, "
|
366
|
+
msg += "issuer: #{peer_cert.issuer}, "
|
367
|
+
msg += "not valid after: #{peer_cert.not_after}, "
|
368
|
+
msg += "X.509 usage extensions: #{exts.join(', ')}"
|
369
|
+
|
370
|
+
msg
|
371
|
+
end
|
372
|
+
|
373
|
+
def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
|
374
|
+
@logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
|
375
|
+
end
|
376
|
+
|
377
|
+
def log_peer_certificate_chain_info(severity, chain)
|
378
|
+
chain.each do |cert|
|
379
|
+
self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
|
380
|
+
end
|
381
|
+
end
|
297
382
|
|
298
383
|
def inline_client_certificate_from(opts)
|
299
|
-
opts[:tls_certificate] || opts[:ssl_cert_string]
|
384
|
+
opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
|
300
385
|
end
|
301
386
|
|
302
387
|
def inline_client_key_from(opts)
|
@@ -304,6 +389,10 @@ module Bunny
|
|
304
389
|
end
|
305
390
|
|
306
391
|
def prepare_tls_context(opts)
|
392
|
+
if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
|
393
|
+
opts[:verify_peer] = true
|
394
|
+
end
|
395
|
+
|
307
396
|
# client cert/key paths
|
308
397
|
@tls_certificate_path = tls_certificate_path_from(opts)
|
309
398
|
@tls_key_path = tls_key_path_from(opts)
|
@@ -312,10 +401,20 @@ module Bunny
|
|
312
401
|
@tls_key = tls_key_from(opts)
|
313
402
|
@tls_certificate_store = opts[:tls_certificate_store]
|
314
403
|
|
315
|
-
@
|
316
|
-
@verify_peer = opts[:verify_ssl] || opts[:verify_peer]
|
404
|
+
@verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
|
317
405
|
|
318
|
-
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
|
406
|
+
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
|
407
|
+
end
|
408
|
+
|
409
|
+
def as_boolean(val)
|
410
|
+
case val
|
411
|
+
when true then true
|
412
|
+
when false then false
|
413
|
+
when "true" then true
|
414
|
+
when "false" then false
|
415
|
+
else
|
416
|
+
!!val
|
417
|
+
end
|
319
418
|
end
|
320
419
|
|
321
420
|
def wrap_in_tls_socket(socket)
|
@@ -323,6 +422,11 @@ module Bunny
|
|
323
422
|
raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
|
324
423
|
|
325
424
|
s = Bunny::SSLSocketImpl.new(socket, @tls_context)
|
425
|
+
|
426
|
+
# always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
|
427
|
+
# that TLS clients supporting the extensions can talk to TLS servers that do not
|
428
|
+
s.hostname = @host if s.respond_to?(:hostname)
|
429
|
+
|
326
430
|
s.sync_close = true
|
327
431
|
s
|
328
432
|
end
|
@@ -349,65 +453,68 @@ module Bunny
|
|
349
453
|
end
|
350
454
|
end
|
351
455
|
|
352
|
-
def initialize_tls_context(ctx)
|
456
|
+
def initialize_tls_context(ctx, opts={})
|
353
457
|
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
|
354
458
|
ctx.key = OpenSSL::PKey::RSA.new(@tls_key) if @tls_key
|
355
459
|
ctx.cert_store = if @tls_certificate_store
|
356
460
|
@tls_certificate_store
|
357
461
|
else
|
462
|
+
# this ivar exists so that this value can be exposed in the API
|
463
|
+
@tls_ca_certificates = tls_ca_certificates_paths_from(opts)
|
358
464
|
initialize_tls_certificate_store(@tls_ca_certificates)
|
359
465
|
end
|
360
466
|
|
361
467
|
if !@tls_certificate
|
362
468
|
@logger.warn <<-MSG
|
363
|
-
|
364
|
-
|
469
|
+
Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
|
470
|
+
certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
|
365
471
|
MSG
|
366
472
|
end
|
367
473
|
if @tls_certificate && !@tls_key
|
368
474
|
@logger.warn "Using TLS but no client private key is provided!"
|
369
475
|
end
|
370
476
|
|
371
|
-
# setting TLS/SSL version only works correctly when done
|
372
|
-
# vis set_params. MK.
|
373
|
-
ctx.set_params(:ssl_version => @opts.fetch(:tls_protocol, DEFAULT_TLS_PROTOCOL))
|
374
|
-
|
375
477
|
verify_mode = if @verify_peer
|
376
478
|
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
377
479
|
else
|
378
480
|
OpenSSL::SSL::VERIFY_NONE
|
379
481
|
end
|
482
|
+
@logger.debug { "Will use peer verification mode #{verify_mode}" }
|
483
|
+
ctx.verify_mode = verify_mode
|
484
|
+
|
485
|
+
if !@verify_peer
|
486
|
+
@logger.warn <<-MSG
|
487
|
+
Using TLS but peer hostname verification is disabled. This is convenient for local development
|
488
|
+
but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
|
489
|
+
MSG
|
490
|
+
end
|
380
491
|
|
381
|
-
|
492
|
+
ssl_version = opts[:tls_protocol] || opts[:ssl_version]
|
493
|
+
ctx.ssl_version = ssl_version if ssl_version
|
382
494
|
|
383
495
|
ctx
|
384
496
|
end
|
385
497
|
|
386
|
-
def default_tls_certificates
|
387
|
-
if defined?(JRUBY_VERSION)
|
388
|
-
# see https://github.com/jruby/jruby/issues/1055. MK.
|
389
|
-
[]
|
390
|
-
else
|
391
|
-
default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
|
392
|
-
default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
|
393
|
-
|
394
|
-
[
|
395
|
-
default_ca_file,
|
396
|
-
File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
|
397
|
-
File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
|
398
|
-
File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
|
399
|
-
].uniq
|
400
|
-
end
|
401
|
-
end
|
402
|
-
|
403
498
|
def initialize_tls_certificate_store(certs)
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
499
|
+
cert_files = []
|
500
|
+
cert_inlines = []
|
501
|
+
certs.each do |cert|
|
502
|
+
# if it starts with / or C:/ then it's a file path that may or may not
|
503
|
+
# exist (e.g. a default OpenSSL path). MK.
|
504
|
+
if File.readable?(cert) || cert =~ /^([a-z]:?)?\//i
|
505
|
+
cert_files.push(cert)
|
506
|
+
else
|
507
|
+
cert_inlines.push(cert)
|
508
|
+
end
|
408
509
|
end
|
510
|
+
@logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
|
511
|
+
@logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
|
409
512
|
OpenSSL::X509::Store.new.tap do |store|
|
410
|
-
|
513
|
+
store.set_default_paths
|
514
|
+
cert_files.select { |path| File.readable?(path) }.
|
515
|
+
each { |path| store.add_file(path) }
|
516
|
+
cert_inlines.
|
517
|
+
each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
|
411
518
|
end
|
412
519
|
end
|
413
520
|
|