Add keymaster_config_path option to keymaster
Also, tighten up the format checks on root secrets. Change-Id: I1cd9a97c4e8d87d7c065866e7ad3a9e748ff19ab
This commit is contained in:
parent
9045f33869
commit
6740a7badd
@ -783,6 +783,15 @@ use = egg:swift#keymaster
|
|||||||
# likely to result in data loss.
|
# likely to result in data loss.
|
||||||
encryption_root_secret = changeme
|
encryption_root_secret = changeme
|
||||||
|
|
||||||
|
# Sets the path from which the keymaster config options should be read. This
|
||||||
|
# allows multiple processes which need to be encryption-aware (for example,
|
||||||
|
# proxy-server and container-sync) to share the same config file, ensuring
|
||||||
|
# that the encryption keys used are the same. The format expected is similar
|
||||||
|
# to other config files, with a single [keymaster] section and a single
|
||||||
|
# encryption_root_secret option. If this option is set, the root secret
|
||||||
|
# MUST NOT be set in proxy-server.conf.
|
||||||
|
# keymaster_config_path =
|
||||||
|
|
||||||
[filter:encryption]
|
[filter:encryption]
|
||||||
use = egg:swift#encryption
|
use = egg:swift#encryption
|
||||||
|
|
||||||
|
@ -16,9 +16,13 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import os
|
import os
|
||||||
|
import string
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from swift.common.middleware.crypto.crypto_utils import CRYPTO_KEY_CALLBACK
|
from swift.common.middleware.crypto.crypto_utils import CRYPTO_KEY_CALLBACK
|
||||||
from swift.common.swob import Request, HTTPException
|
from swift.common.swob import Request, HTTPException
|
||||||
|
from swift.common.utils import readconf
|
||||||
from swift.common.wsgi import WSGIContext
|
from swift.common.wsgi import WSGIContext
|
||||||
|
|
||||||
|
|
||||||
@ -109,15 +113,30 @@ class KeyMaster(object):
|
|||||||
|
|
||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
|
keymaster_config_path = conf.get('keymaster_config_path')
|
||||||
|
if keymaster_config_path:
|
||||||
|
if any(opt in conf for opt in ('encryption_root_secret',)):
|
||||||
|
raise ValueError('keymaster_config_path is set, but there '
|
||||||
|
'are other config options specified!')
|
||||||
|
conf = readconf(keymaster_config_path, 'keymaster')
|
||||||
|
|
||||||
self.root_secret = conf.get('encryption_root_secret')
|
self.root_secret = conf.get('encryption_root_secret')
|
||||||
try:
|
try:
|
||||||
|
# b64decode will silently discard bad characters, but we should
|
||||||
|
# treat them as an error
|
||||||
|
if not isinstance(self.root_secret, six.string_types) or any(
|
||||||
|
c not in string.digits + string.ascii_letters + '/+\r\n'
|
||||||
|
for c in self.root_secret.strip('\r\n=')):
|
||||||
|
raise ValueError
|
||||||
self.root_secret = base64.b64decode(self.root_secret)
|
self.root_secret = base64.b64decode(self.root_secret)
|
||||||
if len(self.root_secret) < 32:
|
if len(self.root_secret) < 32:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'encryption_root_secret option in proxy-server.conf must be '
|
'encryption_root_secret option in %s must be a base64 '
|
||||||
'a base64 encoding of at least 32 raw bytes')
|
'encoding of at least 32 raw bytes' % (
|
||||||
|
keymaster_config_path or 'proxy-server.conf'))
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
req = Request(env)
|
req = Request(env)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import mock
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from swift.common import swob
|
from swift.common import swob
|
||||||
@ -126,25 +127,42 @@ class TestKeymaster(unittest.TestCase):
|
|||||||
def test_root_secret(self):
|
def test_root_secret(self):
|
||||||
for secret in (os.urandom(32), os.urandom(33), os.urandom(50)):
|
for secret in (os.urandom(32), os.urandom(33), os.urandom(50)):
|
||||||
encoded_secret = base64.b64encode(secret)
|
encoded_secret = base64.b64encode(secret)
|
||||||
|
for conf_val in (bytes(encoded_secret), unicode(encoded_secret),
|
||||||
|
encoded_secret[:30] + '\n' + encoded_secret[30:]):
|
||||||
try:
|
try:
|
||||||
app = keymaster.KeyMaster(
|
app = keymaster.KeyMaster(
|
||||||
self.swift, {'encryption_root_secret':
|
self.swift, {'encryption_root_secret': conf_val,
|
||||||
bytes(encoded_secret)})
|
'encryption_root_secret_path': ''})
|
||||||
self.assertEqual(secret, app.root_secret)
|
self.assertEqual(secret, app.root_secret)
|
||||||
except AssertionError as err:
|
except AssertionError as err:
|
||||||
self.fail(str(err) + ' for secret %s' % secret)
|
self.fail(str(err) + ' for secret %r' % conf_val)
|
||||||
|
|
||||||
|
@mock.patch('swift.common.middleware.crypto.keymaster.readconf')
|
||||||
|
def test_keymaster_config_path(self, mock_readconf):
|
||||||
|
for secret in (os.urandom(32), os.urandom(33), os.urandom(50)):
|
||||||
|
enc_secret = base64.b64encode(secret)
|
||||||
|
for conf_val in (bytes(enc_secret), unicode(enc_secret),
|
||||||
|
enc_secret[:30] + '\n' + enc_secret[30:],
|
||||||
|
enc_secret[:30] + '\r\n' + enc_secret[30:]):
|
||||||
|
for ignored_secret in ('invalid! but ignored!',
|
||||||
|
'xValidButIgnored' * 10):
|
||||||
|
mock_readconf.reset_mock()
|
||||||
|
mock_readconf.return_value = {
|
||||||
|
'encryption_root_secret': conf_val}
|
||||||
|
|
||||||
|
app = keymaster.KeyMaster(self.swift, {
|
||||||
|
'keymaster_config_path': '/some/path'})
|
||||||
try:
|
try:
|
||||||
app = keymaster.KeyMaster(
|
|
||||||
self.swift, {'encryption_root_secret':
|
|
||||||
unicode(encoded_secret)})
|
|
||||||
self.assertEqual(secret, app.root_secret)
|
self.assertEqual(secret, app.root_secret)
|
||||||
|
self.assertEqual(mock_readconf.mock_calls, [
|
||||||
|
mock.call('/some/path', 'keymaster')])
|
||||||
except AssertionError as err:
|
except AssertionError as err:
|
||||||
self.fail(str(err) + ' for secret %s' % secret)
|
self.fail(str(err) + ' for secret %r' % secret)
|
||||||
|
|
||||||
def test_invalid_root_secret(self):
|
def test_invalid_root_secret(self):
|
||||||
for secret in (bytes(base64.b64encode(os.urandom(31))), # too short
|
for secret in (bytes(base64.b64encode(os.urandom(31))), # too short
|
||||||
unicode(base64.b64encode(os.urandom(31))),
|
unicode(base64.b64encode(os.urandom(31))),
|
||||||
u'?' * 44, b'?' * 44, # not base64
|
u'a' * 44 + u'????', b'a' * 44 + b'????', # not base64
|
||||||
u'a' * 45, b'a' * 45, # bad padding
|
u'a' * 45, b'a' * 45, # bad padding
|
||||||
99, None):
|
99, None):
|
||||||
conf = {'encryption_root_secret': secret}
|
conf = {'encryption_root_secret': secret}
|
||||||
@ -158,6 +176,38 @@ class TestKeymaster(unittest.TestCase):
|
|||||||
except AssertionError as err:
|
except AssertionError as err:
|
||||||
self.fail(str(err) + ' for conf %s' % str(conf))
|
self.fail(str(err) + ' for conf %s' % str(conf))
|
||||||
|
|
||||||
|
@mock.patch('swift.common.middleware.crypto.keymaster.readconf')
|
||||||
|
def test_root_secret_path_invalid_secret(self, mock_readconf):
|
||||||
|
for secret in (bytes(base64.b64encode(os.urandom(31))), # too short
|
||||||
|
unicode(base64.b64encode(os.urandom(31))),
|
||||||
|
u'a' * 44 + u'????', b'a' * 44 + b'????', # not base64
|
||||||
|
u'a' * 45, b'a' * 45, # bad padding
|
||||||
|
99, None):
|
||||||
|
mock_readconf.reset_mock()
|
||||||
|
mock_readconf.return_value = {'encryption_root_secret': secret}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.assertRaises(ValueError) as err:
|
||||||
|
keymaster.KeyMaster(self.swift, {
|
||||||
|
'keymaster_config_path': '/some/other/path'})
|
||||||
|
self.assertEqual(
|
||||||
|
'encryption_root_secret option in /some/other/path '
|
||||||
|
'must be a base64 encoding of at least 32 raw bytes',
|
||||||
|
err.exception.message)
|
||||||
|
self.assertEqual(mock_readconf.mock_calls, [
|
||||||
|
mock.call('/some/other/path', 'keymaster')])
|
||||||
|
except AssertionError as err:
|
||||||
|
self.fail(str(err) + ' for secret %r' % secret)
|
||||||
|
|
||||||
|
def test_can_only_configure_secret_in_one_place(self):
|
||||||
|
conf = {'encryption_root_secret': 'a' * 44,
|
||||||
|
'keymaster_config_path': '/ets/swift/keymaster.conf'}
|
||||||
|
with self.assertRaises(ValueError) as err:
|
||||||
|
keymaster.KeyMaster(self.swift, conf)
|
||||||
|
self.assertEqual('keymaster_config_path is set, but there are '
|
||||||
|
'other config options specified!',
|
||||||
|
err.exception.message)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user