Merge "s3api: include error response reason in log_info"
This commit is contained in:
9 changed files with 179 additions and 30 deletions
@@ -27,11 +27,13 @@ from paste.deploy import loadwsgi
from six.moves.urllib.parse import unquote, quote
import swift.common.middleware.s3api
from swift.common.middleware.proxy_logging import ProxyLoggingMiddleware
from swift.common.middleware.s3api.s3response import ErrorResponse, \
AccessDenied
from swift.common.middleware.s3api.utils import Config
from swift.common.middleware.keystoneauth import KeystoneAuth
from swift.common import swob, registry
from swift.common.request_helpers import get_log_info
from swift.common.swob import Request
from swift.common.utils import md5, get_logger, UTC
@@ -255,6 +257,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_header_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_header_auth',
get_log_info(req.environ))
def test_bad_method(self):
req = Request.blank('/',
@@ -266,6 +270,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
def test_bad_method_but_method_exists_in_controller(self):
req = Request.blank(
@@ -278,6 +284,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
def test_path_info_encode(self):
bucket_name = 'b%75cket'
@@ -412,6 +420,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.expired': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.expired',
get_log_info(req.environ))
def test_signed_urls(self):
# Set expire to last 32b timestamp value
@@ -461,6 +471,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
def test_signed_urls_no_sign(self):
expire = '2147483647' # 19 Jan 2038 03:14:07
@@ -475,6 +487,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_signed_urls_no_access(self):
expire = '2147483647' # 19 Jan 2038 03:14:07
@@ -488,6 +502,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_signed_urls_v4(self):
req = Request.blank(
@@ -537,6 +553,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.AuthorizationQueryParametersError': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AuthorizationQueryParametersError',
get_log_info(req.environ))
dt = self.get_v4_amz_date_header().split('T', 1)[0]
test('test:tester/not-a-date/us-east-1/s3/aws4_request',
@@ -568,6 +586,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
def test_signed_urls_v4_invalid_algorithm(self):
req = Request.blank(
@@ -586,6 +606,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def test_signed_urls_v4_missing_signed_headers(self):
req = Request.blank(
@@ -604,6 +626,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.AuthorizationHeaderMalformed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AuthorizationHeaderMalformed',
get_log_info(req.environ))
def test_signed_urls_v4_invalid_credentials(self):
req = Request.blank('/bucket/object'
@@ -621,6 +645,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_credential': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_credential',
get_log_info(req.environ))
def test_signed_urls_v4_missing_signature(self):
req = Request.blank(
@@ -638,6 +664,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_bucket_virtual_hosted_style(self):
req = Request.blank('/',
@@ -737,6 +765,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidURI': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidURI', get_log_info(req.environ))
def test_object_create_bad_md5_unreadable(self):
req = Request.blank('/bucket/object',
@@ -749,6 +778,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_too_short(self):
too_short_digest = md5(b'hey', usedforsecurity=False).digest()[:-1]
@@ -766,6 +796,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_bad_padding(self):
too_short_digest = md5(b'hey', usedforsecurity=False).digest()
@@ -783,6 +814,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_too_long(self):
too_long_digest = md5(
@@ -801,6 +833,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_invalid_metadata_directive(self):
req = Request.blank('/',
@@ -814,6 +847,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def test_invalid_storage_class(self):
req = Request.blank('/',
@@ -826,6 +861,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidStorageClass': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidStorageClass',
get_log_info(req.environ))
def test_invalid_ssc(self):
req = Request.blank('/',
@@ -838,6 +875,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def _test_unsupported_header(self, header, value=None):
if value is None:
@@ -853,6 +892,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_mfa(self):
self._test_unsupported_header('x-amz-mfa')
@@ -915,6 +956,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_notification(self):
self._test_unsupported_resource('notification')
@@ -955,6 +998,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
req = Request.blank('/bucket?tagging',
environ={'REQUEST_METHOD': 'DELETE'},
@@ -966,6 +1011,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_restore(self):
self._test_unsupported_resource('restore')
@@ -983,6 +1030,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
@mock.patch.object(registry, '_sensitive_headers', set())
@mock.patch.object(registry, '_sensitive_params', set())
@@ -1118,6 +1167,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
def test_signature_v4_no_payload(self):
environ = {
@@ -1140,6 +1191,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'400.InvalidRequest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidRequest',
get_log_info(req.environ))
def test_signature_v4_bad_authorization_string(self):
def test(auth_str, error, msg, metric, extra=b''):
@@ -1160,6 +1213,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{metric: 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:%s' % metric[4:],
get_log_info(req.environ))
auth_str = ('AWS4-HMAC-SHA256 '
'SignedHeaders=host;x-amz-date,'
@@ -1424,6 +1479,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
# But if we are missing Signature in query param
req = Request.blank(
@@ -1440,6 +1497,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
def test_s3api_with_only_s3_token(self):
self.swift = FakeSwift()
@@ -1555,6 +1614,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual(
{'403.SignatureDoesNotMatch': 1},
statsd_client.get_increment_counts())
self.assertEqual('s3:err:SignatureDoesNotMatch',
get_log_info(req.environ))
def test_s3api_with_only_s3_token_in_s3acl(self):
self.swift = FakeSwift()
@@ -1600,39 +1661,46 @@ class TestS3ApiMiddleware(S3ApiTestCase):
'AWS test:tester:hmac'},
headers={'Date': self.get_date_header(skew=skew)})
self.s3api.logger.logger.clear()
return self.call_s3api(req)
status, headers, body = self.call_s3api(req)
return req, status, headers, body
status, _, body = do_test(800)
req, status, _, body = do_test(800)
self.assertEqual('200 OK', status)
self.assertFalse(
self.s3api.logger.logger.statsd_client.get_increment_counts())
status, _, body = do_test(-800)
req, status, _, body = do_test(-800)
self.assertEqual('200 OK', status)
self.assertFalse(
self.s3api.logger.logger.statsd_client.get_increment_counts())
status, _, body = do_test(1000)
req, status, _, body = do_test(1000)
self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual(
{'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
status, _, body = do_test(-1000)
req, status, _, body = do_test(-1000)
self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual(
{'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
self.s3api.conf.allowable_clock_skew = 100
status, _, body = do_test(800)
req, status, _, body = do_test(800)
self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual(
{'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
def test_s3api_error_metric(self):
class KaboomResponse(ErrorResponse):
@@ -1650,22 +1718,55 @@ class TestS3ApiMiddleware(S3ApiTestCase):
with mock.patch.object(
self.s3api, 'handle_request', side_effect=err_response):
self.call_s3api(req)
return req
do_test(ErrorResponse(status=403, msg='not good', reason='bad'))
req = do_test(ErrorResponse(status=403, msg='not good', reason='bad'))
self.assertEqual(
{'403.ErrorResponse.bad': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:ErrorResponse.bad',
get_log_info(req.environ))
do_test(AccessDenied(msg='no entry', reason='invalid_date'))
req = do_test(AccessDenied(msg='no entry', reason='invalid_date'))
self.assertEqual(
{'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
# check whitespace replaced with underscore
do_test(KaboomResponse(status=400, msg='boom', reason='boom boom'))
req = do_test(KaboomResponse(status=400, msg='boom',
reason='boom boom'))
self.assertEqual(
{'400.ka_boom.boom_boom': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:ka_boom.boom_boom',
get_log_info(req.environ))
def test_error_response_reason_logging(self):
# verify that proxy logging gets error reason in log_info
environ = {'REQUEST_METHOD': 'HEAD'}
headers = {
'Authorization':
'AWS4-HMAC-SHA256 SignedHeaders=host;x-amz-date,Signature=X',
'X-Amz-Date': self.get_v4_amz_date_header(),
# invalid sha
'X-Amz-Content-SHA256': '0123456789'}
req = Request.blank('/bucket/object', environ=environ,
headers=headers)
req.content_type = 'text/plain'
log_conf = {'log_msg_template': '{method}{path}{log_info}'}
app = ProxyLoggingMiddleware(self.s3api, log_conf, self.logger)
status, headers, body = self.call_app(req, app=app)
self.assertEqual(
{'403.AccessDenied.invalid_credential': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_credential',
get_log_info(req.environ))
self.assertEqual(
['HEAD /bucket/object s3:err:AccessDenied.invalid_credential'],
self.logger.get_lines_for_level('info'))
if __name__ == '__main__':
@@ -17,7 +17,7 @@ import unittest
from swift.common.swob import Response
from swift.common.utils import HeaderKeyDict
from swift.common.middleware.s3api.s3response import S3Response
from swift.common.middleware.s3api.s3response import S3Response, ErrorResponse
from swift.common.middleware.s3api.utils import sysmeta_prefix
@@ -103,5 +103,27 @@ class TestResponse(unittest.TestCase):
self.assertEqual(expected_headers, s3resp.sysmeta_headers)
class DummyErrorResponse(ErrorResponse):
_status = "418 I'm a teapot"
class TestErrorResponse(unittest.TestCase):
def test_error_response(self):
resp = DummyErrorResponse(msg='my-msg', reason='my reason')
self.assertEqual("418 I'm a teapot", str(resp))
self.assertEqual("418 I'm a teapot", resp.status)
self.assertEqual(418, resp.status_int)
self.assertEqual('my reason', resp.reason)
self.assertEqual('DummyErrorResponse.my_reason', resp.summary)
self.assertEqual('418.DummyErrorResponse.my_reason', resp.metric_name)
self.assertEqual(
b"<?xml version='1.0' encoding='UTF-8'?>\n"
b"<Error>"
b"<Code>DummyErrorResponse</Code>"
b"<Message>my-msg</Message>"
b"</Error>",
resp.body)
if __name__ == '__main__':
unittest.main()
@@ -32,6 +32,22 @@ server_types = ['account', 'container', 'object']
class TestRequestHelpers(unittest.TestCase):
def test_append_log_info(self):
req = Request.blank('/v/a/c/o')
self.assertNotIn('swift.log_info', req.environ)
rh.append_log_info(req.environ, 'msg1')
self.assertEqual(['msg1'], req.environ.get('swift.log_info'))
rh.append_log_info(req.environ, 'msg2')
self.assertEqual(['msg1', 'msg2'], req.environ.get('swift.log_info'))
def test_get_log_info(self):
req = Request.blank('/v/a/c/o')
self.assertEqual('', rh.get_log_info(req.environ))
req.environ['swift.log_info'] = ['msg1']
self.assertEqual('msg1', rh.get_log_info(req.environ))
rh.append_log_info(req.environ, 'msg2')
self.assertEqual('msg1,msg2', rh.get_log_info(req.environ))
def test_constrain_req_limit(self):
req = Request.blank('')
self.assertEqual(10, rh.constrain_req_limit(req, 10))
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.