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:
Jonathan Hinson 2015-11-12 16:55:29 -06:00
parent 19226e4449
commit 8cb5505bf1
4 changed files with 63 additions and 35 deletions

View File

@ -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

View File

@ -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

View File

@ -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::
<path_key> = HMAC_SHA256(<root_key>, <path>)
<path_key> = HMAC_SHA256(<root_secret>, <path>)
"""
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

View File

@ -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()