diff --git a/nova/tests/unit/volume/encryptors/test_cryptsetup.py b/nova/tests/unit/volume/encryptors/test_cryptsetup.py index e524f40d8..53eb1098c 100644 --- a/nova/tests/unit/volume/encryptors/test_cryptsetup.py +++ b/nova/tests/unit/volume/encryptors/test_cryptsetup.py @@ -19,16 +19,17 @@ import copy from castellan.common.objects import symmetric_key as key import mock +from oslo_concurrency import processutils import six +import uuid from nova import exception from nova.tests.unit.volume.encryptors import test_base from nova.volume.encryptors import cryptsetup -def fake__get_key(context): - raw = bytes(binascii.unhexlify('0' * 32)) - +def fake__get_key(context, passphrase): + raw = bytes(binascii.unhexlify(passphrase)) symmetric_key = key.SymmetricKey('AES', len(raw) * 8, raw) return symmetric_key @@ -59,14 +60,15 @@ class CryptsetupEncryptorTestCase(test_base.VolumeEncryptorTestCase): @mock.patch('nova.utils.execute') def test_attach_volume(self, mock_execute): + fake_key = uuid.uuid4().hex self.encryptor._get_key = mock.MagicMock() - self.encryptor._get_key.return_value = fake__get_key(None) + self.encryptor._get_key.return_value = fake__get_key(None, fake_key) self.encryptor.attach_volume(None) mock_execute.assert_has_calls([ mock.call('cryptsetup', 'create', '--key-file=-', self.dev_name, - self.dev_path, process_input='0' * 32, + self.dev_path, process_input=fake_key, run_as_root=True, check_exit_code=True), mock.call('ln', '--symbolic', '--force', '/dev/mapper/%s' % self.dev_name, self.symlink_path, @@ -138,3 +140,31 @@ class CryptsetupEncryptorTestCase(test_base.VolumeEncryptorTestCase): mock.call('/dev/mapper/%s' % wwn)]) mock_execute.assert_called_once_with( 'cryptsetup', 'status', wwn, run_as_root=True) + + @mock.patch('nova.utils.execute') + def test_attach_volume_unmangle_passphrase(self, mock_execute): + fake_key = '0725230b' + fake_key_mangled = '72523b' + self.encryptor._get_key = mock.MagicMock() + self.encryptor._get_key.return_value = fake__get_key(None, fake_key) + + mock_execute.side_effect = [ + processutils.ProcessExecutionError(exit_code=2), # luksOpen + mock.DEFAULT, + mock.DEFAULT, + ] + + self.encryptor.attach_volume(None) + + mock_execute.assert_has_calls([ + mock.call('cryptsetup', 'create', '--key-file=-', self.dev_name, + self.dev_path, process_input=fake_key, + run_as_root=True, check_exit_code=True), + mock.call('cryptsetup', 'create', '--key-file=-', self.dev_name, + self.dev_path, process_input=fake_key_mangled, + run_as_root=True, check_exit_code=True), + mock.call('ln', '--symbolic', '--force', + '/dev/mapper/%s' % self.dev_name, self.symlink_path, + run_as_root=True, check_exit_code=True), + ]) + self.assertEqual(3, mock_execute.call_count) diff --git a/nova/volume/encryptors/cryptsetup.py b/nova/volume/encryptors/cryptsetup.py index fa690db6c..62614a847 100644 --- a/nova/volume/encryptors/cryptsetup.py +++ b/nova/volume/encryptors/cryptsetup.py @@ -14,6 +14,7 @@ # under the License. +import array import binascii import os @@ -21,7 +22,7 @@ from oslo_concurrency import processutils from oslo_log import log as logging from nova import exception -from nova.i18n import _LW +from nova.i18n import _LW, _LI from nova import utils from nova.volume.encryptors import base @@ -119,6 +120,17 @@ class CryptsetupEncryptor(base.VolumeEncryptor): utils.execute(*cmd, process_input=passphrase, check_exit_code=True, run_as_root=True) + def _get_mangled_passphrase(self, key): + """Convert the raw key into a list of unsigned int's and then a string + """ + # NOTE(lyarwood): This replicates the methods used prior to Newton to + # first encode the passphrase as a list of unsigned int's before + # decoding back into a string. This method strips any leading 0's + # of the resulting hex digit pairs, resulting in a different + # passphrase being returned. + encoded_key = array.array('B', key).tolist() + return ''.join(hex(x).replace('0x', '') for x in encoded_key) + def attach_volume(self, context, **kwargs): """Shadows the device and passes an unencrypted version to the instance. @@ -132,7 +144,16 @@ class CryptsetupEncryptor(base.VolumeEncryptor): key = self._get_key(context).get_encoded() passphrase = self._get_passphrase(key) - self._open_volume(passphrase, **kwargs) + try: + self._open_volume(passphrase, **kwargs) + except processutils.ProcessExecutionError as e: + if e.exit_code == 2: + # NOTE(lyarwood): Workaround bug#1633518 by attempting to use + # a mangled passphrase to open the device.. + LOG.info(_LI("Unable to open %s with the current passphrase, " + "attempting to use a mangled passphrase to open " + "the volume."), self.dev_path) + self._open_volume(self._get_mangled_passphrase(key), **kwargs) # modify the original symbolic link to refer to the decrypted device utils.execute('ln', '--symbolic', '--force',