Merge "Factor out a proper BaseKeyMaster class"
This commit is contained in:
@@ -122,31 +122,40 @@ class KeyMasterContext(WSGIContext):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
class KeyMaster(object):
|
class BaseKeyMaster(object):
|
||||||
"""Middleware for providing encryption keys.
|
"""Base middleware for providing encryption keys.
|
||||||
|
|
||||||
The middleware requires at least one encryption root secret(s) to be set.
|
This provides some basic helpers for:
|
||||||
This is the root secret from which encryption keys are derived. This must
|
|
||||||
be set before first use to a value that is at least 256 bits. The security
|
- loading from a separate config path,
|
||||||
of all encrypted data critically depends on this key, therefore it should
|
- deriving keys based on path, and
|
||||||
be set to a high-entropy value. For example, a suitable value may be
|
- installing a ``swift.callback.fetch_crypto_keys`` hook
|
||||||
obtained by generating a 32 byte (or longer) value using a
|
in the request environment.
|
||||||
cryptographically secure random number generator. Changing the root secret
|
|
||||||
is likely to result in data loss.
|
Subclasses should define ``log_route``, ``keymaster_opts``, and
|
||||||
|
``keymaster_conf_section`` attributes, and implement the
|
||||||
|
``_get_root_secret`` function.
|
||||||
"""
|
"""
|
||||||
log_route = 'keymaster'
|
@property
|
||||||
keymaster_opts = ()
|
def log_route(self):
|
||||||
keymaster_conf_section = 'keymaster'
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def keymaster_opts(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def keymaster_conf_section(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_root_secret(self, conf):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.logger = get_logger(conf, log_route=self.log_route)
|
self.logger = get_logger(conf, log_route=self.log_route)
|
||||||
self.keymaster_config_path = conf.get('keymaster_config_path')
|
self.keymaster_config_path = conf.get('keymaster_config_path')
|
||||||
if type(self) is KeyMaster:
|
conf = self._load_keymaster_config_file(conf)
|
||||||
self.keymaster_opts = ('encryption_root_secret*',
|
|
||||||
'active_root_secret_id')
|
|
||||||
if self.keymaster_config_path:
|
|
||||||
conf = self._load_keymaster_config_file(conf)
|
|
||||||
|
|
||||||
# The _get_root_secret() function is overridden by other keymasters
|
# The _get_root_secret() function is overridden by other keymasters
|
||||||
# which may historically only return a single value
|
# which may historically only return a single value
|
||||||
@@ -169,6 +178,9 @@ class KeyMaster(object):
|
|||||||
return sorted(self._root_secrets.keys())
|
return sorted(self._root_secrets.keys())
|
||||||
|
|
||||||
def _load_keymaster_config_file(self, conf):
|
def _load_keymaster_config_file(self, conf):
|
||||||
|
if not self.keymaster_config_path:
|
||||||
|
return conf
|
||||||
|
|
||||||
# Keymaster options specified in the filter section would be ignored if
|
# Keymaster options specified in the filter section would be ignored if
|
||||||
# a separate keymaster config file is specified. To avoid confusion,
|
# a separate keymaster config file is specified. To avoid confusion,
|
||||||
# prohibit them existing in the filter section.
|
# prohibit them existing in the filter section.
|
||||||
@@ -185,13 +197,6 @@ class KeyMaster(object):
|
|||||||
return readconf(self.keymaster_config_path,
|
return readconf(self.keymaster_config_path,
|
||||||
self.keymaster_conf_section)
|
self.keymaster_conf_section)
|
||||||
|
|
||||||
def _decode_root_secret(self, b64_root_secret):
|
|
||||||
binary_root_secret = strict_b64decode(b64_root_secret,
|
|
||||||
allow_line_breaks=True)
|
|
||||||
if len(binary_root_secret) < 32:
|
|
||||||
raise ValueError
|
|
||||||
return binary_root_secret
|
|
||||||
|
|
||||||
def _load_multikey_opts(self, conf, prefix):
|
def _load_multikey_opts(self, conf, prefix):
|
||||||
result = []
|
result = []
|
||||||
for k, v in conf.items():
|
for k, v in conf.items():
|
||||||
@@ -203,34 +208,6 @@ class KeyMaster(object):
|
|||||||
result.append((k, suffix[1:] or None, v))
|
result.append((k, suffix[1:] or None, v))
|
||||||
return sorted(result)
|
return sorted(result)
|
||||||
|
|
||||||
def _get_root_secret(self, conf):
|
|
||||||
"""
|
|
||||||
This keymaster requires ``encryption_root_secret[_id]`` options to be
|
|
||||||
set. At least one must be set before first use to a value that is a
|
|
||||||
base64 encoding of at least 32 bytes. The encryption root secrets are
|
|
||||||
specified in either proxy-server.conf, or in an external file
|
|
||||||
referenced from proxy-server.conf using ``keymaster_config_path``.
|
|
||||||
|
|
||||||
:param conf: the keymaster config section from proxy-server.conf
|
|
||||||
:type conf: dict
|
|
||||||
|
|
||||||
:return: a dict mapping secret ids to encryption root secret binary
|
|
||||||
bytes
|
|
||||||
:rtype: dict
|
|
||||||
"""
|
|
||||||
root_secrets = {}
|
|
||||||
for opt, secret_id, value in self._load_multikey_opts(
|
|
||||||
conf, 'encryption_root_secret'):
|
|
||||||
try:
|
|
||||||
secret = self._decode_root_secret(value)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(
|
|
||||||
'%s option in %s must be a base64 encoding of at '
|
|
||||||
'least 32 raw bytes' %
|
|
||||||
(opt, self.keymaster_config_path or 'proxy-server.conf'))
|
|
||||||
root_secrets[secret_id] = secret
|
|
||||||
return root_secrets
|
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
req = Request(env)
|
req = Request(env)
|
||||||
|
|
||||||
@@ -269,6 +246,58 @@ class KeyMaster(object):
|
|||||||
return hmac.new(key, path, digestmod=hashlib.sha256).digest()
|
return hmac.new(key, path, digestmod=hashlib.sha256).digest()
|
||||||
|
|
||||||
|
|
||||||
|
class KeyMaster(BaseKeyMaster):
|
||||||
|
"""Middleware for providing encryption keys.
|
||||||
|
|
||||||
|
The middleware requires its encryption root secret to be set. This is the
|
||||||
|
root secret from which encryption keys are derived. This must be set before
|
||||||
|
first use to a value that is at least 256 bits. The security of all
|
||||||
|
encrypted data critically depends on this key, therefore it should be set
|
||||||
|
to a high-entropy value. For example, a suitable value may be obtained by
|
||||||
|
generating a 32 byte (or longer) value using a cryptographically secure
|
||||||
|
random number generator. Changing the root secret is likely to result in
|
||||||
|
data loss.
|
||||||
|
"""
|
||||||
|
log_route = 'keymaster'
|
||||||
|
keymaster_opts = ('encryption_root_secret*', 'active_root_secret_id')
|
||||||
|
keymaster_conf_section = 'keymaster'
|
||||||
|
|
||||||
|
def _get_root_secret(self, conf):
|
||||||
|
"""
|
||||||
|
This keymaster requires ``encryption_root_secret[_id]`` options to be
|
||||||
|
set. At least one must be set before first use to a value that is a
|
||||||
|
base64 encoding of at least 32 bytes. The encryption root secrets are
|
||||||
|
specified in either proxy-server.conf, or in an external file
|
||||||
|
referenced from proxy-server.conf using ``keymaster_config_path``.
|
||||||
|
|
||||||
|
:param conf: the keymaster config section from proxy-server.conf
|
||||||
|
:type conf: dict
|
||||||
|
|
||||||
|
:return: a dict mapping secret ids to encryption root secret binary
|
||||||
|
bytes
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
root_secrets = {}
|
||||||
|
for opt, secret_id, value in self._load_multikey_opts(
|
||||||
|
conf, 'encryption_root_secret'):
|
||||||
|
try:
|
||||||
|
secret = self._decode_root_secret(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
'%s option in %s must be a base64 encoding of at '
|
||||||
|
'least 32 raw bytes' %
|
||||||
|
(opt, self.keymaster_config_path or 'proxy-server.conf'))
|
||||||
|
root_secrets[secret_id] = secret
|
||||||
|
return root_secrets
|
||||||
|
|
||||||
|
def _decode_root_secret(self, b64_root_secret):
|
||||||
|
binary_root_secret = strict_b64decode(b64_root_secret,
|
||||||
|
allow_line_breaks=True)
|
||||||
|
if len(binary_root_secret) < 32:
|
||||||
|
raise ValueError
|
||||||
|
return binary_root_secret
|
||||||
|
|
||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ example::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class KmipKeyMaster(keymaster.KeyMaster):
|
class KmipKeyMaster(keymaster.BaseKeyMaster):
|
||||||
log_route = 'kmip_keymaster'
|
log_route = 'kmip_keymaster'
|
||||||
keymaster_opts = ('host', 'port', 'certfile', 'keyfile',
|
keymaster_opts = ('host', 'port', 'certfile', 'keyfile',
|
||||||
'ca_certs', 'username', 'password',
|
'ca_certs', 'username', 'password',
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
from castellan import key_manager, options
|
from castellan import key_manager, options
|
||||||
from castellan.common.credentials import keystone_password
|
from castellan.common.credentials import keystone_password
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from swift.common.middleware.crypto.keymaster import KeyMaster
|
from swift.common.middleware.crypto.keymaster import BaseKeyMaster
|
||||||
|
|
||||||
|
|
||||||
class KmsKeyMaster(KeyMaster):
|
class KmsKeyMaster(BaseKeyMaster):
|
||||||
"""Middleware for retrieving a encryption root secret from an external KMS.
|
"""Middleware for retrieving a encryption root secret from an external KMS.
|
||||||
|
|
||||||
The middleware accesses the encryption root secret from an external key
|
The middleware accesses the encryption root secret from an external key
|
||||||
|
|||||||
Reference in New Issue
Block a user