feat: add corrupt cookie header monitoring (#30039)

In case of unusual cookie headers containing "Cookie ",
add custom attributes for monitoring:
- cookies.header.corrupt_count
- cookies.header.corrupt_key_count

See annotation documentation for more details.

Separately, updated to skip cookie log sampling for
0 size cookie header.

ARCHBOM-2055
This commit is contained in:
Robert Raposa
2022-03-11 14:36:26 -05:00
committed by GitHub
parent c32d3b2762
commit 78ba9f09a1
2 changed files with 62 additions and 0 deletions

View File

@@ -143,6 +143,28 @@ class CookieMonitoringMiddleware:
# .. custom_attribute_description: The total size in bytes of the cookie header.
set_custom_attribute('cookies.header.size', cookie_header_size)
if cookie_header_size == 0:
return
if corrupt_cookie_count := raw_header_cookie.count('Cookie: '):
# .. custom_attribute_name: cookies.header.corrupt_count
# .. custom_attribute_description: The attribute will only appear for potentially corrupt cookie headers,
# where "Cookie: " is found in the header. If this custom attribute is seen on the same
# requests where other mysterious cookie problems are occurring, this may help troubleshoot.
# See https://openedx.atlassian.net/browse/CR-4614 for more details.
# Also see cookies.header.corrupt_key_count
set_custom_attribute('cookies.header.corrupt_count', corrupt_cookie_count)
# .. custom_attribute_name: cookies.header.corrupt_key_count
# .. custom_attribute_description: The attribute will only appear for potentially corrupt cookie headers,
# where "Cookie: " is found in some of the cookie keys. If this custom attribute is seen on the same
# requests where other mysterious cookie problems are occurring, this may help troubleshoot.
# See https://openedx.atlassian.net/browse/CR-4614 for more details.
# Also see cookies.header.corrupt_count.
set_custom_attribute(
'cookies.header.corrupt_key_count',
sum(1 for key in request.COOKIES.keys() if 'Cookie: ' in key)
)
# .. setting_name: COOKIE_HEADER_SIZE_LOGGING_THRESHOLD
# .. setting_default: None
# .. setting_description: The minimum size for the full cookie header to log a list of cookie names and sizes.

View File

@@ -135,6 +135,31 @@ class CookieMonitoringMiddlewareTestCase(unittest.TestCase):
# cookie logging was not enabled, so nothing should be logged
mock_logger.info.assert_not_called()
@override_settings(COOKIE_HEADER_SIZE_LOGGING_THRESHOLD=None)
@override_settings(COOKIE_SAMPLING_REQUEST_COUNT=None)
@patch("openedx.core.lib.request_utils.set_custom_attribute")
@ddt.data(
# A corrupt cookie header contains "Cookie: ".
('corruptCookie: normal-cookie=value', 1, 1),
('corrupt1Cookie: normal-cookie1=value1;corrupt2Cookie: normal-cookie2=value2', 2, 2),
('corrupt=Cookie: value', 1, 0),
)
@ddt.unpack
def test_cookie_header_corrupt_monitoring(
self, corrupt_cookie_header, expected_corrupt_count, expected_corrupt_key_count, mock_set_custom_attribute
):
middleware = CookieMonitoringMiddleware(self.mock_response)
request = RequestFactory().request()
request.META['HTTP_COOKIE'] = corrupt_cookie_header
middleware(request)
mock_set_custom_attribute.assert_has_calls([
call('cookies.header.size', len(request.META['HTTP_COOKIE'])),
call('cookies.header.corrupt_count', expected_corrupt_count),
call('cookies.header.corrupt_key_count', expected_corrupt_key_count),
])
@override_settings(COOKIE_HEADER_SIZE_LOGGING_THRESHOLD=1)
@patch('openedx.core.lib.request_utils.log', autospec=True)
@patch("openedx.core.lib.request_utils.set_custom_attribute")
@@ -178,6 +203,21 @@ class CookieMonitoringMiddlewareTestCase(unittest.TestCase):
"Sampled small (< 9999) cookie header. BEGIN-COOKIE-SIZES(total=16) b: 3, a: 2, c: 1 END-COOKIE-SIZES"
)
@override_settings(COOKIE_HEADER_SIZE_LOGGING_THRESHOLD=9999)
@override_settings(COOKIE_SAMPLING_REQUEST_COUNT=1)
@patch('openedx.core.lib.request_utils.log', autospec=True)
@patch("openedx.core.lib.request_utils.set_custom_attribute")
def test_empty_cookie_header_skips_sampling(self, mock_set_custom_attribute, mock_logger):
middleware = CookieMonitoringMiddleware(self.mock_response)
cookies_dict = {}
middleware(self.get_mock_request(cookies_dict))
mock_set_custom_attribute.assert_has_calls([
call('cookies.header.size', 0),
])
mock_logger.info.assert_not_called()
def get_mock_request(self, cookies_dict):
"""
Return mock request with the provided cookies in the header.