encryptors: Introduce encryption provider constants

These constants detail the supported encryption formats and their
associated in tree encryption provider implementations.

The use of out of tree and direct use of these in tree implementations
is now deprecated and will be blocked in the 16.0.0 Pike release of
Nova.

Change-Id: Ic155bd29d46059832cce970bf60375e7e472eca6
Partial-bug: #1639293
This commit is contained in:
Lee Yarwood
2016-11-04 18:28:47 +00:00
parent 80306af88b
commit cbbc218282
2 changed files with 101 additions and 35 deletions

View File

@@ -41,43 +41,57 @@ class VolumeEncryptorTestCase(test.NoDBTestCase):
} }
self.encryptor = self._create(self.connection_info) self.encryptor = self._create(self.connection_info)
class VolumeEncryptorInitTestCase(VolumeEncryptorTestCase):
def setUp(self):
super(VolumeEncryptorInitTestCase, self).setUp()
def _test_get_encryptor(self, provider, expected_provider_class):
encryption = {'control_location': 'front-end',
'provider': provider}
encryptor = encryptors.get_volume_encryptor(self.connection_info,
**encryption)
self.assertIsInstance(encryptor, expected_provider_class)
def test_get_encryptors(self): def test_get_encryptors(self):
encryption = {'control_location': 'front-end',
'provider': 'LuksEncryptor'}
encryptor = encryptors.get_volume_encryptor(self.connection_info,
**encryption)
self.assertIsInstance(encryptor,
luks.LuksEncryptor,
"encryptor is not an instance of LuksEncryptor")
encryption = {'control_location': 'front-end', self._test_get_encryptor('luks',
'provider': 'CryptsetupEncryptor'} luks.LuksEncryptor)
encryptor = encryptors.get_volume_encryptor(self.connection_info, # TODO(lyarwood): Remove the following in 16.0.0 Pike
**encryption) self._test_get_encryptor('LuksEncryptor',
self.assertIsInstance(encryptor, luks.LuksEncryptor)
cryptsetup.CryptsetupEncryptor, self._test_get_encryptor('nova.volume.encryptors.luks.LuksEncryptor',
"encryptor is not an instance of" luks.LuksEncryptor)
"CryptsetupEncryptor")
encryption = {'control_location': 'front-end', self._test_get_encryptor('plain',
'provider': 'NoOpEncryptor'} cryptsetup.CryptsetupEncryptor)
encryptor = encryptors.get_volume_encryptor(self.connection_info, # TODO(lyarwood): Remove the following in 16.0.0 Pike
**encryption) self._test_get_encryptor('CryptsetupEncryptor',
self.assertIsInstance(encryptor, cryptsetup.CryptsetupEncryptor)
nop.NoOpEncryptor, self._test_get_encryptor(
"encryptor is not an instance of NoOpEncryptor") 'nova.volume.encryptors.cryptsetup.CryptsetupEncryptor',
cryptsetup.CryptsetupEncryptor)
def test_get_error_encryptos(self): self._test_get_encryptor(None,
nop.NoOpEncryptor)
# TODO(lyarwood): Remove the following in 16.0.0 Pike
self._test_get_encryptor('NoOpEncryptor',
nop.NoOpEncryptor)
self._test_get_encryptor('nova.volume.encryptors.nop.NoOpEncryptor',
nop.NoOpEncryptor)
def test_get_missing_encryptor_error(self):
encryption = {'control_location': 'front-end', encryption = {'control_location': 'front-end',
'provider': 'ErrorEncryptor'} 'provider': 'ErrorEncryptor'}
self.assertRaises(ValueError, encryptors.get_volume_encryptor, self.assertRaises(ValueError, encryptors.get_volume_encryptor,
self.connection_info, **encryption) self.connection_info, **encryption)
@mock.patch('nova.volume.encryptors.LOG') @mock.patch('nova.volume.encryptors.LOG')
def test_error_log(self, log): def test_get_missing_out_of_tree_encryptor_log(self, log):
encryption = {'control_location': 'front-end',
'provider': 'TestEncryptor'}
provider = 'TestEncryptor' provider = 'TestEncryptor'
encryption = {'control_location': 'front-end',
'provider': provider}
try: try:
encryptors.get_volume_encryptor(self.connection_info, **encryption) encryptors.get_volume_encryptor(self.connection_info, **encryption)
except Exception as e: except Exception as e:
@@ -86,3 +100,29 @@ class VolumeEncryptorTestCase(test.NoDBTestCase):
"%(exception)s", "%(exception)s",
{'provider': provider, {'provider': provider,
'exception': e}) 'exception': e})
log.warning.assert_called_once_with("Use of the out of tree "
"encryptor class %(provider)s "
"will be blocked with the "
"16.0.0 Pike release of Nova.",
{'provider': provider})
@mock.patch('nova.volume.encryptors.LOG')
def test_get_direct_encryptor_log(self, log):
encryption = {'control_location': 'front-end',
'provider': 'LuksEncryptor'}
encryptors.get_volume_encryptor(self.connection_info, **encryption)
encryption = {'control_location': 'front-end',
'provider': 'nova.volume.encryptors.luks.LuksEncryptor'}
encryptors.get_volume_encryptor(self.connection_info, **encryption)
log.warning.assert_has_calls([
mock.call("Use of the in tree encryptor class %(provider)s by "
"directly referencing the implementation class will be "
"blocked in the 16.0.0 Pike release of Nova.",
{'provider': 'LuksEncryptor'}),
mock.call("Use of the in tree encryptor class %(provider)s by "
"directly referencing the implementation class will be "
"blocked in the 16.0.0 Pike release of Nova.",
{'provider':
'nova.volume.encryptors.luks.LuksEncryptor'})])

View File

@@ -19,11 +19,27 @@ from oslo_utils import importutils
from oslo_utils import strutils from oslo_utils import strutils
from nova.i18n import _LE, _LW from nova.i18n import _LE, _LW
from nova.volume.encryptors import nop
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LUKS = "luks"
PLAIN = "plain"
FORMAT_TO_FRONTEND_ENCRYPTOR_MAP = {
LUKS: 'nova.volume.encryptors.luks.LuksEncryptor',
PLAIN: 'nova.volume.encryptors.cryptsetup.CryptsetupEncryptor'
}
LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP = {
"nova.volume.encryptors.luks.LuksEncryptor": LUKS,
"nova.volume.encryptors.cryptsetup.CryptsetupEncryptor": PLAIN,
"nova.volume.encryptors.nop.NoopEncryptor": None,
"LuksEncryptor": LUKS,
"CryptsetupEncryptor": PLAIN,
"NoOpEncryptor": None,
}
def get_volume_encryptor(connection_info, **kwargs): def get_volume_encryptor(connection_info, **kwargs):
"""Creates a VolumeEncryptor used to encrypt the specified volume. """Creates a VolumeEncryptor used to encrypt the specified volume.
@@ -31,18 +47,28 @@ def get_volume_encryptor(connection_info, **kwargs):
:param: the connection information used to attach the volume :param: the connection information used to attach the volume
:returns VolumeEncryptor: the VolumeEncryptor for the volume :returns VolumeEncryptor: the VolumeEncryptor for the volume
""" """
encryptor = nop.NoOpEncryptor(connection_info, **kwargs)
location = kwargs.get('control_location', None) location = kwargs.get('control_location', None)
if location and location.lower() == 'front-end': # case insensitive if location and location.lower() == 'front-end': # case insensitive
provider = kwargs.get('provider') provider = kwargs.get('provider')
if provider == 'LuksEncryptor': # TODO(lyarwood): Remove the following in 16.0.0 Pike and raise an
provider = 'nova.volume.encryptors.luks.' + provider # ERROR if provider is not a key in SUPPORTED_ENCRYPTION_PROVIDERS.
elif provider == 'CryptsetupEncryptor': # Until then continue to allow both the class name and path to be used.
provider = 'nova.volume.encryptors.cryptsetup.' + provider if provider in LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP:
elif provider == 'NoOpEncryptor': LOG.warning(_LW("Use of the in tree encryptor class %(provider)s"
provider = 'nova.volume.encryptors.nop.' + provider " by directly referencing the implementation class"
" will be blocked in the 16.0.0 Pike release of "
"Nova."), {'provider': provider})
provider = LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP[provider]
if provider in FORMAT_TO_FRONTEND_ENCRYPTOR_MAP:
provider = FORMAT_TO_FRONTEND_ENCRYPTOR_MAP[provider]
elif provider is None:
provider = "nova.volume.encryptors.nop.NoOpEncryptor"
else:
LOG.warning(_LW("Use of the out of tree encryptor class "
"%(provider)s will be blocked with the 16.0.0 "
"Pike release of Nova."), {'provider': provider})
try: try:
encryptor = importutils.import_object(provider, connection_info, encryptor = importutils.import_object(provider, connection_info,
**kwargs) **kwargs)