Transition to 'keymaster'
Transitioned from 'trivial_keymaster' with constant root key to 'keymaster' with root key set in proxy-server.conf. Change-Id: Ib5051bc402d5758f149c6afbaee7ccf06c44bbca
This commit is contained in:
parent
19226e4449
commit
8cb5505bf1
|
@ -717,13 +717,17 @@ use = egg:swift#versioned_writes
|
||||||
|
|
||||||
# Note: To enable encryption, add the following 3 dependent pieces of
|
# Note: To enable encryption, add the following 3 dependent pieces of
|
||||||
# crypto middleware to the proxy-server pipeline as follows:
|
# crypto middleware to the proxy-server pipeline as follows:
|
||||||
# ... decrypter trivial_keymaster encrypter proxy-logging (end of pipeline)
|
# ... decrypter keymaster encrypter proxy-logging (end of pipeline)
|
||||||
|
|
||||||
[filter:decrypter]
|
[filter:decrypter]
|
||||||
use = egg:swift#decrypter
|
use = egg:swift#decrypter
|
||||||
|
|
||||||
[filter:trivial_keymaster]
|
[filter:keymaster]
|
||||||
use = egg:swift#trivial_keymaster
|
use = egg:swift#keymaster
|
||||||
|
|
||||||
|
# Sets the root key from which encryption keys are derived. Change before
|
||||||
|
# first use. After that, changing the key may result in data loss.
|
||||||
|
encryption_root_secret = change_before_use
|
||||||
|
|
||||||
[filter:encrypter]
|
[filter:encrypter]
|
||||||
use = egg:swift#encrypter
|
use = egg:swift#encrypter
|
||||||
|
|
|
@ -97,7 +97,7 @@ paste.filter_factory =
|
||||||
xprofile = swift.common.middleware.xprofile:filter_factory
|
xprofile = swift.common.middleware.xprofile:filter_factory
|
||||||
versioned_writes = swift.common.middleware.versioned_writes:filter_factory
|
versioned_writes = swift.common.middleware.versioned_writes:filter_factory
|
||||||
fake_footers = swift.common.middleware.fake_footers:filter_factory
|
fake_footers = swift.common.middleware.fake_footers:filter_factory
|
||||||
trivial_keymaster = swift.common.middleware.trivial_keymaster:filter_factory
|
keymaster = swift.common.middleware.keymaster:filter_factory
|
||||||
decrypter = swift.common.middleware.decrypter:filter_factory
|
decrypter = swift.common.middleware.decrypter:filter_factory
|
||||||
encrypter = swift.common.middleware.encrypter:filter_factory
|
encrypter = swift.common.middleware.encrypter:filter_factory
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The simple scheme used here for testing the encryption feature in swift is as
|
The simple scheme for key derivation is as follows:
|
||||||
follows: every path is associated with a key, where the key is derived from the
|
every path is associated with a key, where the key is derived from the
|
||||||
path itself in a deterministic fashion such that the key does not need to be
|
path itself in a deterministic fashion such that the key does not need to be
|
||||||
stored. Specifically, the key for any path is an HMAC of a root key and the
|
stored. Specifically, the key for any path is an HMAC of a root key and the
|
||||||
path itself, calculated using an SHA256 hash function::
|
path itself, calculated using an SHA256 hash function::
|
||||||
|
|
||||||
<path_key> = HMAC_SHA256(<root_key>, <path>)
|
<path_key> = HMAC_SHA256(<root_secret>, <path>)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
@ -35,9 +35,9 @@ from swift.common.wsgi import WSGIContext
|
||||||
from swift.common.swob import Request, HTTPException, HTTPUnprocessableEntity
|
from swift.common.swob import Request, HTTPException, HTTPUnprocessableEntity
|
||||||
|
|
||||||
|
|
||||||
class TrivialKeyMasterContext(WSGIContext):
|
class KeyMasterContext(WSGIContext):
|
||||||
def __init__(self, keymaster, account, container, obj):
|
def __init__(self, keymaster, account, container, obj):
|
||||||
super(TrivialKeyMasterContext, self).__init__(keymaster.app)
|
super(KeyMasterContext, self).__init__(keymaster.app)
|
||||||
self.keymaster = keymaster
|
self.keymaster = keymaster
|
||||||
self.logger = keymaster.logger
|
self.logger = keymaster.logger
|
||||||
self.account = account
|
self.account = account
|
||||||
|
@ -169,15 +169,16 @@ class TrivialKeyMasterContext(WSGIContext):
|
||||||
return self.keys
|
return self.keys
|
||||||
|
|
||||||
|
|
||||||
class TrivialKeyMaster(object):
|
class KeyMaster(object):
|
||||||
"""
|
|
||||||
Encryption keymaster middleware for testing. Don't use in production.
|
|
||||||
"""
|
|
||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.logger = get_logger(conf, log_route="trivial_keymaster")
|
self.logger = get_logger(conf, log_route="keymaster")
|
||||||
# TODO: consider optionally loading root key from conf
|
self.root_secret = conf.get('encryption_root_secret', None)
|
||||||
self.root_key = 'secret'.encode('utf-8')
|
if not self.root_secret:
|
||||||
|
raise ValueError('encryption_root_secret not set in '
|
||||||
|
'proxy-server.conf')
|
||||||
|
self.root_secret = self.root_secret.encode('utf-8')
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
req = Request(env)
|
req = Request(env)
|
||||||
|
@ -187,9 +188,9 @@ class TrivialKeyMaster(object):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
|
|
||||||
if hasattr(TrivialKeyMasterContext, req.method):
|
if hasattr(KeyMasterContext, req.method):
|
||||||
# handle only those request methods that may require keys
|
# handle only those request methods that may require keys
|
||||||
km_context = TrivialKeyMasterContext(self, *parts[1:])
|
km_context = KeyMasterContext(self, *parts[1:])
|
||||||
try:
|
try:
|
||||||
return getattr(km_context, req.method)(req, start_response)
|
return getattr(km_context, req.method)(req, start_response)
|
||||||
except HTTPException as err_resp:
|
except HTTPException as err_resp:
|
||||||
|
@ -199,7 +200,11 @@ class TrivialKeyMaster(object):
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
|
|
||||||
def create_key(self, key_id):
|
def create_key(self, key_id):
|
||||||
return hmac.new(self.root_key, key_id,
|
key_id = 'fixed'
|
||||||
|
# TODO: setting key_id to 'fixed' is a temporary workaround for
|
||||||
|
# problems caused by the lack of copy middleware. Once that is merged,
|
||||||
|
# we can remove the above line.
|
||||||
|
return hmac.new(self.root_secret, key_id,
|
||||||
digestmod=hashlib.sha256).digest()
|
digestmod=hashlib.sha256).digest()
|
||||||
|
|
||||||
|
|
||||||
|
@ -207,7 +212,7 @@ def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
|
||||||
def trivial_keymaster_filter(app):
|
def keymaster_filter(app):
|
||||||
return TrivialKeyMaster(app, conf)
|
return KeyMaster(app, conf)
|
||||||
|
|
||||||
return trivial_keymaster_filter
|
return keymaster_filter
|
|
@ -16,7 +16,7 @@ import base64
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from swift.common.middleware import trivial_keymaster
|
from swift.common.middleware import keymaster
|
||||||
from swift.common import swob
|
from swift.common import swob
|
||||||
Request = swob.Request
|
Request = swob.Request
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ def capture_start_response():
|
||||||
return start_response, calls
|
return start_response, calls
|
||||||
|
|
||||||
|
|
||||||
class TestTrivialKeymaster(unittest.TestCase):
|
class TestKeymaster(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestTrivialKeymaster, self).setUp()
|
super(TestKeymaster, self).setUp()
|
||||||
self.swift = FakeSwift()
|
self.swift = FakeSwift()
|
||||||
|
|
||||||
def test_object_path(self):
|
def test_object_path(self):
|
||||||
|
@ -48,7 +48,8 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
|
|
||||||
def verify_keys_for_path(self, path, expected_keys, key_id=None):
|
def verify_keys_for_path(self, path, expected_keys, key_id=None):
|
||||||
put_keys = None
|
put_keys = None
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
for method, resp_class, status in (
|
for method, resp_class, status in (
|
||||||
('PUT', swob.HTTPCreated, '201'),
|
('PUT', swob.HTTPCreated, '201'),
|
||||||
('POST', swob.HTTPAccepted, '202'),
|
('POST', swob.HTTPAccepted, '202'),
|
||||||
|
@ -85,7 +86,8 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
# first get keys when path matches key_id
|
# first get keys when path matches key_id
|
||||||
method = 'HEAD'
|
method = 'HEAD'
|
||||||
self.swift.register(method, path, swob.HTTPOk, resp_headers, '')
|
self.swift.register(method, path, swob.HTTPOk, resp_headers, '')
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
||||||
start_response, calls = capture_start_response()
|
start_response, calls = capture_start_response()
|
||||||
app(req.environ, start_response)
|
app(req.environ, start_response)
|
||||||
|
@ -98,7 +100,8 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
path = '/v1/a/got/relocated'
|
path = '/v1/a/got/relocated'
|
||||||
for method in ('HEAD', 'GET'):
|
for method in ('HEAD', 'GET'):
|
||||||
self.swift.register(method, path, swob.HTTPOk, resp_headers, '')
|
self.swift.register(method, path, swob.HTTPOk, resp_headers, '')
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
||||||
start_response, calls = capture_start_response()
|
start_response, calls = capture_start_response()
|
||||||
app(req.environ, start_response)
|
app(req.environ, start_response)
|
||||||
|
@ -113,7 +116,8 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
for method in ('HEAD', 'GET'):
|
for method in ('HEAD', 'GET'):
|
||||||
path = '/v1/a/c/o'
|
path = '/v1/a/c/o'
|
||||||
self.swift.register(method, path, swob.HTTPOk, {}, '')
|
self.swift.register(method, path, swob.HTTPOk, {}, '')
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
||||||
start_response, calls = capture_start_response()
|
start_response, calls = capture_start_response()
|
||||||
app(req.environ, start_response)
|
app(req.environ, start_response)
|
||||||
|
@ -130,13 +134,14 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
self.swift.register(method, path, swob.HTTPOk,
|
self.swift.register(method, path, swob.HTTPOk,
|
||||||
{'x-object-sysmeta-crypto-meta-foo': 'gotcha'},
|
{'x-object-sysmeta-crypto-meta-foo': 'gotcha'},
|
||||||
'')
|
'')
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
||||||
start_response, calls = capture_start_response()
|
start_response, calls = capture_start_response()
|
||||||
app(req.environ, start_response)
|
app(req.environ, start_response)
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls))
|
||||||
# TODO change to expect 422 once FakeFooters is removed.
|
# TODO change to expect 422 once FakeFooters is removed.
|
||||||
# error_if_need_keys is currently disabled in trivial_keymaster
|
# error_if_need_keys is currently disabled in keymaster
|
||||||
# because of how FakeFooters works. So 422's will not currently be
|
# because of how FakeFooters works. So 422's will not currently be
|
||||||
# returned when keys are 'missing' and crypto-meta is found.
|
# returned when keys are 'missing' and crypto-meta is found.
|
||||||
self.assertEqual('200 OK', calls[0][0])
|
self.assertEqual('200 OK', calls[0][0])
|
||||||
|
@ -151,7 +156,8 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
'x-object-meta-crypto-meta': 'no probs',
|
'x-object-meta-crypto-meta': 'no probs',
|
||||||
'crypto-meta': 'pas de problem'},
|
'crypto-meta': 'pas de problem'},
|
||||||
'')
|
'')
|
||||||
app = trivial_keymaster.TrivialKeyMaster(self.swift, {})
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
req = Request.blank(path, environ={'REQUEST_METHOD': method})
|
||||||
start_response, calls = capture_start_response()
|
start_response, calls = capture_start_response()
|
||||||
app(req.environ, start_response)
|
app(req.environ, start_response)
|
||||||
|
@ -159,17 +165,30 @@ class TestTrivialKeymaster(unittest.TestCase):
|
||||||
self.assertEqual('200 OK', calls[0][0])
|
self.assertEqual('200 OK', calls[0][0])
|
||||||
|
|
||||||
def test_filter(self):
|
def test_filter(self):
|
||||||
factory = trivial_keymaster.filter_factory({})
|
factory = keymaster.filter_factory(
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
self.assertTrue(callable(factory))
|
self.assertTrue(callable(factory))
|
||||||
self.assertTrue(callable(factory(self.swift)))
|
self.assertTrue(callable(factory(self.swift)))
|
||||||
|
|
||||||
def test_app_exception(self):
|
def test_app_exception(self):
|
||||||
app = trivial_keymaster.TrivialKeyMaster(
|
app = keymaster.KeyMaster(
|
||||||
FakeAppThatExcepts(), {})
|
FakeAppThatExcepts(), {'encryption_root_secret': 'secret'})
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'})
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'})
|
||||||
start_response, _ = capture_start_response()
|
start_response, _ = capture_start_response()
|
||||||
self.assertRaises(Exception, app, req.environ, start_response)
|
self.assertRaises(Exception, app, req.environ, start_response)
|
||||||
|
|
||||||
|
def test_key_loaded(self):
|
||||||
|
app = keymaster.KeyMaster(self.swift,
|
||||||
|
{'encryption_root_secret': 'secret'})
|
||||||
|
self.assertEqual(app.root_secret, 'secret')
|
||||||
|
|
||||||
|
def test_no_root_secret_error(self):
|
||||||
|
with self.assertRaises(ValueError) as err:
|
||||||
|
keymaster.KeyMaster(self.swift, {})
|
||||||
|
|
||||||
|
self.assertEqual(err.exception.message,
|
||||||
|
'encryption_root_secret not set in proxy-server.conf')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
Loading…
Reference in New Issue