encryption: Expose decrypted metadata via CORS
Normally, the proxy object controller would be adding these, but when encrypted, there won't be any headers in the x-object-meta-* namespace. Closes-Bug: #1868045 Change-Id: I8e708a60ee63f679056300fc9d68227e46d605e8
This commit is contained in:
parent
3bf7cf60b9
commit
cd693e519e
@ -197,7 +197,7 @@ class DecrypterObjContext(BaseDecrypterContext):
|
|||||||
result.append((new_prefix + short_name, decrypted_value))
|
result.append((new_prefix + short_name, decrypted_value))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def decrypt_resp_headers(self, put_keys, post_keys):
|
def decrypt_resp_headers(self, put_keys, post_keys, update_cors_exposed):
|
||||||
"""
|
"""
|
||||||
Find encrypted headers and replace with the decrypted versions.
|
Find encrypted headers and replace with the decrypted versions.
|
||||||
|
|
||||||
@ -236,11 +236,27 @@ class DecrypterObjContext(BaseDecrypterContext):
|
|||||||
# that map to the same x-object-meta- header names i.e. decrypted
|
# that map to the same x-object-meta- header names i.e. decrypted
|
||||||
# headers win over unexpected, unencrypted headers.
|
# headers win over unexpected, unencrypted headers.
|
||||||
if post_keys:
|
if post_keys:
|
||||||
mod_hdr_pairs.extend(self.decrypt_user_metadata(post_keys))
|
decrypted_meta = self.decrypt_user_metadata(post_keys)
|
||||||
|
mod_hdr_pairs.extend(decrypted_meta)
|
||||||
|
else:
|
||||||
|
decrypted_meta = []
|
||||||
|
|
||||||
mod_hdr_names = {h.lower() for h, v in mod_hdr_pairs}
|
mod_hdr_names = {h.lower() for h, v in mod_hdr_pairs}
|
||||||
mod_hdr_pairs.extend([(h, v) for h, v in self._response_headers
|
|
||||||
if h.lower() not in mod_hdr_names])
|
found_aceh = False
|
||||||
|
for header, value in self._response_headers:
|
||||||
|
lheader = header.lower()
|
||||||
|
if lheader in mod_hdr_names:
|
||||||
|
continue
|
||||||
|
if lheader == 'access-control-expose-headers':
|
||||||
|
found_aceh = True
|
||||||
|
mod_hdr_pairs.append((header, value + ', ' + ', '.join(
|
||||||
|
meta.lower() for meta, _data in decrypted_meta)))
|
||||||
|
else:
|
||||||
|
mod_hdr_pairs.append((header, value))
|
||||||
|
if update_cors_exposed and not found_aceh:
|
||||||
|
mod_hdr_pairs.append(('Access-Control-Expose-Headers', ', '.join(
|
||||||
|
meta.lower() for meta, _data in decrypted_meta)))
|
||||||
return mod_hdr_pairs
|
return mod_hdr_pairs
|
||||||
|
|
||||||
def multipart_response_iter(self, resp, boundary, body_key, crypto_meta):
|
def multipart_response_iter(self, resp, boundary, body_key, crypto_meta):
|
||||||
@ -326,7 +342,9 @@ class DecrypterObjContext(BaseDecrypterContext):
|
|||||||
self._response_exc_info)
|
self._response_exc_info)
|
||||||
return app_resp
|
return app_resp
|
||||||
|
|
||||||
mod_resp_headers = self.decrypt_resp_headers(put_keys, post_keys)
|
mod_resp_headers = self.decrypt_resp_headers(
|
||||||
|
put_keys, post_keys,
|
||||||
|
update_cors_exposed=bool(req.headers.get('origin')))
|
||||||
|
|
||||||
if put_crypto_meta and req.method == 'GET' and \
|
if put_crypto_meta and req.method == 'GET' and \
|
||||||
is_success(self._get_status_int()):
|
is_success(self._get_status_int()):
|
||||||
|
@ -1542,7 +1542,7 @@ class TestObject(unittest.TestCase):
|
|||||||
def put_obj(url, token, parsed, conn, obj):
|
def put_obj(url, token, parsed, conn, obj):
|
||||||
conn.request(
|
conn.request(
|
||||||
'PUT', '%s/%s/%s' % (parsed.path, self.container, obj),
|
'PUT', '%s/%s/%s' % (parsed.path, self.container, obj),
|
||||||
'test', {'X-Auth-Token': token})
|
'test', {'X-Auth-Token': token, 'X-Object-Meta-Color': 'red'})
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
|
|
||||||
def check_cors(url, token, parsed, conn,
|
def check_cors(url, token, parsed, conn,
|
||||||
@ -1576,6 +1576,8 @@ class TestObject(unittest.TestCase):
|
|||||||
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
self.assertEqual(headers.get('access-control-allow-origin'),
|
self.assertEqual(headers.get('access-control-allow-origin'),
|
||||||
'*')
|
'*')
|
||||||
|
# Just a pre-flight; this doesn't show up yet
|
||||||
|
self.assertNotIn('access-control-expose-headers', headers)
|
||||||
|
|
||||||
resp = retry(check_cors,
|
resp = retry(check_cors,
|
||||||
'GET', 'cat', {'Origin': 'http://m.com'})
|
'GET', 'cat', {'Origin': 'http://m.com'})
|
||||||
@ -1583,6 +1585,8 @@ class TestObject(unittest.TestCase):
|
|||||||
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
self.assertEqual(headers.get('access-control-allow-origin'),
|
self.assertEqual(headers.get('access-control-allow-origin'),
|
||||||
'*')
|
'*')
|
||||||
|
self.assertIn('x-object-meta-color', headers.get(
|
||||||
|
'access-control-expose-headers').split(', '))
|
||||||
|
|
||||||
resp = retry(check_cors,
|
resp = retry(check_cors,
|
||||||
'GET', 'cat', {'Origin': 'http://m.com',
|
'GET', 'cat', {'Origin': 'http://m.com',
|
||||||
@ -1591,6 +1595,8 @@ class TestObject(unittest.TestCase):
|
|||||||
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
self.assertEqual(headers.get('access-control-allow-origin'),
|
self.assertEqual(headers.get('access-control-allow-origin'),
|
||||||
'*')
|
'*')
|
||||||
|
self.assertIn('x-object-meta-color', headers.get(
|
||||||
|
'access-control-expose-headers').split(', '))
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
resp.headers['X-Object-Sysmeta-Container-Update-Override-Etag'])
|
resp.headers['X-Object-Sysmeta-Container-Update-Override-Etag'])
|
||||||
self.assertNotIn('X-Object-Sysmeta-Crypto-Body-Meta', resp.headers)
|
self.assertNotIn('X-Object-Sysmeta-Crypto-Body-Meta', resp.headers)
|
||||||
self.assertNotIn('X-Object-Sysmeta-Crypto-Etag', resp.headers)
|
self.assertNotIn('X-Object-Sysmeta-Crypto-Etag', resp.headers)
|
||||||
|
self.assertNotIn('Access-Control-Expose-Headers', resp.headers)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def test_GET_success(self):
|
def test_GET_success(self):
|
||||||
@ -226,6 +227,7 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
||||||
self.assertEqual('text/plain', resp.headers['Content-Type'])
|
self.assertEqual('text/plain', resp.headers['Content-Type'])
|
||||||
self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
|
self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
|
||||||
|
self.assertNotIn('Access-Control-Expose-Headers', resp.headers)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def test_GET_unencrypted_data_and_encrypted_metadata(self):
|
def test_GET_unencrypted_data_and_encrypted_metadata(self):
|
||||||
@ -259,6 +261,7 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
self.assertEqual(plaintext_etag, resp.headers['Etag'])
|
||||||
self.assertEqual('text/plain', resp.headers['Content-Type'])
|
self.assertEqual('text/plain', resp.headers['Content-Type'])
|
||||||
self.assertEqual('unencrypted', resp.headers['x-object-meta-test'])
|
self.assertEqual('unencrypted', resp.headers['x-object-meta-test'])
|
||||||
|
self.assertNotIn('Access-Control-Expose-Headers', resp.headers)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def test_GET_encrypted_data_and_unencrypted_metadata(self):
|
def test_GET_encrypted_data_and_unencrypted_metadata(self):
|
||||||
@ -271,7 +274,8 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_headers_case(self):
|
def test_headers_case(self):
|
||||||
body = b'fAkE ApP'
|
body = b'fAkE ApP'
|
||||||
req = Request.blank('/v1/a/c/o', body='FaKe')
|
req = Request.blank('/v1/a/c/o', body='FaKe', headers={
|
||||||
|
'Origin': 'http://example.com'})
|
||||||
req.environ[CRYPTO_KEY_CALLBACK] = fetch_crypto_keys
|
req.environ[CRYPTO_KEY_CALLBACK] = fetch_crypto_keys
|
||||||
plaintext_etag = md5hex(body)
|
plaintext_etag = md5hex(body)
|
||||||
body_key = os.urandom(32)
|
body_key = os.urandom(32)
|
||||||
@ -281,7 +285,10 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
|
|
||||||
hdrs.update({
|
hdrs.update({
|
||||||
'x-Object-mEta-ignoRes-caSe': 'thIs pArt WilL bE cOol',
|
'x-Object-mEta-ignoRes-caSe': 'thIs pArt WilL bE cOol',
|
||||||
|
'access-control-Expose-Headers': 'x-object-meta-ignores-case',
|
||||||
|
'access-control-allow-origin': '*',
|
||||||
})
|
})
|
||||||
|
self.assertNotIn('x-object-meta-test', [k.lower() for k in hdrs])
|
||||||
self.app.register(
|
self.app.register(
|
||||||
'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
|
'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
|
||||||
|
|
||||||
@ -296,6 +303,11 @@ class TestDecrypterObjectRequests(unittest.TestCase):
|
|||||||
'X-Object-Meta-Ignores-Case': 'thIs pArt WilL bE cOol',
|
'X-Object-Meta-Ignores-Case': 'thIs pArt WilL bE cOol',
|
||||||
'X-Object-Sysmeta-Test': 'do not encrypt me',
|
'X-Object-Sysmeta-Test': 'do not encrypt me',
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
|
'Access-Control-Expose-Headers': ', '.join([
|
||||||
|
'x-object-meta-ignores-case',
|
||||||
|
'x-object-meta-test',
|
||||||
|
]),
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
}
|
}
|
||||||
self.assertEqual(dict(headers), expected)
|
self.assertEqual(dict(headers), expected)
|
||||||
self.assertEqual(b'fAkE ApP', b''.join(app_iter))
|
self.assertEqual(b'fAkE ApP', b''.join(app_iter))
|
||||||
|
Loading…
Reference in New Issue
Block a user