Skip to content

Commit a8d41fc

Browse files
RUBY-3831 Skip OpenTelemetry command spans for sensitive commands (#3038)
1 parent 1c6ef47 commit a8d41fc

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

201279
describe '#span_attributes' do

0 commit comments

Comments
 (0)