Pass empty etag override values through encrypter
Currently if the encrypter middleware receives an x-object-sysmeta-container-update-override-etag value of '' in either a header or footer, it may or not pass the value on to the proxy app: if there is no object body then the '' value is passed on, but if there is a body then the override value is replaced with the encrypted etag of the body. However strange it may seem for an override etag value to be the empty string, the encrypter should at least behave consistently, so this patch makes it always respect the empty string override value and pass it through, unencrypted (i.e. with no crypto_meta appended). It is assumed that the middleware that set the override value knows what it is doing. Note that the override etag value is never used to verify the object content, it is only used to forward a value to the container server for inclusion with the object's listing entry. Change-Id: Ie1d073be9ffb00d0f360298c3040eb2f15de0926
This commit is contained in:
@@ -146,12 +146,16 @@ class EncInputWrapper(object):
|
||||
# * override in the footer, otherwise
|
||||
# * override in the header, and finally
|
||||
# * MD5 of the plaintext received
|
||||
# This may be None if no override was set and no data was read
|
||||
# This may be None if no override was set and no data was read. An
|
||||
# override value of '' will be passed on.
|
||||
container_listing_etag = footers.get(
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag',
|
||||
container_listing_etag_header) or plaintext_etag
|
||||
container_listing_etag_header)
|
||||
|
||||
if (container_listing_etag is not None and
|
||||
if container_listing_etag is None:
|
||||
container_listing_etag = plaintext_etag
|
||||
|
||||
if (container_listing_etag and
|
||||
(container_listing_etag != MD5_OF_EMPTY_STRING or
|
||||
plaintext_etag)):
|
||||
# Encrypt the container-listing etag using the container key
|
||||
|
||||
@@ -227,7 +227,6 @@ class TestEncrypter(unittest.TestCase):
|
||||
|
||||
def _test_PUT_with_other_footers(self, override_etag):
|
||||
# verify handling of another middleware's footer callback
|
||||
cont_key = fetch_crypto_keys()['container']
|
||||
body_key = os.urandom(32)
|
||||
object_key = fetch_crypto_keys()['object']
|
||||
plaintext = 'FAKE APP'
|
||||
@@ -324,7 +323,7 @@ class TestEncrypter(unittest.TestCase):
|
||||
def test_PUT_with_other_footers(self):
|
||||
self._test_PUT_with_other_footers('override etag')
|
||||
|
||||
def test_PUT_with_other_footers_and_empty_etag(self):
|
||||
def test_PUT_with_other_footers_and_etag_of_empty_body(self):
|
||||
# verify that an override etag value of EMPTY_ETAG will be encrypted
|
||||
# when there was a non-zero body length
|
||||
self._test_PUT_with_other_footers(EMPTY_ETAG)
|
||||
@@ -380,11 +379,82 @@ class TestEncrypter(unittest.TestCase):
|
||||
def test_PUT_with_etag_override_in_headers(self):
|
||||
self._test_PUT_with_etag_override_in_headers('override_etag')
|
||||
|
||||
def test_PUT_with_etag_override_in_headers_and_empty_etag(self):
|
||||
def test_PUT_with_etag_of_empty_body_override_in_headers(self):
|
||||
# verify that an override etag value of EMPTY_ETAG will be encrypted
|
||||
# when there was a non-zero body length
|
||||
self._test_PUT_with_etag_override_in_headers(EMPTY_ETAG)
|
||||
|
||||
def _test_PUT_with_empty_etag_override_in_headers(self, plaintext):
|
||||
# verify that an override etag value of '' from other middleware is
|
||||
# passed through unencrypted
|
||||
plaintext_etag = md5hex(plaintext)
|
||||
override_etag = ''
|
||||
env = {'REQUEST_METHOD': 'PUT',
|
||||
CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
|
||||
hdrs = {'content-type': 'text/plain',
|
||||
'content-length': str(len(plaintext)),
|
||||
'Etag': plaintext_etag,
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag':
|
||||
override_etag}
|
||||
req = Request.blank(
|
||||
'/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
|
||||
self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
|
||||
resp = req.get_response(self.encrypter)
|
||||
|
||||
self.assertEqual('201 Created', resp.status)
|
||||
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
||||
self.assertEqual(1, len(self.app.calls), self.app.calls)
|
||||
self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
|
||||
req_hdrs = self.app.headers[0]
|
||||
self.assertIn(
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
|
||||
self.assertEqual(
|
||||
override_etag,
|
||||
req_hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'])
|
||||
|
||||
def test_PUT_with_empty_etag_override_in_headers(self):
|
||||
self._test_PUT_with_empty_etag_override_in_headers('body')
|
||||
|
||||
def test_PUT_with_empty_etag_override_in_headers_no_body(self):
|
||||
self._test_PUT_with_empty_etag_override_in_headers('')
|
||||
|
||||
def _test_PUT_with_empty_etag_override_in_footers(self, plaintext):
|
||||
# verify that an override etag value of '' from other middleware is
|
||||
# passed through unencrypted
|
||||
plaintext_etag = md5hex(plaintext)
|
||||
override_etag = ''
|
||||
other_footers = {
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag}
|
||||
env = {'REQUEST_METHOD': 'PUT',
|
||||
CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
|
||||
'swift.callback.update_footers':
|
||||
lambda footers: footers.update(other_footers)}
|
||||
|
||||
hdrs = {'content-type': 'text/plain',
|
||||
'content-length': str(len(plaintext)),
|
||||
'Etag': plaintext_etag}
|
||||
req = Request.blank(
|
||||
'/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
|
||||
self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
|
||||
resp = req.get_response(self.encrypter)
|
||||
|
||||
self.assertEqual('201 Created', resp.status)
|
||||
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
||||
self.assertEqual(1, len(self.app.calls), self.app.calls)
|
||||
self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
|
||||
req_hdrs = self.app.headers[0]
|
||||
self.assertIn(
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
|
||||
self.assertEqual(
|
||||
override_etag,
|
||||
req_hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'])
|
||||
|
||||
def test_PUT_with_empty_etag_override_in_footers(self):
|
||||
self._test_PUT_with_empty_etag_override_in_footers('body')
|
||||
|
||||
def test_PUT_with_empty_etag_override_in_footers_no_body(self):
|
||||
self._test_PUT_with_empty_etag_override_in_footers('')
|
||||
|
||||
def test_PUT_with_bad_etag_in_other_footers(self):
|
||||
# verify that etag supplied in footers from other middleware overrides
|
||||
# header etag when validating inbound plaintext etags
|
||||
@@ -531,6 +601,29 @@ class TestEncrypter(unittest.TestCase):
|
||||
for k in call_headers[0]:
|
||||
self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-'))
|
||||
|
||||
# if upstream footer override etag is an empty string then check that
|
||||
# it is not encrypted
|
||||
other_footers = {
|
||||
'Etag': EMPTY_ETAG,
|
||||
'X-Object-Sysmeta-Container-Update-Override-Etag': ''}
|
||||
env.update({'swift.callback.update_footers':
|
||||
lambda footers: footers.update(other_footers)})
|
||||
req = Request.blank('/v1/a/c/o', environ=env, body='', headers=hdrs)
|
||||
|
||||
call_headers = []
|
||||
resp = req.get_response(encrypter.Encrypter(NonReadingApp(), {}))
|
||||
|
||||
self.assertEqual('201 Created', resp.status)
|
||||
self.assertEqual('response etag', resp.headers['Etag'])
|
||||
self.assertEqual(1, len(call_headers))
|
||||
|
||||
# verify that other middleware's footers made it to app
|
||||
for k, v in other_footers.items():
|
||||
self.assertEqual(v, call_headers[0][k])
|
||||
# verify no encryption footers
|
||||
for k in call_headers[0]:
|
||||
self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-'))
|
||||
|
||||
def test_POST_req(self):
|
||||
body = 'FAKE APP'
|
||||
env = {'REQUEST_METHOD': 'POST',
|
||||
|
||||
Reference in New Issue
Block a user