PYTHON-2718 Test redaction of security sensitive command monitoring events (#637)

This commit is contained in:
Prashant Mital 2021-06-21 18:07:28 -07:00 committed by GitHub
parent abb081a012
commit 59dc6d8ca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 585 additions and 10 deletions

View File

@ -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

View 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"
}
}
}
]
}
]
}
]
}

View 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()

View File

@ -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()

View 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()

View File

@ -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):