diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 8a213940eb..6e6cc1bd7a 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -717,13 +717,17 @@ use = egg:swift#versioned_writes # Note: To enable encryption, add the following 3 dependent pieces of # 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] use = egg:swift#decrypter -[filter:trivial_keymaster] -use = egg:swift#trivial_keymaster +[filter: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] use = egg:swift#encrypter diff --git a/setup.cfg b/setup.cfg index deecc72d4f..932eed9fed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,7 +97,7 @@ paste.filter_factory = xprofile = swift.common.middleware.xprofile:filter_factory versioned_writes = swift.common.middleware.versioned_writes: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 encrypter = swift.common.middleware.encrypter:filter_factory diff --git a/swift/common/middleware/trivial_keymaster.py b/swift/common/middleware/keymaster.py similarity index 87% rename from swift/common/middleware/trivial_keymaster.py rename to swift/common/middleware/keymaster.py index 6d90900cf0..c976a4ede4 100644 --- a/swift/common/middleware/trivial_keymaster.py +++ b/swift/common/middleware/keymaster.py @@ -14,13 +14,13 @@ # limitations under the License. """ -The simple scheme used here for testing the encryption feature in swift is as -follows: every path is associated with a key, where the key is derived from the +The simple scheme for key derivation is as follows: +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 stored. Specifically, the key for any path is an HMAC of a root key and the path itself, calculated using an SHA256 hash function:: - = HMAC_SHA256(, ) + = HMAC_SHA256(, ) """ import base64 @@ -35,9 +35,9 @@ from swift.common.wsgi import WSGIContext from swift.common.swob import Request, HTTPException, HTTPUnprocessableEntity -class TrivialKeyMasterContext(WSGIContext): +class KeyMasterContext(WSGIContext): def __init__(self, keymaster, account, container, obj): - super(TrivialKeyMasterContext, self).__init__(keymaster.app) + super(KeyMasterContext, self).__init__(keymaster.app) self.keymaster = keymaster self.logger = keymaster.logger self.account = account @@ -169,15 +169,16 @@ class TrivialKeyMasterContext(WSGIContext): return self.keys -class TrivialKeyMaster(object): - """ - Encryption keymaster middleware for testing. Don't use in production. - """ +class KeyMaster(object): + def __init__(self, app, conf): self.app = app - self.logger = get_logger(conf, log_route="trivial_keymaster") - # TODO: consider optionally loading root key from conf - self.root_key = 'secret'.encode('utf-8') + self.logger = get_logger(conf, log_route="keymaster") + self.root_secret = conf.get('encryption_root_secret', None) + 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): req = Request(env) @@ -187,9 +188,9 @@ class TrivialKeyMaster(object): except ValueError: 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 - km_context = TrivialKeyMasterContext(self, *parts[1:]) + km_context = KeyMasterContext(self, *parts[1:]) try: return getattr(km_context, req.method)(req, start_response) except HTTPException as err_resp: @@ -199,7 +200,11 @@ class TrivialKeyMaster(object): return self.app(env, start_response) 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() @@ -207,7 +212,7 @@ def filter_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) - def trivial_keymaster_filter(app): - return TrivialKeyMaster(app, conf) + def keymaster_filter(app): + return KeyMaster(app, conf) - return trivial_keymaster_filter + return keymaster_filter diff --git a/test/unit/common/middleware/test_trivial_keymaster.py b/test/unit/common/middleware/test_keymaster.py similarity index 81% rename from test/unit/common/middleware/test_trivial_keymaster.py rename to test/unit/common/middleware/test_keymaster.py index edbeccbc39..0f841895d3 100644 --- a/test/unit/common/middleware/test_trivial_keymaster.py +++ b/test/unit/common/middleware/test_keymaster.py @@ -16,7 +16,7 @@ import base64 import unittest -from swift.common.middleware import trivial_keymaster +from swift.common.middleware import keymaster from swift.common import swob Request = swob.Request @@ -31,10 +31,10 @@ def capture_start_response(): return start_response, calls -class TestTrivialKeymaster(unittest.TestCase): +class TestKeymaster(unittest.TestCase): def setUp(self): - super(TestTrivialKeymaster, self).setUp() + super(TestKeymaster, self).setUp() self.swift = FakeSwift() 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): put_keys = None - app = trivial_keymaster.TrivialKeyMaster(self.swift, {}) + app = keymaster.KeyMaster(self.swift, + {'encryption_root_secret': 'secret'}) for method, resp_class, status in ( ('PUT', swob.HTTPCreated, '201'), ('POST', swob.HTTPAccepted, '202'), @@ -85,7 +86,8 @@ class TestTrivialKeymaster(unittest.TestCase): # first get keys when path matches key_id method = 'HEAD' 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}) start_response, calls = capture_start_response() app(req.environ, start_response) @@ -98,7 +100,8 @@ class TestTrivialKeymaster(unittest.TestCase): path = '/v1/a/got/relocated' for method in ('HEAD', 'GET'): 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}) start_response, calls = capture_start_response() app(req.environ, start_response) @@ -113,7 +116,8 @@ class TestTrivialKeymaster(unittest.TestCase): for method in ('HEAD', 'GET'): path = '/v1/a/c/o' 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}) start_response, calls = capture_start_response() app(req.environ, start_response) @@ -130,13 +134,14 @@ class TestTrivialKeymaster(unittest.TestCase): self.swift.register(method, path, swob.HTTPOk, {'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}) start_response, calls = capture_start_response() app(req.environ, start_response) self.assertEqual(1, len(calls)) # 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 # returned when keys are 'missing' and crypto-meta is found. self.assertEqual('200 OK', calls[0][0]) @@ -151,7 +156,8 @@ class TestTrivialKeymaster(unittest.TestCase): 'x-object-meta-crypto-meta': 'no probs', '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}) start_response, calls = capture_start_response() app(req.environ, start_response) @@ -159,17 +165,30 @@ class TestTrivialKeymaster(unittest.TestCase): self.assertEqual('200 OK', calls[0][0]) 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.swift))) def test_app_exception(self): - app = trivial_keymaster.TrivialKeyMaster( - FakeAppThatExcepts(), {}) + app = keymaster.KeyMaster( + FakeAppThatExcepts(), {'encryption_root_secret': 'secret'}) req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'}) start_response, _ = capture_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__': unittest.main()