diff --git a/swift/common/middleware/crypto/keymaster.py b/swift/common/middleware/crypto/keymaster.py index 83e63cf3f2..274513c9e8 100644 --- a/swift/common/middleware/crypto/keymaster.py +++ b/swift/common/middleware/crypto/keymaster.py @@ -122,31 +122,40 @@ class KeyMasterContext(WSGIContext): return resp -class KeyMaster(object): - """Middleware for providing encryption keys. +class BaseKeyMaster(object): + """Base middleware for providing encryption keys. - The middleware requires at least one encryption root secret(s) 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. + This provides some basic helpers for: + + - loading from a separate config path, + - deriving keys based on path, and + - installing a ``swift.callback.fetch_crypto_keys`` hook + in the request environment. + + Subclasses should define ``log_route``, ``keymaster_opts``, and + ``keymaster_conf_section`` attributes, and implement the + ``_get_root_secret`` function. """ - log_route = 'keymaster' - keymaster_opts = () - keymaster_conf_section = 'keymaster' + @property + def log_route(self): + 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): self.app = app self.logger = get_logger(conf, log_route=self.log_route) self.keymaster_config_path = conf.get('keymaster_config_path') - if type(self) is KeyMaster: - self.keymaster_opts = ('encryption_root_secret*', - 'active_root_secret_id') - if self.keymaster_config_path: - conf = self._load_keymaster_config_file(conf) + conf = self._load_keymaster_config_file(conf) # The _get_root_secret() function is overridden by other keymasters # which may historically only return a single value @@ -169,6 +178,9 @@ class KeyMaster(object): return sorted(self._root_secrets.keys()) 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 # a separate keymaster config file is specified. To avoid confusion, # prohibit them existing in the filter section. @@ -185,13 +197,6 @@ class KeyMaster(object): return readconf(self.keymaster_config_path, 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): result = [] for k, v in conf.items(): @@ -203,34 +208,6 @@ class KeyMaster(object): result.append((k, suffix[1:] or None, v)) 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): req = Request(env) @@ -269,6 +246,58 @@ class KeyMaster(object): 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): conf = global_conf.copy() conf.update(local_conf) diff --git a/swift/common/middleware/crypto/kmip_keymaster.py b/swift/common/middleware/crypto/kmip_keymaster.py index 4c3540aef4..e1ea989348 100644 --- a/swift/common/middleware/crypto/kmip_keymaster.py +++ b/swift/common/middleware/crypto/kmip_keymaster.py @@ -97,7 +97,7 @@ example:: """ -class KmipKeyMaster(keymaster.KeyMaster): +class KmipKeyMaster(keymaster.BaseKeyMaster): log_route = 'kmip_keymaster' keymaster_opts = ('host', 'port', 'certfile', 'keyfile', 'ca_certs', 'username', 'password', diff --git a/swift/common/middleware/crypto/kms_keymaster.py b/swift/common/middleware/crypto/kms_keymaster.py index 4e936122f2..7465a1e85b 100644 --- a/swift/common/middleware/crypto/kms_keymaster.py +++ b/swift/common/middleware/crypto/kms_keymaster.py @@ -15,10 +15,10 @@ from castellan import key_manager, options from castellan.common.credentials import keystone_password 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. The middleware accesses the encryption root secret from an external key