diff --git a/swift/common/middleware/crypto/encrypter.py b/swift/common/middleware/crypto/encrypter.py index 05e7bda79c..a5293b10fd 100644 --- a/swift/common/middleware/crypto/encrypter.py +++ b/swift/common/middleware/crypto/encrypter.py @@ -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 diff --git a/test/unit/common/middleware/crypto/test_encrypter.py b/test/unit/common/middleware/crypto/test_encrypter.py index f6c171b5a5..3a7569b564 100644 --- a/test/unit/common/middleware/crypto/test_encrypter.py +++ b/test/unit/common/middleware/crypto/test_encrypter.py @@ -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',