From 0e9499de7df204ad41b00c29eb3e9898dc775f1a Mon Sep 17 00:00:00 2001 From: Rebecca Graber Date: Mon, 10 Jan 2022 13:02:47 -0500 Subject: [PATCH] feat: log an encrypted string of the full cookie header when over threshold (#29735) * feat: log an encrypted string of the full cookie header when over threshold --- openedx/core/lib/request_utils.py | 29 ++++++++++++- openedx/core/lib/tests/test_request_utils.py | 45 ++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/openedx/core/lib/request_utils.py b/openedx/core/lib/request_utils.py index 371287fdb0..d3121a6f5b 100644 --- a/openedx/core/lib/request_utils.py +++ b/openedx/core/lib/request_utils.py @@ -14,6 +14,7 @@ from edx_toggles.toggles import WaffleFlag from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from rest_framework.views import exception_handler +from common.djangoapps.util.log_sensitive import encrypt_for_log from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -143,11 +144,37 @@ class CookieMonitoringMiddleware(MiddlewareMixin): - `request_utils.capture_cookie_sizes` - TOP_N_COOKIES_CAPTURED - TOP_N_COOKIE_GROUPS_CAPTURED + - COOKIE_SIZE_LOGGING_THRESHOLD + - COOKIE_HEADER_DEBUG_PUBLIC_KEY """ raw_header_cookie = request.META.get('HTTP_COOKIE', '') - set_custom_attribute('cookies.header.size', len(raw_header_cookie.encode('utf-8'))) + cookie_header_size = len(raw_header_cookie.encode('utf-8')) + set_custom_attribute('cookies.header.size', cookie_header_size) + + # .. setting_name: COOKIE_SIZE_LOGGING_THRESHOLD + # .. setting_default: None + # .. setting_description: The minimum size for logging the entire (encrypted) cookie header. Should be set + # to a relatively high threshold (suggested 9-10K) to avoid flooding the logs. + # .. setting_warning: Requires COOKIE_HEADER_DEBUG_PUBLIC_KEY to be set + logging_threshold = getattr(settings, "COOKIE_SIZE_LOGGING_THRESHOLD", None) + + # .. setting_name: COOKIE_HEADER_DEBUG_PUBLIC_KEY + # .. setting_default: None + # .. setting_description: The public key used to encrypt large cookie headers. See See + # https://github.com/edx/edx-platform/blob/master/common/djangoapps/util/log_sensitive.py + # for instructions on decrypting. + debug_key = getattr(settings, "COOKIE_HEADER_DEBUG_PUBLIC_KEY", None) + + if logging_threshold and cookie_header_size > logging_threshold: + if not debug_key: + log.warning("COOKIE_SIZE_LOGGING_THRESHOLD set without COOKIE_HEADER_DEBUG_PUBLIC_KEY") + else: + encrypted_cookie_header = encrypt_for_log(str(raw_header_cookie), + debug_key) + log.info(f"Large (> {logging_threshold}) cookie header detected." + f" Encrypted contents: {encrypted_cookie_header}") if not CAPTURE_COOKIE_SIZES.is_enabled(): return diff --git a/openedx/core/lib/tests/test_request_utils.py b/openedx/core/lib/tests/test_request_utils.py index 35369deb6f..0a9021d65d 100644 --- a/openedx/core/lib/tests/test_request_utils.py +++ b/openedx/core/lib/tests/test_request_utils.py @@ -267,6 +267,51 @@ class RequestUtilTestCase(unittest.TestCase): call('cookies_total_size', 25), ], any_order=True) + @override_settings(COOKIE_SIZE_LOGGING_THRESHOLD=1) + @patch("openedx.core.lib.request_utils.CAPTURE_COOKIE_SIZES") + @patch("openedx.core.lib.request_utils.encrypt_for_log") + def test_log_encrypted_cookies_no_key(self, mock_encrypt, mock_capture_cookie_sizes): + middleware = CookieMonitoringMiddleware() + + cookies_dict = { + "a": "." * 10, + "b": "." * 15, + } + + factory = RequestFactory() + for name, value in cookies_dict.items(): + factory.cookies[name] = value + + mock_request = factory.request() + + middleware.process_request(mock_request) + mock_encrypt.assert_not_called() + + @override_settings(COOKIE_SIZE_LOGGING_THRESHOLD=1) + @override_settings(COOKIE_HEADER_DEBUG_PUBLIC_KEY="fake-key") + @patch("openedx.core.lib.request_utils.CAPTURE_COOKIE_SIZES") + @patch("openedx.core.lib.request_utils.encrypt_for_log") + def test_log_encrypted_cookies(self, mock_encrypt, mock_capture_cookie_sizes): + + middleware = CookieMonitoringMiddleware() + + cookies_dict = { + "a": "." * 10, + "b": "." * 15, + } + + factory = RequestFactory() + for name, value in cookies_dict.items(): + factory.cookies[name] = value + + mock_request = factory.request() + cookie_header = str(mock_request.META.get('HTTP_COOKIE', '')) + + middleware.process_request(mock_request) + mock_encrypt.assert_has_calls([ + call(cookie_header, "fake-key") + ]) + class TestGetExpectedErrorSettingsDict(unittest.TestCase): """