Skip to content

Commit 458d48f

Browse files
authored
RUBY-1743 Warn on handshake and auth failures (#1283)
1 parent c9238ba commit 458d48f

File tree

6 files changed

+120
-49
lines changed

6 files changed

+120
-49
lines changed

lib/mongo/server/connection.rb

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -217,59 +217,75 @@ def handshake!(socket)
217217
raise Error::HandshakeError, "Cannot handshake because there is no usable socket"
218218
end
219219

220+
response = average_rtt = nil
220221
@server.handle_handshake_failure! do
221-
response, exc, rtt, average_rtt =
222-
@server.monitor.round_trip_time_averager.measure do
223-
socket.write(app_metadata.ismaster_bytes)
224-
Protocol::Message.deserialize(socket, max_message_size).documents[0]
225-
end
222+
begin
223+
response, exc, rtt, average_rtt =
224+
@server.monitor.round_trip_time_averager.measure do
225+
socket.write(app_metadata.ismaster_bytes)
226+
Protocol::Message.deserialize(socket, max_message_size).documents[0]
227+
end
226228

227-
if exc
228-
raise exc
229+
if exc
230+
raise exc
231+
end
232+
rescue => e
233+
log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
234+
raise
229235
end
236+
end
230237

231-
if response["ok"] == 1
232-
# Auth mechanism is entirely dependent on the contents of
233-
# ismaster response *for this connection*.
234-
# Ismaster received by the monitoring connection should advertise
235-
# the same wire protocol, but if it doesn't, we use whatever
236-
# the monitoring connection advertised for filling out the
237-
# server description and whatever the non-monitoring connection
238-
# (that's this one) advertised for performing auth on that
239-
# connection.
240-
@auth_mechanism = if response['saslSupportedMechs']
241-
if response['saslSupportedMechs'].include?(Mongo::Auth::SCRAM::SCRAM_SHA_256_MECHANISM)
242-
:scram256
243-
else
244-
:scram
245-
end
238+
post_handshake(response, average_rtt)
239+
end
240+
241+
# This is a separate method to keep the nesting level down.
242+
def post_handshake(response, average_rtt)
243+
if response["ok"] == 1
244+
# Auth mechanism is entirely dependent on the contents of
245+
# ismaster response *for this connection*.
246+
# Ismaster received by the monitoring connection should advertise
247+
# the same wire protocol, but if it doesn't, we use whatever
248+
# the monitoring connection advertised for filling out the
249+
# server description and whatever the non-monitoring connection
250+
# (that's this one) advertised for performing auth on that
251+
# connection.
252+
@auth_mechanism = if response['saslSupportedMechs']
253+
if response['saslSupportedMechs'].include?(Mongo::Auth::SCRAM::SCRAM_SHA_256_MECHANISM)
254+
:scram256
246255
else
247-
# MongoDB servers < 2.6 are no longer suported.
248-
# Wire versions should always be returned in ismaster.
249-
# See also https://jira.mongodb.org/browse/RUBY-1584.
250-
min_wire_version = response[Description::MIN_WIRE_VERSION]
251-
max_wire_version = response[Description::MAX_WIRE_VERSION]
252-
features = Description::Features.new(min_wire_version..max_wire_version)
253-
if features.scram_sha_1_enabled?
254-
:scram
255-
else
256-
:mongodb_cr
257-
end
256+
:scram
258257
end
259258
else
260-
@auth_mechanism = nil
259+
# MongoDB servers < 2.6 are no longer suported.
260+
# Wire versions should always be returned in ismaster.
261+
# See also https://jira.mongodb.org/browse/RUBY-1584.
262+
min_wire_version = response[Description::MIN_WIRE_VERSION]
263+
max_wire_version = response[Description::MAX_WIRE_VERSION]
264+
features = Description::Features.new(min_wire_version..max_wire_version)
265+
if features.scram_sha_1_enabled?
266+
:scram
267+
else
268+
:mongodb_cr
269+
end
261270
end
262-
263-
new_description = Description.new(address, response, average_rtt)
264-
@server.monitor.publish(Event::DESCRIPTION_CHANGED, @server.description, new_description)
271+
else
272+
@auth_mechanism = nil
265273
end
274+
275+
new_description = Description.new(address, response, average_rtt)
276+
@server.monitor.publish(Event::DESCRIPTION_CHANGED, @server.description, new_description)
266277
end
267278

268279
def authenticate!(pending_connection)
269280
if options[:user] || options[:auth_mech]
270281
user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism).merge(options))
271282
@server.handle_auth_failure! do
272-
Auth.get(user).login(pending_connection)
283+
begin
284+
Auth.get(user).login(pending_connection)
285+
rescue => e
286+
log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
287+
raise
288+
end
273289
end
274290
end
275291
end

lib/mongo/server/monitor/connection.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ def handshake!(socket)
227227
set_compressor!(reply)
228228
reply
229229
end
230+
rescue => e
231+
log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
232+
raise
230233
end
231234

232235
def retry_message

spec/mongo/server/connection_spec.rb

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ class ConnectionSpecTestException < Exception; end
88
ClientRegistry.instance.close_all_clients
99
end
1010

11-
after(:all) do
12-
ClientRegistry.instance.close_all_clients
13-
end
14-
15-
let(:address) do
11+
let!(:address) do
1612
default_address
1713
end
1814

@@ -43,10 +39,9 @@ class ConnectionSpecTestException < Exception; end
4339

4440
declare_topology_double
4541

42+
let(:server_options) { SpecConfig.instance.test_options.merge(monitoring_io: false) }
4643
let(:server) do
47-
Mongo::Server.new(address, cluster, monitoring, listeners,
48-
SpecConfig.instance.test_options.merge(monitoring_io: false),
49-
)
44+
Mongo::Server.new(address, cluster, monitoring, listeners, server_options)
5045
end
5146

5247
let(:monitored_server) do
@@ -142,6 +137,23 @@ class ConnectionSpecTestException < Exception; end
142137
end
143138
end
144139

140+
shared_examples_for 'logs a warning' do
141+
let(:expected_message) do
142+
"MONGODB | Failed to handshake with #{address}: #{error.class}: #{error}"
143+
end
144+
145+
it 'logs a warning' do
146+
messages = []
147+
# Straightforward expectations are not working here for some reason
148+
expect(Mongo::Logger.logger).to receive(:warn) do |msg|
149+
messages << msg
150+
end
151+
expect(error).not_to be nil
152+
expect(messages).to include(expected_message)
153+
end
154+
155+
end
156+
145157
context 'when #handshake! dependency raises a non-network exception' do
146158

147159
let(:exception) do
@@ -181,15 +193,24 @@ class ConnectionSpecTestException < Exception; end
181193

182194
it_behaves_like 'failing connection'
183195
it_behaves_like 'marks server unknown'
196+
it_behaves_like 'logs a warning'
184197
end
185198

186199
context 'when #authenticate! raises an exception' do
200+
require_auth
201+
202+
let(:server_options) do
203+
SpecConfig.instance.test_options.merge(monitoring_io: false).
204+
merge(SpecConfig.instance.auth_options)
205+
end
206+
187207
let(:exception) do
188208
Mongo::Error::OperationFailure.new
189209
end
190210

191211
let(:error) do
192-
expect(connection).to receive(:authenticate!).and_raise(exception)
212+
expect(Mongo::Auth).to receive(:get).and_raise(exception)
213+
expect(connection.send(:socket)).to be nil
193214
begin
194215
connection.connect!
195216
rescue Exception => e
@@ -200,6 +221,7 @@ class ConnectionSpecTestException < Exception; end
200221
end
201222

202223
it_behaves_like 'failing connection'
224+
it_behaves_like 'logs a warning'
203225
end
204226

205227
context 'when a non-Mongo exception is raised' do

spec/mongo/server/monitor/connection_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,26 @@
158158
end
159159
end
160160
end
161+
162+
describe '#connect!' do
163+
context 'network error' do
164+
before do
165+
address
166+
server.monitor.instance_variable_get('@thread').kill
167+
server.monitor.connection.disconnect!
168+
expect_any_instance_of(Mongo::Socket).to receive(:write).and_raise(Mongo::Error::SocketError, 'test error')
169+
end
170+
171+
let(:options) { SpecConfig.instance.test_options }
172+
173+
let(:expected_message) { "MONGODB | Failed to handshake with #{address}: Mongo::Error::SocketError: test error" }
174+
175+
it 'logs a warning' do
176+
expect(Mongo::Logger.logger).to receive(:warn).with(expected_message).and_call_original
177+
expect do
178+
server.monitor.connection.connect!
179+
end.to raise_error(Mongo::Error::SocketError, 'test error')
180+
end
181+
end
182+
end
161183
end

spec/support/constraints.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,15 @@ def ruby_version_lt(version)
138138

139139
def require_auth
140140
before do
141-
unless ENV['AUTH'] == 'auth' || ClusterConfig.instance.auth_enabled?
141+
unless ENV['AUTH'] == 'auth' || SpecConfig.instance.user || ClusterConfig.instance.auth_enabled?
142142
skip "Auth required"
143143
end
144144
end
145145
end
146146

147147
def require_no_auth
148148
before do
149-
if ENV['AUTH'] == 'auth' || ClusterConfig.instance.auth_enabled?
149+
if ENV['AUTH'] == 'auth' || SpecConfig.instance.user || ClusterConfig.instance.auth_enabled?
150150
skip "Auth not allowed"
151151
end
152152
end

spec/support/spec_config.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ def test_db
148148

149149
# Option hashes
150150

151+
def auth_options
152+
{
153+
user: user,
154+
password: password,
155+
auth_source: auth_source,
156+
}
157+
end
158+
151159
def ssl_options
152160
if ssl?
153161
{

0 commit comments

Comments
 (0)