PYTHON-2718 Test redaction of security sensitive command monitoring events (#637)
This commit is contained in:
parent
abb081a012
commit
59dc6d8ca0
@ -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
|
||||
|
||||
492
test/command_monitoring/unified/redacted-commands.json
Normal file
492
test/command_monitoring/unified/redacted-commands.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
test/load_balancer/test_command_monitoring_unified.py
Normal file
23
test/load_balancer/test_command_monitoring_unified.py
Normal file
@ -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()
|
||||
@ -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()
|
||||
38
test/test_command_monitoring_unified.py
Normal file
38
test/test_command_monitoring_unified.py
Normal file
@ -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()
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user