Merge "Trim sensitive information in the logs (CVE-2017-8761)"
This commit is contained in:
commit
dc061dd95e
@ -178,13 +178,25 @@ Middleware may advertize its availability and capabilities via Swift's
|
|||||||
:ref:`discoverability` support by using
|
:ref:`discoverability` support by using
|
||||||
:func:`.register_swift_info`::
|
: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):
|
def webhook_factory(global_conf, **local_conf):
|
||||||
register_swift_info('webhook')
|
register_swift_info('webhook')
|
||||||
def webhook_filter(app):
|
def webhook_filter(app):
|
||||||
return WebhookMiddleware(app)
|
return WebhookMiddleware(app)
|
||||||
return webhook_filter
|
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
|
Swift Metadata
|
||||||
|
@ -86,6 +86,9 @@ bind_port = 8080
|
|||||||
# cors_expose_headers =
|
# cors_expose_headers =
|
||||||
#
|
#
|
||||||
# client_timeout = 60.0
|
# client_timeout = 60.0
|
||||||
|
#
|
||||||
|
# Note: enabling evenlet_debug might reveal sensitive information, for example
|
||||||
|
# signatures for temp urls
|
||||||
# eventlet_debug = false
|
# eventlet_debug = false
|
||||||
#
|
#
|
||||||
# You can set scheduling priority of processes. Niceness values range from -20
|
# 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
|
# list like this: access_log_headers_only = Host, X-Object-Meta-Mtime
|
||||||
# access_log_headers_only =
|
# access_log_headers_only =
|
||||||
#
|
#
|
||||||
# By default, the X-Auth-Token is logged. To obscure the value,
|
# The default log format includes several sensitive values in logs:
|
||||||
# set reveal_sensitive_prefix to the number of characters to log.
|
# * X-Auth-Token header
|
||||||
# For example, if set to 12, only the first 12 characters of the
|
# * temp_url_sig query parameter
|
||||||
# token appear in the log. An unauthorized access of the log file
|
# * Authorization header
|
||||||
# won't allow unauthorized usage of the token. However, the first
|
# * X-Amz-Signature query parameter
|
||||||
# 12 or so characters is unique enough that you can trace/debug
|
# To prevent an unauthorized access of the log file leading to an unauthorized
|
||||||
# token usage. Set to 0 to suppress the token completely (replaced
|
# access of cluster data, only a portion of these values are written, with the
|
||||||
# by '...' in the log).
|
# remainder replaced by '...' in the log. Set reveal_sensitive_prefix to the
|
||||||
# Note: reveal_sensitive_prefix will not affect the value
|
# number of characters to log. Set to 0 to suppress the values entirely; set
|
||||||
# logged with access_log_headers=True.
|
# 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
|
# reveal_sensitive_prefix = 16
|
||||||
#
|
#
|
||||||
# What HTTP methods are allowed for StatsD logging (comma-sep); request methods
|
# 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)
|
LogStringFormatter)
|
||||||
|
|
||||||
from swift.common.storage_policy import POLICIES
|
from swift.common.storage_policy import POLICIES
|
||||||
|
from swift.common.registry import get_sensitive_headers, \
|
||||||
|
get_sensitive_params, register_sensitive_header
|
||||||
|
|
||||||
|
|
||||||
class ProxyLoggingMiddleware(object):
|
class ProxyLoggingMiddleware(object):
|
||||||
@ -200,6 +202,24 @@ class ProxyLoggingMiddleware(object):
|
|||||||
return value[:self.reveal_sensitive_prefix] + '...'
|
return value[:self.reveal_sensitive_prefix] + '...'
|
||||||
return value
|
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,
|
def log_request(self, req, status_int, bytes_received, bytes_sent,
|
||||||
start_time, end_time, resp_headers=None, ttfb=0,
|
start_time, end_time, resp_headers=None, ttfb=0,
|
||||||
wire_status_int=None):
|
wire_status_int=None):
|
||||||
@ -215,6 +235,7 @@ class ProxyLoggingMiddleware(object):
|
|||||||
:param resp_headers: dict of the response headers
|
:param resp_headers: dict of the response headers
|
||||||
:param wire_status_int: the on the wire status int
|
:param wire_status_int: the on the wire status int
|
||||||
"""
|
"""
|
||||||
|
self.obscure_req(req)
|
||||||
resp_headers = resp_headers or {}
|
resp_headers = resp_headers or {}
|
||||||
logged_headers = None
|
logged_headers = None
|
||||||
if self.log_hdrs:
|
if self.log_hdrs:
|
||||||
@ -270,8 +291,7 @@ class ProxyLoggingMiddleware(object):
|
|||||||
req.environ.get('SERVER_PROTOCOL'),
|
req.environ.get('SERVER_PROTOCOL'),
|
||||||
'status_int': status_int,
|
'status_int': status_int,
|
||||||
'auth_token':
|
'auth_token':
|
||||||
self.obscure_sensitive(
|
req.headers.get('x-auth-token'),
|
||||||
req.headers.get('x-auth-token')),
|
|
||||||
'bytes_recvd': bytes_received,
|
'bytes_recvd': bytes_received,
|
||||||
'bytes_sent': bytes_sent,
|
'bytes_sent': bytes_sent,
|
||||||
'transaction_id': req.environ.get('swift.trans_id'),
|
'transaction_id': req.environ.get('swift.trans_id'),
|
||||||
@ -445,6 +465,12 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
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):
|
def proxy_logger(app):
|
||||||
return ProxyLoggingMiddleware(app, conf)
|
return ProxyLoggingMiddleware(app, conf)
|
||||||
return proxy_logger
|
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
|
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.utils import Config
|
||||||
from swift.common.middleware.s3api.acl_handlers import get_acl_handler
|
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):
|
class ListingEtagMiddleware(object):
|
||||||
@ -464,6 +465,10 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
s3_acl=config_true_value(conf.get('s3_acl', False)),
|
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):
|
def s3api_filter(app):
|
||||||
return S3ApiMiddleware(ListingEtagMiddleware(app), conf)
|
return S3ApiMiddleware(ListingEtagMiddleware(app), conf)
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ from swift.common.swob import header_to_environ_key, HTTPUnauthorized, \
|
|||||||
HTTPBadRequest, wsgi_to_str
|
HTTPBadRequest, wsgi_to_str
|
||||||
from swift.common.utils import split_path, get_valid_utf8_str, \
|
from swift.common.utils import split_path, get_valid_utf8_str, \
|
||||||
get_hmac, streq_const_time, quote, get_logger, strict_b64decode
|
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'
|
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)
|
register_swift_info('tempurl', **info_conf)
|
||||||
conf.update(info_conf)
|
conf.update(info_conf)
|
||||||
|
|
||||||
|
register_sensitive_param('temp_url_sig')
|
||||||
|
|
||||||
return lambda app: TempURL(app, conf)
|
return lambda app: TempURL(app, conf)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# Used by get_swift_info and register_swift_info to store information about
|
# Used by get_swift_info and register_swift_info to store information about
|
||||||
# the swift cluster.
|
# the swift cluster.
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
import six
|
||||||
|
|
||||||
_swift_info = {}
|
_swift_info = {}
|
||||||
_swift_admin_info = {}
|
_swift_admin_info = {}
|
||||||
@ -84,3 +85,35 @@ def register_swift_info(name='swift', admin=False, **kwargs):
|
|||||||
if "." in key:
|
if "." in key:
|
||||||
raise ValueError('Cannot use "." in a swift_info key: %s' % key)
|
raise ValueError('Cannot use "." in a swift_info key: %s' % key)
|
||||||
dict_to_use[name][key] = val
|
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):
|
def test_mfa(self):
|
||||||
self._test_unsupported_header('x-amz-mfa')
|
self._test_unsupported_header('x-amz-mfa')
|
||||||
|
|
||||||
@mock.patch.object(registry, '_swift_admin_info', new_callable=dict)
|
@mock.patch.object(registry, '_swift_admin_info', dict())
|
||||||
def test_server_side_encryption(self, mock_info):
|
def test_server_side_encryption(self):
|
||||||
sse_header = 'x-amz-server-side-encryption'
|
sse_header = 'x-amz-server-side-encryption'
|
||||||
self._test_unsupported_header(sse_header, 'AES256')
|
self._test_unsupported_header(sse_header, 'AES256')
|
||||||
self._test_unsupported_header(sse_header, 'aws:kms')
|
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('./Method').text, 'POST')
|
||||||
self.assertEqual(elem.find('./ResourceType').text, 'ACL')
|
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):
|
def test_registered_defaults(self):
|
||||||
conf_from_file = {k: str(v) for k, v in self.conf.items()}
|
conf_from_file = {k: str(v) for k, v in self.conf.items()}
|
||||||
filter_factory(conf_from_file)
|
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.utils import get_logger, split_path, StatsdClient
|
||||||
from swift.common.middleware import proxy_logging
|
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.swob import Request, Response
|
||||||
from swift.common import constraints
|
from swift.common import constraints, registry
|
||||||
from swift.common.storage_policy import StoragePolicy
|
from swift.common.storage_policy import StoragePolicy
|
||||||
from test.debug_logger import debug_logger
|
from test.debug_logger import debug_logger
|
||||||
from test.unit import patch_policies
|
from test.unit import patch_policies
|
||||||
@ -712,6 +714,14 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
self.assertTrue(callable(factory))
|
self.assertTrue(callable(factory))
|
||||||
self.assertTrue(callable(factory(FakeApp())))
|
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):
|
def test_unread_body(self):
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(
|
app = proxy_logging.ProxyLoggingMiddleware(
|
||||||
FakeApp(['some', 'stuff']), {})
|
FakeApp(['some', 'stuff']), {})
|
||||||
@ -921,10 +931,10 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
|
|
||||||
def test_log_auth_token(self):
|
def test_log_auth_token(self):
|
||||||
auth_token = 'b05bf940-0464-4c0e-8c70-87717d2d73e8'
|
auth_token = 'b05bf940-0464-4c0e-8c70-87717d2d73e8'
|
||||||
|
with mock.patch.object(registry, '_sensitive_headers', set()):
|
||||||
# Default - reveal_sensitive_prefix is 16
|
# Default - reveal_sensitive_prefix is 16
|
||||||
# No x-auth-token header
|
# No x-auth-token header
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
app = proxy_logging.filter_factory({})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = app(req.environ, start_response)
|
resp = app(req.environ, start_response)
|
||||||
@ -932,26 +942,26 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
log_parts = self._log_parts(app)
|
log_parts = self._log_parts(app)
|
||||||
self.assertEqual(log_parts[9], '-')
|
self.assertEqual(log_parts[9], '-')
|
||||||
# Has x-auth-token header
|
# Has x-auth-token header
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
|
app = proxy_logging.filter_factory({})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||||
resp = app(req.environ, start_response)
|
resp = app(req.environ, start_response)
|
||||||
resp_body = b''.join(resp)
|
resp_body = b''.join(resp)
|
||||||
log_parts = self._log_parts(app)
|
log_parts = self._log_parts(app)
|
||||||
self.assertEqual(log_parts[9], 'b05bf940-0464-4c...')
|
self.assertEqual(log_parts[9], 'b05bf940-0464-4c...', log_parts)
|
||||||
|
|
||||||
# Truncate to first 8 characters
|
# Truncate to first 8 characters
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': '8'})
|
{'reveal_sensitive_prefix': '8'})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = app(req.environ, start_response)
|
resp = app(req.environ, start_response)
|
||||||
resp_body = b''.join(resp)
|
resp_body = b''.join(resp)
|
||||||
log_parts = self._log_parts(app)
|
log_parts = self._log_parts(app)
|
||||||
self.assertEqual(log_parts[9], '-')
|
self.assertEqual(log_parts[9], '-')
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': '8'})
|
{'reveal_sensitive_prefix': '8'})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||||
@ -961,8 +971,8 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
self.assertEqual(log_parts[9], 'b05bf940...')
|
self.assertEqual(log_parts[9], 'b05bf940...')
|
||||||
|
|
||||||
# Token length and reveal_sensitive_prefix are same (no truncate)
|
# Token length and reveal_sensitive_prefix are same (no truncate)
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': str(len(auth_token))})
|
{'reveal_sensitive_prefix': str(len(auth_token))})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||||
@ -972,8 +982,9 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
self.assertEqual(log_parts[9], auth_token)
|
self.assertEqual(log_parts[9], auth_token)
|
||||||
|
|
||||||
# No effective limit on auth token
|
# No effective limit on auth token
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': constraints.MAX_HEADER_SIZE})
|
{'reveal_sensitive_prefix': constraints.MAX_HEADER_SIZE}
|
||||||
|
)(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||||
@ -983,16 +994,16 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
self.assertEqual(log_parts[9], auth_token)
|
self.assertEqual(log_parts[9], auth_token)
|
||||||
|
|
||||||
# Don't log x-auth-token
|
# Don't log x-auth-token
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': '0'})
|
{'reveal_sensitive_prefix': '0'})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = app(req.environ, start_response)
|
resp = app(req.environ, start_response)
|
||||||
resp_body = b''.join(resp)
|
resp_body = b''.join(resp)
|
||||||
log_parts = self._log_parts(app)
|
log_parts = self._log_parts(app)
|
||||||
self.assertEqual(log_parts[9], '-')
|
self.assertEqual(log_parts[9], '-')
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {
|
app = proxy_logging.filter_factory(
|
||||||
'reveal_sensitive_prefix': '0'})
|
{'reveal_sensitive_prefix': '0'})(FakeApp())
|
||||||
app.access_logger = debug_logger()
|
app.access_logger = debug_logger()
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET',
|
||||||
'HTTP_X_AUTH_TOKEN': auth_token})
|
'HTTP_X_AUTH_TOKEN': auth_token})
|
||||||
@ -1151,3 +1162,109 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
b''.join(resp)
|
b''.join(resp)
|
||||||
log_parts = self._log_parts(app)
|
log_parts = self._log_parts(app)
|
||||||
self.assertEqual(log_parts[20], '1')
|
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 six.moves.urllib.parse import quote
|
||||||
from time import time, strftime, gmtime
|
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.header_key_dict import HeaderKeyDict
|
||||||
from swift.common.swob import Request, Response
|
from swift.common.swob import Request, Response
|
||||||
from swift.common import utils, registry
|
from swift.common import utils, registry
|
||||||
|
from test.debug_logger import debug_logger
|
||||||
|
|
||||||
|
|
||||||
class FakeApp(object):
|
class FakeApp(object):
|
||||||
@ -206,6 +207,31 @@ class TestTempURL(unittest.TestCase):
|
|||||||
for sig in (sig1, sig2):
|
for sig in (sig1, sig2):
|
||||||
self.assert_valid_sig(expires, path, account_keys, sig, environ)
|
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)
|
@mock.patch('swift.common.middleware.tempurl.time', return_value=0)
|
||||||
def test_get_valid_with_filename(self, mock_time):
|
def test_get_valid_with_filename(self, mock_time):
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
@ -15,14 +15,19 @@
|
|||||||
|
|
||||||
from swift.common import registry, utils
|
from swift.common import registry, utils
|
||||||
|
|
||||||
|
import mock
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestSwiftInfo(unittest.TestCase):
|
class TestSwiftInfo(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def setUp(self):
|
||||||
registry._swift_info = {}
|
patcher = mock.patch.object(registry, '_swift_info', dict())
|
||||||
registry._swift_admin_info = {}
|
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):
|
def test_register_swift_info(self):
|
||||||
registry.register_swift_info(foo='bar')
|
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._swift_info['swift']['foo'], 'bar')
|
||||||
self.assertEqual(registry.get_swift_info(admin=True),
|
self.assertEqual(registry.get_swift_info(admin=True),
|
||||||
utils.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