Skip to content

Commit d45140c

Browse files
RUBY-3831 Skip OpenTelemetry command spans for sensitive commands (#3038)
1 parent 1708c3d commit d45140c

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

lib/mongo/tracing/open_telemetry/command_tracer.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ module OpenTelemetry
2121
#
2222
# @api private
2323
class CommandTracer
24+
include Mongo::Monitoring::Event::Secure
25+
26+
# Commands for which a span MUST NOT be created. The OpenTelemetry spec
27+
# requires drivers to skip command spans for sensitive commands listed in
28+
# the Command Logging and Monitoring spec. We additionally skip hello /
29+
# legacy hello in all forms — these are handshake/heartbeat traffic and
30+
# would only add noise to traces.
31+
HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
32+
2433
# Initializes a new CommandTracer.
2534
#
2635
# @param otel_tracer [ OpenTelemetry::Trace::Tracer ] the OpenTelemetry tracer.
@@ -57,6 +66,8 @@ def start_span(message, operation_context, connection); end
5766
# @return [ Object ] the result of the command.
5867
# rubocop:disable Lint/RescueException
5968
def trace_command(message, _operation_context, connection)
69+
return yield if skip_tracing?(message)
70+
6071
# Commands should always be nested under their operation span, not directly under
6172
# the transaction span. Don't pass with_parent to use automatic parent resolution
6273
# from the currently active span (the operation span).
@@ -76,6 +87,22 @@ def trace_command(message, _operation_context, connection)
7687

7788
private
7889

90+
# Determines whether the command must not be traced. Sensitive auth
91+
# commands carry credentials in their payloads (SCRAM proofs, cleartext
92+
# passwords, etc.) and the OpenTelemetry spec requires drivers to skip
93+
# command spans for them. Hello / legacy hello are also skipped to keep
94+
# handshake traffic out of traces.
95+
#
96+
# @param message [ Mongo::Protocol::Message ] the command message.
97+
#
98+
# @return [ Boolean ] true when no command span should be created.
99+
def skip_tracing?(message)
100+
name = command_name(message)
101+
return true if HELLO_COMMANDS.include?(name)
102+
103+
sensitive?(command_name: name, document: message.documents.first)
104+
end
105+
79106
# Creates a span for a command.
80107
#
81108
# @param message [ Mongo::Protocol::Message ] the command message.

spec/mongo/tracing/open_telemetry/command_tracer_spec.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,84 @@
197197
end.to raise_error(error)
198198
end
199199
end
200+
201+
shared_examples 'skipped command' do
202+
let(:query_text_max_length) { 1000 }
203+
204+
it 'does not create a span' do
205+
expect(otel_tracer).not_to receive(:start_span)
206+
command_tracer.trace_command(message, operation_context, connection) { result }
207+
end
208+
209+
it 'does not enter an OpenTelemetry context' do
210+
expect(OpenTelemetry::Trace).not_to receive(:with_span)
211+
command_tracer.trace_command(message, operation_context, connection) { result }
212+
end
213+
214+
it 'yields the block' do
215+
yielded = false
216+
command_tracer.trace_command(message, operation_context, connection) do
217+
yielded = true
218+
result
219+
end
220+
expect(yielded).to be true
221+
end
222+
223+
it 'returns the block result' do
224+
return_value = command_tracer.trace_command(message, operation_context, connection) { result }
225+
expect(return_value).to eq(result)
226+
end
227+
end
228+
229+
%w[
230+
authenticate
231+
saslStart
232+
saslContinue
233+
getnonce
234+
createUser
235+
updateUser
236+
copydbgetnonce
237+
copydbsaslstart
238+
copydb
239+
].each do |sensitive_cmd|
240+
context "with #{sensitive_cmd} command" do
241+
let(:document) do
242+
{
243+
sensitive_cmd => 1,
244+
'$db' => 'admin',
245+
'payload' => BSON::Binary.new('secret-credential-bytes')
246+
}
247+
end
248+
249+
include_examples 'skipped command'
250+
end
251+
end
252+
253+
%w[hello ismaster isMaster].each do |hello_cmd|
254+
context "with #{hello_cmd} command without speculativeAuthenticate" do
255+
let(:document) do
256+
{ hello_cmd => 1, '$db' => 'admin' }
257+
end
258+
259+
include_examples 'skipped command'
260+
end
261+
262+
context "with #{hello_cmd} command with speculativeAuthenticate" do
263+
let(:document) do
264+
{
265+
hello_cmd => 1,
266+
'$db' => 'admin',
267+
'speculativeAuthenticate' => {
268+
'saslStart' => 1,
269+
'mechanism' => 'SCRAM-SHA-256',
270+
'payload' => BSON::Binary.new('secret-handshake-bytes')
271+
}
272+
}
273+
end
274+
275+
include_examples 'skipped command'
276+
end
277+
end
200278
end
201279

202280
describe '#span_attributes' do

0 commit comments

Comments
 (0)