diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index 5e86e93e0..0643a26d1 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -509,6 +509,15 @@ _SENSITIVE_COMMANDS = set( "updateuser", "copydbgetnonce", "copydbsaslstart", "copydb"]) +# The "hello" command is also deemed sensitive when attempting speculative +# authentication. +def _is_speculative_authenticate(command_name, doc): + if (command_name.lower() in ('hello', 'ismaster') and + 'speculativeAuthenticate' in doc): + return True + return False + + class _CommandEvent(object): """Base class for command events.""" @@ -573,7 +582,9 @@ class CommandStartedEvent(_CommandEvent): command_name = next(iter(command)) super(CommandStartedEvent, self).__init__( command_name, *args, service_id=service_id) - if command_name.lower() in _SENSITIVE_COMMANDS: + cmd_name, cmd_doc = command_name.lower(), command[command_name] + if (cmd_name in _SENSITIVE_COMMANDS or + _is_speculative_authenticate(cmd_name, command)): self.__cmd = {} else: self.__cmd = command @@ -619,7 +630,9 @@ class CommandSucceededEvent(_CommandEvent): command_name, request_id, connection_id, operation_id, service_id=service_id) self.__duration_micros = _to_micros(duration) - if command_name.lower() in _SENSITIVE_COMMANDS: + cmd_name = command_name.lower() + if (cmd_name in _SENSITIVE_COMMANDS or + _is_speculative_authenticate(cmd_name, reply)): self.__reply = {} else: self.__reply = reply diff --git a/test/command_monitoring/bulkWrite.json b/test/command_monitoring/legacy/bulkWrite.json similarity index 100% rename from test/command_monitoring/bulkWrite.json rename to test/command_monitoring/legacy/bulkWrite.json diff --git a/test/command_monitoring/command.json b/test/command_monitoring/legacy/command.json similarity index 100% rename from test/command_monitoring/command.json rename to test/command_monitoring/legacy/command.json diff --git a/test/command_monitoring/deleteMany.json b/test/command_monitoring/legacy/deleteMany.json similarity index 100% rename from test/command_monitoring/deleteMany.json rename to test/command_monitoring/legacy/deleteMany.json diff --git a/test/command_monitoring/deleteOne.json b/test/command_monitoring/legacy/deleteOne.json similarity index 100% rename from test/command_monitoring/deleteOne.json rename to test/command_monitoring/legacy/deleteOne.json diff --git a/test/command_monitoring/find.json b/test/command_monitoring/legacy/find.json similarity index 100% rename from test/command_monitoring/find.json rename to test/command_monitoring/legacy/find.json diff --git a/test/command_monitoring/insertMany.json b/test/command_monitoring/legacy/insertMany.json similarity index 100% rename from test/command_monitoring/insertMany.json rename to test/command_monitoring/legacy/insertMany.json diff --git a/test/command_monitoring/insertOne.json b/test/command_monitoring/legacy/insertOne.json similarity index 100% rename from test/command_monitoring/insertOne.json rename to test/command_monitoring/legacy/insertOne.json diff --git a/test/command_monitoring/unacknowledgedBulkWrite.json b/test/command_monitoring/legacy/unacknowledgedBulkWrite.json similarity index 100% rename from test/command_monitoring/unacknowledgedBulkWrite.json rename to test/command_monitoring/legacy/unacknowledgedBulkWrite.json diff --git a/test/command_monitoring/updateMany.json b/test/command_monitoring/legacy/updateMany.json similarity index 100% rename from test/command_monitoring/updateMany.json rename to test/command_monitoring/legacy/updateMany.json diff --git a/test/command_monitoring/updateOne.json b/test/command_monitoring/legacy/updateOne.json similarity index 100% rename from test/command_monitoring/updateOne.json rename to test/command_monitoring/legacy/updateOne.json diff --git a/test/command_monitoring/unified/redacted-commands.json b/test/command_monitoring/unified/redacted-commands.json new file mode 100644 index 000000000..c53f018d3 --- /dev/null +++ b/test/command_monitoring/unified/redacted-commands.json @@ -0,0 +1,492 @@ +{ + "description": "redacted-commands", + "schemaVersion": "1.5", + "runOnRequirements": [ + { + "minServerVersion": "5.0", + "auth": false + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "observeSensitiveCommands": true + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + } + ], + "tests": [ + { + "description": "authenticate", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "authenticate", + "command": { + "authenticate": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "authenticate", + "command": { + "authenticate": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "saslStart", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslStart", + "command": { + "saslStart": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "saslStart", + "command": { + "saslStart": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "saslContinue", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslContinue", + "command": { + "saslContinue": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "saslContinue", + "command": { + "saslContinue": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "getnonce", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "getnonce", + "command": { + "getnonce": "private" + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "getnonce", + "command": { + "getnonce": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "createUser", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "createUser", + "command": { + "createUser": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "createUser", + "command": { + "createUser": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateUser", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "updateUser", + "command": { + "updateUser": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "updateUser", + "command": { + "updateUser": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydbgetnonce", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydbsaslstart", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydb", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydb", + "command": { + "copydb": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydb", + "command": { + "copydb": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "hello with speculative authenticate", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": "private", + "speculativeAuthenticate": "foo" + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": "private", + "speculativeAuthenticate": "foo" + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": "private", + "speculativeAuthenticate": "foo" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "hello", + "command": { + "hello": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "ismaster", + "command": { + "ismaster": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "isMaster", + "command": { + "isMaster": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "hello without speculative authenticate is not redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": "public" + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": "public" + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": "public" + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "hello", + "command": { + "hello": "public" + } + } + }, + { + "commandStartedEvent": { + "commandName": "ismaster", + "command": { + "ismaster": "public" + } + } + }, + { + "commandStartedEvent": { + "commandName": "isMaster", + "command": { + "isMaster": "public" + } + } + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/test_command_monitoring_unified.py b/test/load_balancer/test_command_monitoring_unified.py new file mode 100644 index 000000000..6b8ef9832 --- /dev/null +++ b/test/load_balancer/test_command_monitoring_unified.py @@ -0,0 +1,23 @@ +# Copyright 2015-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +sys.path[0:0] = [""] + +from test import unittest +from test.test_command_monitoring_unified import * + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_command_monitoring_spec.py b/test/test_command_monitoring_legacy.py similarity index 97% rename from test/test_command_monitoring_spec.py rename to test/test_command_monitoring_legacy.py index 3363a4256..16bdf1c68 100644 --- a/test/test_command_monitoring_spec.py +++ b/test/test_command_monitoring_legacy.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Run the command monitoring spec tests.""" +"""Run the command monitoring legacy-format spec tests.""" import os import re @@ -26,7 +26,8 @@ from bson import json_util from pymongo.errors import OperationFailure from pymongo.write_concern import WriteConcern from test import unittest, client_context -from test.utils import single_client, wait_until, EventListener, parse_read_preference +from test.utils import ( + single_client, wait_until, EventListener, parse_read_preference) # Location of JSON test specifications. _TEST_PATH = os.path.join( @@ -204,7 +205,7 @@ def create_test(scenario_def, test): def create_tests(): - for dirpath, _, filenames in os.walk(_TEST_PATH): + for dirpath, _, filenames in os.walk(os.path.join(_TEST_PATH, 'legacy')): dirname = os.path.split(dirpath)[-1] for filename in filenames: with open(os.path.join(dirpath, filename)) as scenario_stream: @@ -239,5 +240,6 @@ def create_tests(): create_tests() + if __name__ == "__main__": unittest.main() diff --git a/test/test_command_monitoring_unified.py b/test/test_command_monitoring_unified.py new file mode 100644 index 000000000..9390c9fec --- /dev/null +++ b/test/test_command_monitoring_unified.py @@ -0,0 +1,38 @@ +# Copyright 2015-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Run the command monitoring unified format spec tests.""" + +import os +import sys + +sys.path[0:0] = [""] + +from test import unittest +from test.unified_format import generate_test_classes + + +# Location of JSON test specifications. +_TEST_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + 'command_monitoring') + + +globals().update(generate_test_classes( + os.path.join(_TEST_PATH, 'unified'), + module=__name__,)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/unified_format.py b/test/unified_format.py index 9bc5d4afa..d24e280b2 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -178,10 +178,14 @@ class NonLazyCursor(object): class EventListenerUtil(CMAPListener, CommandListener): - def __init__(self, observe_events, ignore_commands): + def __init__(self, observe_events, ignore_commands, + observe_sensitive_commands): self._event_types = set(name.lower() for name in observe_events) - self._ignore_commands = _SENSITIVE_COMMANDS | set(ignore_commands) - self._ignore_commands.add('configurefailpoint') + if observe_sensitive_commands: + self._ignore_commands = set(ignore_commands) + else: + self._ignore_commands = _SENSITIVE_COMMANDS | set(ignore_commands) + self._ignore_commands.add('configurefailpoint') super(EventListenerUtil, self).__init__() def get_events(self, event_type): @@ -244,10 +248,13 @@ class EntityMapUtil(object): kwargs = {} observe_events = spec.get('observeEvents', []) ignore_commands = spec.get('ignoreCommandMonitoringEvents', []) + observe_sensitive_commands = spec.get( + 'observeSensitiveCommands', False) # TODO: SUPPORT storeEventsAsEntities if len(observe_events) or len(ignore_commands): ignore_commands = [cmd.lower() for cmd in ignore_commands] - listener = EventListenerUtil(observe_events, ignore_commands) + listener = EventListenerUtil( + observe_events, ignore_commands, observe_sensitive_commands) self._listeners[spec['id']] = listener kwargs['event_listeners'] = [listener] if spec.get('useMultipleMongoses'): @@ -623,7 +630,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest): Specification of the test suite being currently run is available as a class attribute ``TEST_SPEC``. """ - SCHEMA_VERSION = Version.from_string('1.4') + SCHEMA_VERSION = Version.from_string('1.5') @staticmethod def should_run_on(run_on_spec):