Trim sensitive information in the logs (CVE-2017-8761)
Several headers and query params were previously revealed in logs but are now redacted: * X-Auth-Token header (previously redacted in the {auth_token} field, but not the {headers} field) * temp_url_sig query param (used by tempurl middleware) * Authorization header and X-Amz-Signature and Signature query parameters (used by s3api middleware) This patch adds some new middleware helper methods to track headers and query parameters that should be redacted by proxy-logging. While instantiating the middleware, authors can call either: register_sensitive_header('case-insensitive-header-name') register_sensitive_param('case-sensitive-query-param-name') to add items that should be redacted. The redaction uses proxy-logging's existing reveal_sensitive_prefix config option to determine how much to reveal. Note that query params will still be logged in their entirety if eventlet_debug is enabled. UpgradeImpact ============= The reveal_sensitive_prefix config option now applies to more items; operators should review their currently-configured value to ensure it is appropriate for these new contexts. In particular, operators should consider reducing the value if it is more than 20 or so, even if that previously offered sufficient protection for auth tokens. Co-Authored-By: Tim Burke <tim.burke@gmail.com> Closes-Bug: #1685798 Change-Id: I88b8cfd30292325e0870029058da6fb38026ae1a
This commit is contained in:
parent
57f17ace7c
commit
f2c279bae9
@ -178,13 +178,25 @@ Middleware may advertize its availability and capabilities via Swift's
|
||||
:ref:`discoverability` support by using
|
||||
:func:`.register_swift_info`::
|
||||
|
||||
from swift.common.utils import register_swift_info
|
||||
from swift.common.registry import register_swift_info
|
||||
def webhook_factory(global_conf, **local_conf):
|
||||
register_swift_info('webhook')
|
||||
def webhook_filter(app):
|
||||
return WebhookMiddleware(app)
|
||||
return webhook_filter
|
||||
|
||||
If a middleware handles sensitive information in headers or query parameters
|
||||
that may need redaction when logging, use the :func:`.register_sensitive_header`
|
||||
and :func:`.register_sensitive_param` functions. This should be done in the
|
||||
filter factory::
|
||||
|
||||
from swift.common.registry import register_sensitive_header
|
||||
def webhook_factory(global_conf, **local_conf):
|
||||
register_sensitive_header('webhook-api-key')
|
||||
def webhook_filter(app):
|
||||
return WebhookMiddleware(app)
|
||||
return webhook_filter
|
||||
|
||||
|
||||
--------------
|
||||
Swift Metadata
|
||||
|
@ -86,6 +86,9 @@ bind_port = 8080
|
||||
# cors_expose_headers =
|
||||
#
|
||||
# client_timeout = 60.0
|
||||
#
|
||||
# Note: enabling evenlet_debug might reveal sensitive information, for example
|
||||
# signatures for temp urls
|
||||
# eventlet_debug = false
|
||||
#
|
||||
# You can set scheduling priority of processes. Niceness values range from -20
|
||||
@ -998,16 +1001,17 @@ use = egg:swift#proxy_logging
|
||||
# list like this: access_log_headers_only = Host, X-Object-Meta-Mtime
|
||||
# access_log_headers_only =
|
||||
#
|
||||
# By default, the X-Auth-Token is logged. To obscure the value,
|
||||
# set reveal_sensitive_prefix to the number of characters to log.
|
||||
# For example, if set to 12, only the first 12 characters of the
|
||||
# token appear in the log. An unauthorized access of the log file
|
||||
# won't allow unauthorized usage of the token. However, the first
|
||||
# 12 or so characters is unique enough that you can trace/debug
|
||||
# token usage. Set to 0 to suppress the token completely (replaced
|
||||
# by '...' in the log).
|
||||
# Note: reveal_sensitive_prefix will not affect the value
|
||||
# logged with access_log_headers=True.
|
||||
# The default log format includes several sensitive values in logs:
|
||||
# * X-Auth-Token header
|
||||
# * temp_url_sig query parameter
|
||||
# * Authorization header
|
||||
# * X-Amz-Signature query parameter
|
||||
# To prevent an unauthorized access of the log file leading to an unauthorized
|
||||
# access of cluster data, only a portion of these values are written, with the
|
||||
# remainder replaced by '...' in the log. Set reveal_sensitive_prefix to the
|
||||
# number of characters to log. Set to 0 to suppress the values entirely; set
|
||||
# to something large (1000, say) to write full values. Note that some values
|
||||
# may start appearing in full at values as low as 33.
|
||||
# reveal_sensitive_prefix = 16
|
||||
#
|
||||
# What HTTP methods are allowed for StatsD logging (comma-sep); request methods
|
||||
|
@ -84,6 +84,8 @@ from swift.common.utils import (get_logger, get_remote_client,
|
||||
LogStringFormatter)
|
||||
|
||||
from swift.common.storage_policy import POLICIES
|
||||
from swift.common.registry import get_sensitive_headers, \
|
||||
get_sensitive_params, register_sensitive_header
|
||||
|
||||
|
||||
class ProxyLoggingMiddleware(object):
|
||||
@ -200,6 +202,24 @@ class ProxyLoggingMiddleware(object):
|
||||
return value[:self.reveal_sensitive_prefix] + '...'
|
||||
return value
|
||||
|
||||
def obscure_req(self, req):
|
||||
for header in get_sensitive_headers():
|
||||
if header in req.headers:
|
||||
req.headers[header] = \
|
||||
self.obscure_sensitive(req.headers[header])
|
||||
|
||||
obscure_params = get_sensitive_params()
|
||||
new_params = []
|
||||
any_obscured = False
|
||||
for k, v in req.params.items():
|
||||
if k in obscure_params:
|
||||
new_params.append((k, self.obscure_sensitive(v)))
|
||||
any_obscured = True
|
||||
else:
|
||||
new_params.append((k, v))
|
||||
if any_obscured:
|
||||
req.params = new_params
|
||||
|
||||
def log_request(self, req, status_int, bytes_received, bytes_sent,
|
||||
start_time, end_time, resp_headers=None, ttfb=0,
|
||||
wire_status_int=None):
|
||||
@ -215,6 +235,7 @@ class ProxyLoggingMiddleware(object):
|
||||
:param resp_headers: dict of the response headers
|
||||
:param wire_status_int: the on the wire status int
|
||||
"""
|
||||
self.obscure_req(req)
|
||||
resp_headers = resp_headers or {}
|
||||
logged_headers = None
|
||||
if self.log_hdrs:
|
||||
@ -270,8 +291,7 @@ class ProxyLoggingMiddleware(object):
|
||||
req.environ.get('SERVER_PROTOCOL'),
|
||||
'status_int': status_int,
|
||||
'auth_token':
|
||||
self.obscure_sensitive(
|
||||
req.headers.get('x-auth-token')),
|
||||
req.headers.get('x-auth-token'),
|
||||
'bytes_recvd': bytes_received,
|
||||
'bytes_sent': bytes_sent,
|
||||
'transaction_id': req.environ.get('swift.trans_id'),
|
||||
@ -445,6 +465,12 @@ def filter_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
|
||||
# Normally it would be the middleware that uses the header that
|
||||
# would register it, but because there could be 3rd party auth middlewares
|
||||
# that use 'x-auth-token' or 'x-storage-token' we special case it here.
|
||||
register_sensitive_header('x-auth-token')
|
||||
register_sensitive_header('x-storage-token')
|
||||
|
||||
def proxy_logger(app):
|
||||
return ProxyLoggingMiddleware(app, conf)
|
||||
return proxy_logger
|
||||
|
@ -160,7 +160,8 @@ from swift.common.utils import get_logger, config_true_value, \
|
||||
config_positive_int_value, split_path, closing_if_possible, list_from_csv
|
||||
from swift.common.middleware.s3api.utils import Config
|
||||
from swift.common.middleware.s3api.acl_handlers import get_acl_handler
|
||||
from swift.common.registry import register_swift_info
|
||||
from swift.common.registry import register_swift_info, \
|
||||
register_sensitive_header, register_sensitive_param
|
||||
|
||||
|
||||
class ListingEtagMiddleware(object):
|
||||
@ -464,6 +465,10 @@ def filter_factory(global_conf, **local_conf):
|
||||
s3_acl=config_true_value(conf.get('s3_acl', False)),
|
||||
)
|
||||
|
||||
register_sensitive_header('authorization')
|
||||
register_sensitive_param('Signature')
|
||||
register_sensitive_param('X-Amz-Signature')
|
||||
|
||||
def s3api_filter(app):
|
||||
return S3ApiMiddleware(ListingEtagMiddleware(app), conf)
|
||||
|
||||
|
@ -315,7 +315,7 @@ from swift.common.swob import header_to_environ_key, HTTPUnauthorized, \
|
||||
HTTPBadRequest, wsgi_to_str
|
||||
from swift.common.utils import split_path, get_valid_utf8_str, \
|
||||
get_hmac, streq_const_time, quote, get_logger, strict_b64decode
|
||||
from swift.common.registry import register_swift_info
|
||||
from swift.common.registry import register_swift_info, register_sensitive_param
|
||||
|
||||
|
||||
DISALLOWED_INCOMING_HEADERS = 'x-object-manifest x-symlink-target'
|
||||
@ -873,4 +873,6 @@ def filter_factory(global_conf, **local_conf):
|
||||
register_swift_info('tempurl', **info_conf)
|
||||
conf.update(info_conf)
|
||||
|
||||
register_sensitive_param('temp_url_sig')
|
||||
|
||||
return lambda app: TempURL(app, conf)
|
||||
|
@ -16,6 +16,7 @@
|
||||
# Used by get_swift_info and register_swift_info to store information about
|
||||
# the swift cluster.
|
||||
from copy import deepcopy
|
||||
import six
|
||||
|
||||
_swift_info = {}
|
||||
_swift_admin_info = {}
|
||||
@ -84,3 +85,35 @@ def register_swift_info(name='swift', admin=False, **kwargs):
|
||||
if "." in key:
|
||||
raise ValueError('Cannot use "." in a swift_info key: %s' % key)
|
||||
dict_to_use[name][key] = val
|
||||
|
||||
|
||||
_sensitive_headers = set()
|
||||
_sensitive_params = set()
|
||||
|
||||
|
||||
def get_sensitive_headers():
|
||||
return frozenset(_sensitive_headers)
|
||||
|
||||
|
||||
def register_sensitive_header(header):
|
||||
if not isinstance(header, str):
|
||||
raise TypeError
|
||||
if six.PY2:
|
||||
header.decode('ascii')
|
||||
else:
|
||||
header.encode('ascii')
|
||||
_sensitive_headers.add(header)
|
||||
|
||||
|
||||
def get_sensitive_params():
|
||||
return frozenset(_sensitive_params)
|
||||
|
||||
|
||||
def register_sensitive_param(query_param):
|
||||
if not isinstance(query_param, str):
|
||||
raise TypeError
|
||||
if six.PY2:
|
||||
query_param.decode('ascii')
|
||||
else:
|
||||
query_param.encode('ascii')
|
||||
_sensitive_params.add(query_param)
|
||||
|
@ -760,8 +760,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
|
||||
def test_mfa(self):
|
||||
self._test_unsupported_header('x-amz-mfa')
|
||||
|
||||
@mock.patch.object(registry, '_swift_admin_info', new_callable=dict)
|
||||
def test_server_side_encryption(self, mock_info):
|
||||
@mock.patch.object(registry, '_swift_admin_info', dict())
|
||||
def test_server_side_encryption(self):
|
||||
sse_header = 'x-amz-server-side-encryption'
|
||||
self._test_unsupported_header(sse_header, 'AES256')
|
||||
self._test_unsupported_header(sse_header, 'aws:kms')
|
||||
@ -868,6 +868,19 @@ class TestS3ApiMiddleware(S3ApiTestCase):
|
||||
self.assertEqual(elem.find('./Method').text, 'POST')
|
||||
self.assertEqual(elem.find('./ResourceType').text, 'ACL')
|
||||
|
||||
@mock.patch.object(registry, '_sensitive_headers', set())
|
||||
@mock.patch.object(registry, '_sensitive_params', set())
|
||||
def test_registered_sensitive_info(self):
|
||||
self.assertFalse(registry.get_sensitive_headers())
|
||||
self.assertFalse(registry.get_sensitive_params())
|
||||
filter_factory(self.conf)
|
||||
sensitive = registry.get_sensitive_headers()
|
||||
self.assertIn('authorization', sensitive)
|
||||
sensitive = registry.get_sensitive_params()
|
||||
self.assertIn('X-Amz-Signature', sensitive)
|
||||
self.assertIn('Signature', sensitive)
|
||||
|
||||
@mock.patch.object(registry, '_swift_info', dict())
|
||||
def test_registered_defaults(self):
|
||||
conf_from_file = {k: str(v) for k, v in self.conf.items()}
|
||||
filter_factory(conf_from_file)
|
||||
|
@ -23,8 +23,10 @@ from six.moves.urllib.parse import unquote
|
||||
|
||||
from swift.common.utils import get_logger, split_path, StatsdClient
|
||||
from swift.common.middleware import proxy_logging
|
||||
from swift.common.registry import register_sensitive_header, \
|
||||
register_sensitive_param, get_sensitive_headers
|
||||
from swift.common.swob import Request, Response
|
||||
from swift.common import constraints
|
||||
from swift.common import constraints, registry
|
||||
from swift.common.storage_policy import StoragePolicy
|
||||
from test.debug_logger import debug_logger
|
||||
from test.unit import patch_policies
|
||||
@ -712,6 +714,14 @@ class TestProxyLogging(unittest.TestCase):
|
||||
self.assertTrue(callable(factory))
|
||||
self.assertTrue(callable(factory(FakeApp())))
|
||||
|
||||
def test_sensitive_headers_registered(self):
|
||||
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||
self.assertNotIn('x-auth-token', get_sensitive_headers())
|
||||
self.assertNotIn('x-storage-token', get_sensitive_headers())
|
||||
proxy_logging.filter_factory({})(FakeApp())
|
||||
self.assertIn('x-auth-token', get_sensitive_headers())
|
||||
self.assertIn('x-storage-token', get_sensitive_headers())
|
||||
|
||||
def test_unread_body(self):
|
||||
app = proxy_logging.ProxyLoggingMiddleware(
|
||||
FakeApp(['some', 'stuff']), {})
|
||||
@ -921,89 +931,90 @@ class TestProxyLogging(unittest.TestCase):
|
||||
|
||||
def test_log_auth_token(self):
|
||||
auth_token = 'b05bf940-0464-4c0e-8c70-87717d2d73e8'
|
||||
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||
# Default - reveal_sensitive_prefix is 16
|
||||
# No x-auth-token header
|
||||
app = proxy_logging.filter_factory({})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
# Has x-auth-token header
|
||||
app = proxy_logging.filter_factory({})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], 'b05bf940-0464-4c...', log_parts)
|
||||
|
||||
# Default - reveal_sensitive_prefix is 16
|
||||
# No x-auth-token header
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
# Has x-auth-token header
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], 'b05bf940-0464-4c...')
|
||||
# Truncate to first 8 characters
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': '8'})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': '8'})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], 'b05bf940...')
|
||||
|
||||
# Truncate to first 8 characters
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': '8'})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': '8'})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], 'b05bf940...')
|
||||
# Token length and reveal_sensitive_prefix are same (no truncate)
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': str(len(auth_token))})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], auth_token)
|
||||
|
||||
# Token length and reveal_sensitive_prefix are same (no truncate)
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': str(len(auth_token))})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], auth_token)
|
||||
# No effective limit on auth token
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': constraints.MAX_HEADER_SIZE}
|
||||
)(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], auth_token)
|
||||
|
||||
# No effective limit on auth token
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': constraints.MAX_HEADER_SIZE})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], auth_token)
|
||||
# Don't log x-auth-token
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': '0'})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
app = proxy_logging.filter_factory(
|
||||
{'reveal_sensitive_prefix': '0'})(FakeApp())
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '...')
|
||||
|
||||
# Don't log x-auth-token
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': '0'})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '-')
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
||||
'reveal_sensitive_prefix': '0'})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||
resp = app(req.environ, start_response)
|
||||
resp_body = b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[9], '...')
|
||||
|
||||
# Avoids pyflakes error, "local variable 'resp_body' is assigned to
|
||||
# but never used
|
||||
self.assertTrue(resp_body is not None)
|
||||
# Avoids pyflakes error, "local variable 'resp_body' is assigned to
|
||||
# but never used
|
||||
self.assertTrue(resp_body is not None)
|
||||
|
||||
def test_ensure_fields(self):
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
||||
@ -1151,3 +1162,109 @@ class TestProxyLogging(unittest.TestCase):
|
||||
b''.join(resp)
|
||||
log_parts = self._log_parts(app)
|
||||
self.assertEqual(log_parts[20], '1')
|
||||
|
||||
def test_obscure_req(self):
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
||||
app.access_logger = debug_logger()
|
||||
|
||||
params = [('param_one',
|
||||
'some_long_string_that_might_need_to_be_obscured'),
|
||||
('param_two',
|
||||
"super_secure_param_that_needs_to_be_obscured")]
|
||||
headers = {'X-Auth-Token': 'this_is_my_auth_token',
|
||||
'X-Other-Header': 'another_header_that_we_may_obscure'}
|
||||
|
||||
req = Request.blank('a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers=headers)
|
||||
req.params = params
|
||||
|
||||
# if nothing is sensitive, nothing will be obscured
|
||||
with mock.patch.object(registry, '_sensitive_params', set()):
|
||||
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||
app.obscure_req(req)
|
||||
# show that nothing changed
|
||||
for header, expected_value in headers.items():
|
||||
self.assertEqual(req.headers[header], expected_value)
|
||||
|
||||
for param, expected_value in params:
|
||||
self.assertEqual(req.params[param], expected_value)
|
||||
|
||||
# If an obscured param or header doesn't exist in a req, that's fine
|
||||
with mock.patch.object(registry, '_sensitive_params', set()):
|
||||
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||
register_sensitive_header('X-Not-Exist')
|
||||
register_sensitive_param('non-existent-param')
|
||||
app.obscure_req(req)
|
||||
|
||||
# show that nothing changed
|
||||
for header, expected_value in headers.items():
|
||||
self.assertEqual(req.headers[header], expected_value)
|
||||
|
||||
for param, expected_value in params:
|
||||
self.assertEqual(req.params[param], expected_value)
|
||||
|
||||
def obscured_test(params, headers, params_to_add, headers_to_add,
|
||||
expected_params, expected_headers):
|
||||
with mock.patch.object(registry, '_sensitive_params', set()):
|
||||
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
||||
app.access_logger = debug_logger()
|
||||
req = Request.blank('a/c/o',
|
||||
environ={'REQUEST_METHOD': 'GET'},
|
||||
headers=dict(headers))
|
||||
req.params = params
|
||||
for param in params_to_add:
|
||||
register_sensitive_param(param)
|
||||
|
||||
for header in headers_to_add:
|
||||
register_sensitive_header(header)
|
||||
|
||||
app.obscure_req(req)
|
||||
for header, expected_value in expected_headers.items():
|
||||
self.assertEqual(req.headers[header], expected_value)
|
||||
|
||||
for param, expected_value in expected_params:
|
||||
self.assertEqual(req.params[param], expected_value)
|
||||
|
||||
# first just 1 param
|
||||
expected_params = list(params)
|
||||
expected_params[0] = ('param_one', 'some_long_string...')
|
||||
obscured_test(params, headers, ['param_one'], [], expected_params,
|
||||
headers)
|
||||
# case sensitive
|
||||
expected_params = list(params)
|
||||
obscured_test(params, headers, ['Param_one'], [], expected_params,
|
||||
headers)
|
||||
# Other param
|
||||
expected_params = list(params)
|
||||
expected_params[1] = ('param_two', 'super_secure_par...')
|
||||
obscured_test(params, headers, ['param_two'], [], expected_params,
|
||||
headers)
|
||||
# both
|
||||
expected_params[0] = ('param_one', 'some_long_string...')
|
||||
obscured_test(params, headers, ['param_two', 'param_one'], [],
|
||||
expected_params, headers)
|
||||
|
||||
# Now the headers
|
||||
# first just 1 header
|
||||
expected_headers = headers.copy()
|
||||
expected_headers["X-Auth-Token"] = 'this_is_my_auth_...'
|
||||
obscured_test(params, headers, [], ['X-Auth-Token'], params,
|
||||
expected_headers)
|
||||
# case insensitive
|
||||
obscured_test(params, headers, [], ['x-auth-token'], params,
|
||||
expected_headers)
|
||||
# Other headers
|
||||
expected_headers = headers.copy()
|
||||
expected_headers["X-Other-Header"] = 'another_header_t...'
|
||||
obscured_test(params, headers, [], ['X-Other-Header'], params,
|
||||
expected_headers)
|
||||
# both
|
||||
expected_headers["X-Auth-Token"] = 'this_is_my_auth_...'
|
||||
obscured_test(params, headers, [], ['X-Auth-Token', 'X-Other-Header'],
|
||||
params, expected_headers)
|
||||
|
||||
# all together
|
||||
obscured_test(params, headers, ['param_two', 'param_one'],
|
||||
['X-Auth-Token', 'X-Other-Header'],
|
||||
expected_params, expected_headers)
|
||||
|
@ -38,10 +38,11 @@ import six
|
||||
from six.moves.urllib.parse import quote
|
||||
from time import time, strftime, gmtime
|
||||
|
||||
from swift.common.middleware import tempauth, tempurl
|
||||
from swift.common.middleware import tempauth, tempurl, proxy_logging
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.swob import Request, Response
|
||||
from swift.common import utils, registry
|
||||
from test.debug_logger import debug_logger
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
@ -206,6 +207,31 @@ class TestTempURL(unittest.TestCase):
|
||||
for sig in (sig1, sig2):
|
||||
self.assert_valid_sig(expires, path, account_keys, sig, environ)
|
||||
|
||||
def test_signature_trim(self):
|
||||
# Insert proxy logging into the pipeline
|
||||
p_logging = proxy_logging.filter_factory({})(self.app)
|
||||
self.auth = tempauth.filter_factory({'reseller_prefix': ''})(
|
||||
p_logging)
|
||||
self.tempurl = tempurl.filter_factory({})(self.auth)
|
||||
|
||||
sig = 'valid_sigs_will_be_exactly_40_characters'
|
||||
expires = int(time() + 1000)
|
||||
p_logging.access_logger.logger = debug_logger('fake')
|
||||
resp = self._make_request(
|
||||
'/v1/a/c/o?temp_url_sig=%s&temp_url_expires=%d' % (sig, expires))
|
||||
|
||||
with mock.patch('swift.common.middleware.tempurl.TempURL._get_keys',
|
||||
return_value=[('key', tempurl.CONTAINER_SCOPE)]):
|
||||
with mock.patch(
|
||||
'swift.common.middleware.tempurl.TempURL._get_hmacs',
|
||||
return_value=[(sig, tempurl.CONTAINER_SCOPE)]):
|
||||
resp.get_response(self.tempurl)
|
||||
trimmed_sig_qs = '%s...' % sig[:16]
|
||||
info_lines = p_logging.access_logger. \
|
||||
logger.get_lines_for_level('info')
|
||||
|
||||
self.assertIn(trimmed_sig_qs, info_lines[0])
|
||||
|
||||
@mock.patch('swift.common.middleware.tempurl.time', return_value=0)
|
||||
def test_get_valid_with_filename(self, mock_time):
|
||||
method = 'GET'
|
||||
|
@ -15,14 +15,19 @@
|
||||
|
||||
from swift.common import registry, utils
|
||||
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
|
||||
class TestSwiftInfo(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
registry._swift_info = {}
|
||||
registry._swift_admin_info = {}
|
||||
def setUp(self):
|
||||
patcher = mock.patch.object(registry, '_swift_info', dict())
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch.object(registry, '_swift_admin_info', dict())
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_register_swift_info(self):
|
||||
registry.register_swift_info(foo='bar')
|
||||
@ -211,3 +216,81 @@ class TestSwiftInfo(unittest.TestCase):
|
||||
self.assertEqual(registry._swift_info['swift']['foo'], 'bar')
|
||||
self.assertEqual(registry.get_swift_info(admin=True),
|
||||
utils.get_swift_info(admin=True))
|
||||
|
||||
|
||||
class TestSensitiveRegistry(unittest.TestCase):
|
||||
def setUp(self):
|
||||
patcher = mock.patch.object(registry, '_sensitive_headers', set())
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch.object(registry, '_sensitive_params', set())
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_register_sensitive_header(self):
|
||||
self.assertFalse(registry._sensitive_headers)
|
||||
|
||||
registry.register_sensitive_header('Some-Header')
|
||||
expected_headers = {'Some-Header'}
|
||||
self.assertEqual(expected_headers, registry._sensitive_headers)
|
||||
|
||||
expected_headers.add("New-Header")
|
||||
registry.register_sensitive_header("New-Header")
|
||||
self.assertEqual(expected_headers, registry._sensitive_headers)
|
||||
|
||||
for header_not_str in (1, None, 1.1):
|
||||
with self.assertRaises(TypeError):
|
||||
registry.register_sensitive_header(header_not_str)
|
||||
self.assertEqual(expected_headers, registry._sensitive_headers)
|
||||
|
||||
with self.assertRaises(UnicodeError):
|
||||
registry.register_sensitive_header('\xe2\x98\x83')
|
||||
self.assertEqual(expected_headers, registry._sensitive_headers)
|
||||
|
||||
def test_register_sensitive_param(self):
|
||||
self.assertFalse(registry._sensitive_params)
|
||||
|
||||
registry.register_sensitive_param('some_param')
|
||||
expected_params = {'some_param'}
|
||||
self.assertEqual(expected_params, registry._sensitive_params)
|
||||
|
||||
expected_params.add("another")
|
||||
registry.register_sensitive_param("another")
|
||||
self.assertEqual(expected_params, registry._sensitive_params)
|
||||
|
||||
for param_not_str in (1, None, 1.1):
|
||||
with self.assertRaises(TypeError):
|
||||
registry.register_sensitive_param(param_not_str)
|
||||
self.assertEqual(expected_params, registry._sensitive_params)
|
||||
|
||||
with self.assertRaises(UnicodeError):
|
||||
registry.register_sensitive_param('\xe2\x98\x83')
|
||||
self.assertEqual(expected_params, registry._sensitive_params)
|
||||
|
||||
def test_get_sensitive_headers(self):
|
||||
self.assertFalse(registry.get_sensitive_headers())
|
||||
|
||||
registry.register_sensitive_header('Header1')
|
||||
self.assertEqual(registry.get_sensitive_headers(), {'Header1'})
|
||||
self.assertEqual(registry.get_sensitive_headers(),
|
||||
registry._sensitive_headers)
|
||||
|
||||
registry.register_sensitive_header('Header2')
|
||||
self.assertEqual(registry.get_sensitive_headers(),
|
||||
{'Header1', 'Header2'})
|
||||
self.assertEqual(registry.get_sensitive_headers(),
|
||||
registry._sensitive_headers)
|
||||
|
||||
def test_get_sensitive_params(self):
|
||||
self.assertFalse(registry.get_sensitive_params())
|
||||
|
||||
registry.register_sensitive_param('Param1')
|
||||
self.assertEqual(registry.get_sensitive_params(), {'Param1'})
|
||||
self.assertEqual(registry.get_sensitive_params(),
|
||||
registry._sensitive_params)
|
||||
|
||||
registry.register_sensitive_param('param')
|
||||
self.assertEqual(registry.get_sensitive_params(),
|
||||
{'Param1', 'param'})
|
||||
self.assertEqual(registry.get_sensitive_params(),
|
||||
registry._sensitive_params)
|
||||
|
Loading…
Reference in New Issue
Block a user