From eb99e8f84cf78c20e03f4a4fd92e243a22171b23 Mon Sep 17 00:00:00 2001 From: Donagh McCabe Date: Tue, 23 Jul 2013 15:10:09 +0100 Subject: [PATCH] Obscure the X-Auth-Token in proxy log The X-Auth-Token is sensitive data. If revealed to an unauthozied person, they can now make requests against an account until the token expires. This implementation maintains current behavior (i.e, the token is logged). Implementers can choose to set reveal_sensitive_prefix to (e.g.) 12 so only first 12 characters of the token are logged. Or, set to 0 to replace the token with "...". DocImpact Part of bug #1004114 Change-Id: Iecefa843d8f9ef59b9dcf0860e7a4d0e186a6cb5 --- etc/proxy-server.conf-sample | 12 ++++ swift/common/middleware/proxy_logging.py | 12 +++- .../common/middleware/test_proxy_logging.py | 71 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 2a974ecf3b..5d598a5921 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -425,6 +425,18 @@ use = egg:swift#proxy_logging # access_log_statsd_metric_prefix = # access_log_headers = false # +# 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. +# reveal_sensitive_prefix = 8192 +# # What HTTP methods are allowed for StatsD logging (comma-sep); request methods # not in this list will have "BAD_METHOD" for the portion of the metric. # log_statsd_valid_http_methods = GET,HEAD,POST,PUT,DELETE,COPY,OPTIONS diff --git a/swift/common/middleware/proxy_logging.py b/swift/common/middleware/proxy_logging.py index 081188a98b..b40ef36ac5 100644 --- a/swift/common/middleware/proxy_logging.py +++ b/swift/common/middleware/proxy_logging.py @@ -77,6 +77,7 @@ from swift.common.swob import Request from swift.common.utils import (get_logger, get_remote_client, get_valid_utf8_str, config_true_value, InputProxy) +from swift.common.constraints import MAX_HEADER_SIZE QUOTE_SAFE = '/:' @@ -112,6 +113,8 @@ class ProxyLoggingMiddleware(object): self.access_logger = get_logger(access_log_conf, log_route='proxy-access') self.access_logger.set_statsd_prefix('proxy-server') + self.reveal_sensitive_prefix = int(conf.get('reveal_sensitive_prefix', + MAX_HEADER_SIZE)) def method_from_req(self, req): return req.environ.get('swift.orig_req_method', req.method) @@ -122,6 +125,13 @@ class ProxyLoggingMiddleware(object): def mark_req_logged(self, req): req.environ['swift.proxy_access_log_made'] = True + def obscure_sensitive(self, value): + if not value: + return '-' + if len(value) > self.reveal_sensitive_prefix: + return value[:self.reveal_sensitive_prefix] + '...' + return value + def log_request(self, req, status_int, bytes_received, bytes_sent, request_time): """ @@ -156,7 +166,7 @@ class ProxyLoggingMiddleware(object): status_int, req.referer, req.user_agent, - req.headers.get('x-auth-token'), + self.obscure_sensitive(req.headers.get('x-auth-token')), bytes_received, bytes_sent, req.headers.get('etag', None), diff --git a/test/unit/common/middleware/test_proxy_logging.py b/test/unit/common/middleware/test_proxy_logging.py index 4a08b68b73..0986566d18 100644 --- a/test/unit/common/middleware/test_proxy_logging.py +++ b/test/unit/common/middleware/test_proxy_logging.py @@ -571,6 +571,77 @@ class TestProxyLogging(unittest.TestCase): log_parts = self._log_parts(app) self.assertEquals(log_parts[17], 'one%2Cand%20two') + def test_log_auth_token(self): + auth_token = 'b05bf940-0464-4c0e-8c70-87717d2d73e8' + + # Default - no reveal_sensitive_prefix in config + # No x-auth-token header + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], '-') + # Has x-auth-token header + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', + 'HTTP_X_AUTH_TOKEN': auth_token}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], auth_token) + + # Truncate to first 8 characters + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), { + 'reveal_sensitive_prefix': '8'}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], '-') + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), { + 'reveal_sensitive_prefix': '8'}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', + 'HTTP_X_AUTH_TOKEN': auth_token}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], 'b05bf940...') + + # 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 = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', + 'HTTP_X_AUTH_TOKEN': auth_token}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], auth_token) + + # Don't log x-auth-token + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), { + 'reveal_sensitive_prefix': '0'}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], '-') + app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), { + 'reveal_sensitive_prefix': '0'}) + app.access_logger = FakeLogger() + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', + 'HTTP_X_AUTH_TOKEN': auth_token}) + resp = app(req.environ, start_response) + resp_body = ''.join(resp) + log_parts = self._log_parts(app) + self.assertEquals(log_parts[9], '...') + if __name__ == '__main__': unittest.main()