From 9c23cdc247770830fa288f429ca7231eb431a3b2 Mon Sep 17 00:00:00 2001 From: Lee Yarwood Date: Sat, 29 Oct 2016 12:05:46 +0100 Subject: [PATCH] encryptors: Switch to os-brick encryptor classes This change drops the encryptor classes and supporting code from the codebase in favor of the classes provided by os-brick. This is made possible by the following os-brick change that introduced new encryption provider constants during Ocata : Ic155bd29d46059832cce970bf60375e7e472eca6 Thanks to the following bugfix also released as part of 1.11.0 for Ocata the constants present in os-brick also support the use of the deprecated legacy class paths from Nova, for example nova.volume.encryptors.luks.LuksEncryptor, while using the os-brick provided classes : I3ec6e3fe919bc03d158da04a18fb8b651002ed52 Implements: blueprint switch-to-os-brick-encryptor-classes Change-Id: I37ffc90c0bd57029fced251b5cfd7cd4318a0292 Depends-On: Iae12605dc7d0607e78020a24b5b8801606c2f169 --- etc/nova/rootwrap.d/compute.filters | 8 - nova/tests/unit/virt/libvirt/test_driver.py | 7 +- nova/tests/unit/virt/test_block_device.py | 2 +- nova/tests/unit/volume/encryptors/__init__.py | 0 .../tests/unit/volume/encryptors/test_base.py | 128 --------- .../unit/volume/encryptors/test_cryptsetup.py | 170 ------------ .../tests/unit/volume/encryptors/test_luks.py | 242 ------------------ nova/tests/unit/volume/encryptors/test_nop.py | 28 -- nova/virt/block_device.py | 2 +- nova/virt/libvirt/driver.py | 12 +- nova/volume/encryptors/__init__.py | 111 -------- nova/volume/encryptors/base.py | 56 ---- nova/volume/encryptors/cryptsetup.py | 174 ------------- nova/volume/encryptors/luks.py | 168 ------------ nova/volume/encryptors/nop.py | 31 --- 15 files changed, 14 insertions(+), 1125 deletions(-) delete mode 100644 nova/tests/unit/volume/encryptors/__init__.py delete mode 100644 nova/tests/unit/volume/encryptors/test_base.py delete mode 100644 nova/tests/unit/volume/encryptors/test_cryptsetup.py delete mode 100644 nova/tests/unit/volume/encryptors/test_luks.py delete mode 100644 nova/tests/unit/volume/encryptors/test_nop.py delete mode 100644 nova/volume/encryptors/__init__.py delete mode 100644 nova/volume/encryptors/base.py delete mode 100644 nova/volume/encryptors/cryptsetup.py delete mode 100644 nova/volume/encryptors/luks.py delete mode 100644 nova/volume/encryptors/nop.py diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index e4718486b722..f4a774c6b675 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -225,14 +225,6 @@ privsep-rootwrap: RegExpFilter, privsep-helper, root, privsep-helper, --config-f # nova/storage/linuxscsi.py: sg_scan device sg_scan: CommandFilter, sg_scan, root -# nova/volume/encryptors/cryptsetup.py: -# nova/volume/encryptors/luks.py: -ln: RegExpFilter, ln, root, ln, --symbolic, --force, /dev/mapper/crypt-.+, .+ - -# nova/volume/encryptors.py: -# nova/virt/libvirt/dmcrypt.py: -cryptsetup: CommandFilter, cryptsetup, root - # nova/virt/xenapi/vm_utils.py: xenstore-read: CommandFilter, xenstore-read, root diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 7ed44e367d7f..ce7467468585 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -35,6 +35,7 @@ import fixtures from lxml import etree import mock from mox3 import mox +from os_brick import encryptors from os_brick import exception as brick_exception from os_brick.initiator import connector import os_vif @@ -6423,7 +6424,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): mock_get_domain.assert_called_once_with(instance) @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume') - @mock.patch('nova.volume.encryptors.get_volume_encryptor') + @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_volume_encryptor') @mock.patch('nova.virt.libvirt.host.Host.get_guest') def test_detach_volume_order_with_encryptors(self, mock_get_guest, mock_get_encryptor, mock_disconnect_volume): @@ -6432,7 +6433,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): mock_guest.get_power_state.return_value = power_state.RUNNING mock_get_guest.return_value = mock_guest mock_encryptor = mock.MagicMock( - spec=nova.volume.encryptors.nop.NoOpEncryptor) + spec=encryptors.nop.NoOpEncryptor) mock_get_encryptor.return_value = mock_encryptor mock_order = mock.Mock() @@ -14591,7 +14592,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): block_device_info=None, destroy_disks=True) self.assertTrue(guest.poweroff.called) - @mock.patch('nova.volume.encryptors.get_encryption_metadata') + @mock.patch('os_brick.encryptors.get_encryption_metadata') @mock.patch('nova.virt.libvirt.blockinfo.get_info_from_bdm') def test_create_with_bdm(self, get_info_from_bdm, get_encryption_metadata): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) diff --git a/nova/tests/unit/virt/test_block_device.py b/nova/tests/unit/virt/test_block_device.py index d3c0f0a40576..6370f71c127f 100644 --- a/nova/tests/unit/virt/test_block_device.py +++ b/nova/tests/unit/virt/test_block_device.py @@ -13,6 +13,7 @@ # under the License. import mock +from os_brick import encryptors from oslo_serialization import jsonutils from nova import block_device @@ -28,7 +29,6 @@ from nova.tests import uuidsentinel as uuids from nova.virt import block_device as driver_block_device from nova.virt import driver from nova.volume import cinder -from nova.volume import encryptors class TestDriverBlockDevice(test.NoDBTestCase): diff --git a/nova/tests/unit/volume/encryptors/__init__.py b/nova/tests/unit/volume/encryptors/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/nova/tests/unit/volume/encryptors/test_base.py b/nova/tests/unit/volume/encryptors/test_base.py deleted file mode 100644 index 17235aac2d82..000000000000 --- a/nova/tests/unit/volume/encryptors/test_base.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -from nova import test -from nova.tests.unit.keymgr import fake -from nova.volume import encryptors -from nova.volume.encryptors import cryptsetup -from nova.volume.encryptors import luks -from nova.volume.encryptors import nop - - -class VolumeEncryptorTestCase(test.NoDBTestCase): - def _create(self, device_path): - pass - - def setUp(self): - super(VolumeEncryptorTestCase, self).setUp() - - self.stub_out('nova.keymgr.API', fake.fake_api) - - self.connection_info = { - "data": { - "device_path": "/dev/disk/by-path/" - "ip-192.0.2.0:3260-iscsi-iqn.2010-10.org.openstack" - ":volume-fake_uuid-lun-1", - }, - } - 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): - - self._test_get_encryptor('luks', - luks.LuksEncryptor) - # TODO(lyarwood): Remove the following in 16.0.0 Pike - self._test_get_encryptor('LuksEncryptor', - luks.LuksEncryptor) - self._test_get_encryptor('nova.volume.encryptors.luks.LuksEncryptor', - luks.LuksEncryptor) - - self._test_get_encryptor('plain', - cryptsetup.CryptsetupEncryptor) - # TODO(lyarwood): Remove the following in 16.0.0 Pike - self._test_get_encryptor('CryptsetupEncryptor', - cryptsetup.CryptsetupEncryptor) - self._test_get_encryptor( - 'nova.volume.encryptors.cryptsetup.CryptsetupEncryptor', - cryptsetup.CryptsetupEncryptor) - - 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', - 'provider': 'ErrorEncryptor'} - self.assertRaises(ValueError, encryptors.get_volume_encryptor, - self.connection_info, **encryption) - - @mock.patch('nova.volume.encryptors.LOG') - def test_get_missing_out_of_tree_encryptor_log(self, log): - provider = 'TestEncryptor' - encryption = {'control_location': 'front-end', - 'provider': provider} - try: - encryptors.get_volume_encryptor(self.connection_info, **encryption) - except Exception as e: - log.error.assert_called_once_with("Error instantiating " - "%(provider)s: " - "%(exception)s", - {'provider': provider, - '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'})]) diff --git a/nova/tests/unit/volume/encryptors/test_cryptsetup.py b/nova/tests/unit/volume/encryptors/test_cryptsetup.py deleted file mode 100644 index 4ecd79d8ec35..000000000000 --- a/nova/tests/unit/volume/encryptors/test_cryptsetup.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import binascii -import copy -import uuid - -from castellan.common.objects import symmetric_key as key -import mock -from oslo_concurrency import processutils -import six - -from nova import exception -from nova.tests.unit.volume.encryptors import test_base -from nova.volume.encryptors import cryptsetup - - -def fake__get_key(context, passphrase): - raw = bytes(binascii.unhexlify(passphrase)) - symmetric_key = key.SymmetricKey('AES', len(raw) * 8, raw) - return symmetric_key - - -class CryptsetupEncryptorTestCase(test_base.VolumeEncryptorTestCase): - @mock.patch('os.path.exists', return_value=False) - def _create(self, connection_info, mock_exists): - return cryptsetup.CryptsetupEncryptor(connection_info) - - def setUp(self): - super(CryptsetupEncryptorTestCase, self).setUp() - - self.dev_path = self.connection_info['data']['device_path'] - self.dev_name = 'crypt-%s' % self.dev_path.split('/')[-1] - - self.symlink_path = self.dev_path - - @mock.patch('nova.utils.execute') - def test__open_volume(self, mock_execute): - self.encryptor._open_volume("passphrase") - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'create', '--key-file=-', self.dev_name, - self.dev_path, process_input='passphrase', - run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(1, mock_execute.call_count) - - @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, 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=fake_key, - 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(2, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test__close_volume(self, mock_execute): - self.encryptor.detach_volume() - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'remove', self.dev_name, - run_as_root=True, check_exit_code=[0, 4]), - ]) - self.assertEqual(1, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test_detach_volume(self, mock_execute): - self.encryptor.detach_volume() - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'remove', self.dev_name, - run_as_root=True, check_exit_code=[0, 4]), - ]) - self.assertEqual(1, mock_execute.call_count) - - def test_init_volume_encryption_not_supported(self): - # Tests that creating a CryptsetupEncryptor fails if there is no - # device_path key. - type = 'unencryptable' - data = dict(volume_id='a194699b-aa07-4433-a945-a5d23802043e') - connection_info = dict(driver_volume_type=type, data=data) - exc = self.assertRaises(exception.VolumeEncryptionNotSupported, - cryptsetup.CryptsetupEncryptor, - connection_info) - self.assertIn(type, six.text_type(exc)) - - @mock.patch('os.path.exists', return_value=True) - @mock.patch('nova.utils.execute') - def test_init_volume_encryption_with_old_name(self, mock_execute, - mock_exists): - # If an old name crypt device exists, dev_path should be the old name. - old_dev_name = self.dev_path.split('/')[-1] - encryptor = cryptsetup.CryptsetupEncryptor(self.connection_info) - self.assertFalse(encryptor.dev_name.startswith('crypt-')) - self.assertEqual(old_dev_name, encryptor.dev_name) - self.assertEqual(self.dev_path, encryptor.dev_path) - self.assertEqual(self.symlink_path, encryptor.symlink_path) - mock_exists.assert_called_once_with('/dev/mapper/%s' % old_dev_name) - mock_execute.assert_called_once_with( - 'cryptsetup', 'status', old_dev_name, run_as_root=True) - - @mock.patch('os.path.exists', side_effect=[False, True]) - @mock.patch('nova.utils.execute') - def test_init_volume_encryption_with_wwn(self, mock_execute, mock_exists): - # If an wwn name crypt device exists, dev_path should be based on wwn. - old_dev_name = self.dev_path.split('/')[-1] - wwn = 'fake_wwn' - connection_info = copy.deepcopy(self.connection_info) - connection_info['data']['multipath_id'] = wwn - encryptor = cryptsetup.CryptsetupEncryptor(connection_info) - self.assertFalse(encryptor.dev_name.startswith('crypt-')) - self.assertEqual(wwn, encryptor.dev_name) - self.assertEqual(self.dev_path, encryptor.dev_path) - self.assertEqual(self.symlink_path, encryptor.symlink_path) - mock_exists.assert_has_calls([ - mock.call('/dev/mapper/%s' % old_dev_name), - 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/tests/unit/volume/encryptors/test_luks.py b/nova/tests/unit/volume/encryptors/test_luks.py deleted file mode 100644 index dea67176dc24..000000000000 --- a/nova/tests/unit/volume/encryptors/test_luks.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import binascii -import uuid - -from castellan.common.objects import symmetric_key as key -import mock -from oslo_concurrency import processutils - -from nova.tests.unit.volume.encryptors import test_cryptsetup -from nova.volume.encryptors import luks - - -class LuksEncryptorTestCase(test_cryptsetup.CryptsetupEncryptorTestCase): - def _create(self, connection_info): - return luks.LuksEncryptor(connection_info) - - @mock.patch('nova.utils.execute') - def test_is_luks(self, mock_execute): - luks.is_luks(self.dev_path) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'isLuks', '--verbose', self.dev_path, - run_as_root=True, check_exit_code=True), - ], any_order=False) - self.assertEqual(1, mock_execute.call_count) - - @mock.patch('nova.volume.encryptors.luks.LOG') - @mock.patch('nova.utils.execute') - def test_is_luks_with_error(self, mock_execute, mock_log): - error_msg = "Device %s is not a valid LUKS device." % self.dev_path - mock_execute.side_effect = \ - processutils.ProcessExecutionError(exit_code=1, - stderr=error_msg) - - luks.is_luks(self.dev_path) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'isLuks', '--verbose', self.dev_path, - run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(1, mock_execute.call_count) - - self.assertEqual(1, mock_log.warning.call_count) # warning logged - - @mock.patch('nova.utils.execute') - def test__format_volume(self, mock_execute): - self.encryptor._format_volume("passphrase") - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', '--batch-mode', 'luksFormat', - '--key-file=-', self.dev_path, - process_input='passphrase', - run_as_root=True, check_exit_code=True, attempts=3), - ]) - self.assertEqual(1, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test__open_volume(self, mock_execute): - self.encryptor._open_volume("passphrase") - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input='passphrase', - run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(1, mock_execute.call_count) - - @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 = test_cryptsetup.fake__get_key( - None, fake_key) - - self.encryptor.attach_volume(None) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, 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, - run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(2, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test_attach_volume_not_formatted(self, mock_execute): - fake_key = uuid.uuid4().hex - self.encryptor._get_key = mock.MagicMock() - self.encryptor._get_key.return_value = test_cryptsetup.fake__get_key( - None, fake_key) - - mock_execute.side_effect = [ - processutils.ProcessExecutionError(exit_code=1), # luksOpen - processutils.ProcessExecutionError(exit_code=1), # isLuks - mock.DEFAULT, # luksFormat - mock.DEFAULT, # luksOpen - mock.DEFAULT, # ln - ] - - self.encryptor.attach_volume(None) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input=fake_key, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'isLuks', '--verbose', self.dev_path, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', '--batch-mode', 'luksFormat', - '--key-file=-', self.dev_path, process_input=fake_key, - run_as_root=True, check_exit_code=True, attempts=3), - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, 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, - run_as_root=True, check_exit_code=True), - ], any_order=False) - self.assertEqual(5, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test_attach_volume_fail(self, mock_execute): - fake_key = uuid.uuid4().hex - self.encryptor._get_key = mock.MagicMock() - self.encryptor._get_key.return_value = test_cryptsetup.fake__get_key( - None, fake_key) - - mock_execute.side_effect = [ - processutils.ProcessExecutionError(exit_code=1), # luksOpen - mock.DEFAULT, # isLuks - ] - - self.assertRaises(processutils.ProcessExecutionError, - self.encryptor.attach_volume, None) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input=fake_key, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'isLuks', '--verbose', self.dev_path, - run_as_root=True, check_exit_code=True), - ], any_order=False) - self.assertEqual(2, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test__close_volume(self, mock_execute): - self.encryptor.detach_volume() - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksClose', self.dev_name, - attempts=3, run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(1, mock_execute.call_count) - - @mock.patch('nova.utils.execute') - def test_detach_volume(self, mock_execute): - self.encryptor.detach_volume() - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksClose', self.dev_name, - attempts=3, run_as_root=True, check_exit_code=True), - ]) - self.assertEqual(1, mock_execute.call_count) - - def test_get_mangled_passphrase(self): - # Confirm that a mangled passphrase is provided as per bug#1633518 - unmangled_raw_key = bytes(binascii.unhexlify('0725230b')) - symmetric_key = key.SymmetricKey('AES', len(unmangled_raw_key) * 8, - unmangled_raw_key) - unmangled_encoded_key = symmetric_key.get_encoded() - encryptor = luks.LuksEncryptor(self.connection_info) - self.assertEqual(encryptor._get_mangled_passphrase( - unmangled_encoded_key), '72523b') - - @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(name='mock_execute') - self.encryptor._get_key.return_value = \ - test_cryptsetup.fake__get_key(None, fake_key) - - mock_execute.side_effect = [ - processutils.ProcessExecutionError(exit_code=2), # luksOpen - mock.DEFAULT, # luksOpen - mock.DEFAULT, # luksClose - mock.DEFAULT, # luksAddKey - mock.DEFAULT, # luksOpen - mock.DEFAULT, # luksClose - mock.DEFAULT, # luksRemoveKey - mock.DEFAULT, # luksOpen - mock.DEFAULT, # ln - ] - - self.encryptor.attach_volume(None) - - mock_execute.assert_has_calls([ - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input=fake_key, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input=fake_key_mangled, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'luksClose', self.dev_name, - run_as_root=True, check_exit_code=True, attempts=3), - mock.call('cryptsetup', 'luksAddKey', self.dev_path, - process_input=''.join([fake_key_mangled, - '\n', fake_key, - '\n', fake_key]), - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, process_input=fake_key, - run_as_root=True, check_exit_code=True), - mock.call('cryptsetup', 'luksClose', self.dev_name, - run_as_root=True, check_exit_code=True, attempts=3), - mock.call('cryptsetup', 'luksRemoveKey', self.dev_path, - process_input=fake_key_mangled, run_as_root=True, - check_exit_code=True), - mock.call('cryptsetup', 'luksOpen', '--key-file=-', self.dev_path, - self.dev_name, 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, - run_as_root=True, check_exit_code=True), - ], any_order=False) - self.assertEqual(9, mock_execute.call_count) diff --git a/nova/tests/unit/volume/encryptors/test_nop.py b/nova/tests/unit/volume/encryptors/test_nop.py deleted file mode 100644 index aa32a9c0e6ad..000000000000 --- a/nova/tests/unit/volume/encryptors/test_nop.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from nova.tests.unit.volume.encryptors import test_base -from nova.volume.encryptors import nop - - -class NoOpEncryptorTestCase(test_base.VolumeEncryptorTestCase): - def _create(self, connection_info): - return nop.NoOpEncryptor(connection_info) - - def test_attach_volume(self): - self.encryptor.attach_volume(None) - - def test_detach_volume(self): - self.encryptor.detach_volume() diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py index 521c3e463cae..59f9225d8206 100644 --- a/nova/virt/block_device.py +++ b/nova/virt/block_device.py @@ -15,6 +15,7 @@ import functools import itertools +from os_brick import encryptors from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils @@ -26,7 +27,6 @@ from nova import exception from nova.i18n import _LE from nova.i18n import _LI from nova.i18n import _LW -from nova.volume import encryptors CONF = nova.conf.CONF diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 8ee14cbc1834..1536b2dab0c0 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -44,6 +44,7 @@ import eventlet from eventlet import greenthread from eventlet import tpool from lxml import etree +from os_brick import encryptors from os_brick import exception as brick_exception from os_brick.initiator import connector from oslo_concurrency import processutils @@ -74,6 +75,7 @@ from nova.i18n import _LE from nova.i18n import _LI from nova.i18n import _LW from nova import image +from nova import keymgr from nova.network import model as network_model from nova import objects from nova.objects import fields @@ -109,7 +111,6 @@ from nova.virt.libvirt import vif as libvirt_vif from nova.virt.libvirt.volume import remotefs from nova.virt import netutils from nova.volume import cinder -from nova.volume import encryptors libvirt = None @@ -1164,9 +1165,12 @@ class LibvirtDriver(driver.ComputeDriver): return vol_driver.get_config(connection_info, disk_info) def _get_volume_encryptor(self, connection_info, encryption): - encryptor = encryptors.get_volume_encryptor(connection_info, - **encryption) - return encryptor + root_helper = utils.get_root_helper() + key_manager = keymgr.API(CONF) + return encryptors.get_volume_encryptor(root_helper=root_helper, + keymgr=key_manager, + connection_info=connection_info, + **encryption) def _check_discard_for_attach_volume(self, conf, instance): """Perform some checks for volumes configured for discard support. diff --git a/nova/volume/encryptors/__init__.py b/nova/volume/encryptors/__init__.py deleted file mode 100644 index f677de90ca3e..000000000000 --- a/nova/volume/encryptors/__init__.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -from oslo_log import log as logging -from oslo_utils import importutils -from oslo_utils import strutils - -from nova.i18n import _LE, _LW - - -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): - """Creates a VolumeEncryptor used to encrypt the specified volume. - - :param: the connection information used to attach the volume - :returns VolumeEncryptor: the VolumeEncryptor for the volume - """ - location = kwargs.get('control_location', None) - if location and location.lower() == 'front-end': # case insensitive - provider = kwargs.get('provider') - - # TODO(lyarwood): Remove the following in 16.0.0 Pike and raise an - # ERROR if provider is not a key in SUPPORTED_ENCRYPTION_PROVIDERS. - # Until then continue to allow both the class name and path to be used. - if provider in LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP: - LOG.warning(_LW("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': 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: - encryptor = importutils.import_object(provider, connection_info, - **kwargs) - except Exception as e: - LOG.error(_LE("Error instantiating %(provider)s: %(exception)s"), - {'provider': provider, 'exception': e}) - raise - - msg = ("Using volume encryptor '%(encryptor)s' for connection: " - "%(connection_info)s" % - {'encryptor': encryptor, 'connection_info': connection_info}) - LOG.debug(strutils.mask_password(msg)) - - return encryptor - - -def get_encryption_metadata(context, volume_api, volume_id, connection_info): - metadata = {} - if ('data' in connection_info and - connection_info['data'].get('encrypted', False)): - try: - metadata = volume_api.get_volume_encryption_metadata(context, - volume_id) - if not metadata: - LOG.warning(_LW( - 'Volume %s should be encrypted but there is no ' - 'encryption metadata.'), volume_id) - except Exception as e: - LOG.error(_LE("Failed to retrieve encryption metadata for " - "volume %(volume_id)s: %(exception)s"), - {'volume_id': volume_id, 'exception': e}) - raise - - if metadata: - msg = ("Using volume encryption metadata '%(metadata)s' for " - "connection: %(connection_info)s" % - {'metadata': metadata, 'connection_info': connection_info}) - LOG.debug(strutils.mask_password(msg)) - - return metadata diff --git a/nova/volume/encryptors/base.py b/nova/volume/encryptors/base.py deleted file mode 100644 index caa9a0f4f031..000000000000 --- a/nova/volume/encryptors/base.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import abc - -import six - -from nova import keymgr - - -@six.add_metaclass(abc.ABCMeta) -class VolumeEncryptor(object): - """Base class to support encrypted volumes. - - A VolumeEncryptor provides hooks for attaching and detaching volumes, which - are called immediately prior to attaching the volume to an instance and - immediately following detaching the volume from an instance. This class - performs no actions for either hook. - """ - - def __init__(self, connection_info, **kwargs): - self._key_manager = keymgr.API() - - self.encryption_key_id = kwargs.get('encryption_key_id') - - def _get_key(self, context): - """Retrieves the encryption key for the specified volume. - - :param: the connection information used to attach the volume - """ - return self._key_manager.get(context, self.encryption_key_id) - - @abc.abstractmethod - def attach_volume(self, context, **kwargs): - """Hook called immediately prior to attaching a volume to an instance. - """ - pass - - @abc.abstractmethod - def detach_volume(self, **kwargs): - """Hook called immediately after detaching a volume from an instance. - """ - pass diff --git a/nova/volume/encryptors/cryptsetup.py b/nova/volume/encryptors/cryptsetup.py deleted file mode 100644 index 62614a847308..000000000000 --- a/nova/volume/encryptors/cryptsetup.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import array -import binascii -import os - -from oslo_concurrency import processutils -from oslo_log import log as logging - -from nova import exception -from nova.i18n import _LW, _LI -from nova import utils -from nova.volume.encryptors import base - - -LOG = logging.getLogger(__name__) - - -class CryptsetupEncryptor(base.VolumeEncryptor): - """A VolumeEncryptor based on dm-crypt. - - This VolumeEncryptor uses dm-crypt to encrypt the specified volume. - """ - - def __init__(self, connection_info, **kwargs): - super(CryptsetupEncryptor, self).__init__(connection_info, **kwargs) - - # Fail if no device_path was set when connecting the volume, e.g. in - # the case of libvirt network volume drivers. - data = connection_info['data'] - if not data.get('device_path'): - volume_id = data.get('volume_id') or connection_info.get('serial') - raise exception.VolumeEncryptionNotSupported( - volume_id=volume_id, - volume_type=connection_info['driver_volume_type']) - - # the device's path as given to libvirt -- e.g., /dev/disk/by-path/... - self.symlink_path = connection_info['data']['device_path'] - - # a unique name for the volume -- e.g., the iSCSI participant name - self.dev_name = 'crypt-%s' % self.symlink_path.split('/')[-1] - - # NOTE(tsekiyama): In older version of nova, dev_name was the same - # as the symlink name. Now it has 'crypt-' prefix to avoid conflict - # with multipath device symlink. To enable rolling update, we use the - # old name when the encrypted volume already exists. - old_dev_name = self.symlink_path.split('/')[-1] - wwn = data.get('multipath_id') - if self._is_crypt_device_available(old_dev_name): - self.dev_name = old_dev_name - LOG.debug("Using old encrypted volume name: %s", self.dev_name) - elif wwn and wwn != old_dev_name: - # FibreChannel device could be named '/dev/mapper/'. - if self._is_crypt_device_available(wwn): - self.dev_name = wwn - LOG.debug("Using encrypted volume name from wwn: %s", - self.dev_name) - - # the device's actual path on the compute host -- e.g., /dev/sd_ - self.dev_path = os.path.realpath(self.symlink_path) - - def _is_crypt_device_available(self, dev_name): - if not os.path.exists('/dev/mapper/%s' % dev_name): - return False - - try: - utils.execute('cryptsetup', 'status', dev_name, run_as_root=True) - except processutils.ProcessExecutionError as e: - # If /dev/mapper/ is a non-crypt block device (such as a - # normal disk or multipath device), exit_code will be 1. In the - # case, we will omit the warning message. - if e.exit_code != 1: - LOG.warning(_LW('cryptsetup status %(dev_name)s exited ' - 'abnormally (status %(exit_code)s): %(err)s'), - {"dev_name": dev_name, "exit_code": e.exit_code, - "err": e.stderr}) - return False - return True - - def _get_passphrase(self, key): - """Convert raw key to string.""" - return binascii.hexlify(key).decode('utf-8') - - def _open_volume(self, passphrase, **kwargs): - """Opens the LUKS partition on the volume using the specified - passphrase. - - :param passphrase: the passphrase used to access the volume - """ - LOG.debug("opening encrypted volume %s", self.dev_path) - - # NOTE(joel-coffman): cryptsetup will strip trailing newlines from - # input specified on stdin unless --key-file=- is specified. - cmd = ["cryptsetup", "create", "--key-file=-"] - - cipher = kwargs.get("cipher", None) - if cipher is not None: - cmd.extend(["--cipher", cipher]) - - key_size = kwargs.get("key_size", None) - if key_size is not None: - cmd.extend(["--key-size", key_size]) - - cmd.extend([self.dev_name, self.dev_path]) - - 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. - - Transparent disk encryption is achieved by mounting the volume via - dm-crypt and passing the resulting device to the instance. The - instance is unaware of the underlying encryption due to modifying the - original symbolic link to refer to the device mounted by dm-crypt. - """ - - key = self._get_key(context).get_encoded() - passphrase = self._get_passphrase(key) - - 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', - '/dev/mapper/%s' % self.dev_name, self.symlink_path, - run_as_root=True, check_exit_code=True) - - def _close_volume(self, **kwargs): - """Closes the device (effectively removes the dm-crypt mapping).""" - LOG.debug("closing encrypted volume %s", self.dev_path) - # cryptsetup returns 4 when attempting to destroy a non-active - # dm-crypt device. We are going to ignore this error code to make - # nova deleting that instance successfully. - utils.execute('cryptsetup', 'remove', self.dev_name, - run_as_root=True, check_exit_code=[0, 4]) - - def detach_volume(self, **kwargs): - """Removes the dm-crypt mapping for the device.""" - self._close_volume(**kwargs) diff --git a/nova/volume/encryptors/luks.py b/nova/volume/encryptors/luks.py deleted file mode 100644 index 5b1923d43460..000000000000 --- a/nova/volume/encryptors/luks.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -from oslo_concurrency import processutils -from oslo_log import log as logging - -from nova.i18n import _LI -from nova.i18n import _LW -from nova import utils -from nova.volume.encryptors import cryptsetup - - -LOG = logging.getLogger(__name__) - - -def is_luks(device): - """Checks if the specified device uses LUKS for encryption. - - :param device: the device to check - :returns: true if the specified device uses LUKS; false otherwise - """ - try: - # check to see if the device uses LUKS: exit status is 0 - # if the device is a LUKS partition and non-zero if not - utils.execute('cryptsetup', 'isLuks', '--verbose', device, - run_as_root=True, check_exit_code=True) - return True - except processutils.ProcessExecutionError as e: - LOG.warning(_LW("isLuks exited abnormally (status %(exit_code)s): " - "%(stderr)s"), - {"exit_code": e.exit_code, "stderr": e.stderr}) - return False - - -class LuksEncryptor(cryptsetup.CryptsetupEncryptor): - """A VolumeEncryptor based on LUKS. - - This VolumeEncryptor uses dm-crypt to encrypt the specified volume. - """ - def _format_volume(self, passphrase, **kwargs): - """Creates a LUKS header on the volume. - - :param passphrase: the passphrase used to access the volume - """ - LOG.debug("formatting encrypted volume %s", self.dev_path) - - # NOTE(joel-coffman): cryptsetup will strip trailing newlines from - # input specified on stdin unless --key-file=- is specified. - cmd = ["cryptsetup", "--batch-mode", "luksFormat", "--key-file=-"] - - cipher = kwargs.get("cipher", None) - if cipher is not None: - cmd.extend(["--cipher", cipher]) - - key_size = kwargs.get("key_size", None) - if key_size is not None: - cmd.extend(["--key-size", key_size]) - - cmd.extend([self.dev_path]) - - utils.execute(*cmd, process_input=passphrase, - check_exit_code=True, run_as_root=True, attempts=3) - - def _open_volume(self, passphrase, **kwargs): - """Opens the LUKS partition on the volume using the specified - passphrase. - - :param passphrase: the passphrase used to access the volume - """ - LOG.debug("opening encrypted volume %s", self.dev_path) - utils.execute('cryptsetup', 'luksOpen', '--key-file=-', - self.dev_path, self.dev_name, process_input=passphrase, - run_as_root=True, check_exit_code=True) - - def _unmangle_volume(self, key, passphrase, **kwargs): - """Workaround bug#1633518 by first identifying if a mangled passphrase - is used before replacing it with the correct passphrase. - """ - mangled_passphrase = self._get_mangled_passphrase(key) - self._open_volume(mangled_passphrase, **kwargs) - self._close_volume(**kwargs) - LOG.debug("%s correctly opened with a mangled passphrase, replacing" - "this with the original passphrase", self.dev_path) - - # NOTE(lyarwood): Now that we are sure that the mangled passphrase is - # used attempt to add the correct passphrase before removing the - # mangled version from the volume. - - # luksAddKey currently prompts for the following input : - # Enter any existing passphrase: - # Enter new passphrase for key slot: - # Verify passphrase: - utils.execute('cryptsetup', 'luksAddKey', self.dev_path, - process_input=''.join([mangled_passphrase, '\n', - passphrase, '\n', passphrase]), - run_as_root=True, check_exit_code=True) - - # Verify that we can open the volume with the current passphrase - # before removing the mangled passphrase. - self._open_volume(passphrase, **kwargs) - self._close_volume(**kwargs) - - # luksRemoveKey only prompts for the key to remove. - utils.execute('cryptsetup', 'luksRemoveKey', self.dev_path, - process_input=mangled_passphrase, - run_as_root=True, check_exit_code=True) - LOG.debug("%s mangled passphrase successfully replaced", self.dev_path) - - def attach_volume(self, context, **kwargs): - """Shadows the device and passes an unencrypted version to the - instance. - - Transparent disk encryption is achieved by mounting the volume via - dm-crypt and passing the resulting device to the instance. The - instance is unaware of the underlying encryption due to modifying the - original symbolic link to refer to the device mounted by dm-crypt. - """ - - key = self._get_key(context).get_encoded() - passphrase = self._get_passphrase(key) - - try: - self._open_volume(passphrase, **kwargs) - except processutils.ProcessExecutionError as e: - if e.exit_code == 1 and not is_luks(self.dev_path): - # the device has never been formatted; format it and try again - LOG.info(_LI("%s is not a valid LUKS device;" - " formatting device for first use"), - self.dev_path) - self._format_volume(passphrase, **kwargs) - self._open_volume(passphrase, **kwargs) - elif e.exit_code == 2: - # NOTE(lyarwood): Workaround bug#1633518 by replacing any - # mangled passphrases that are found on the volume. - # TODO(lyarwood): Remove workaround during R. - LOG.warning(_LW("%s is not usable with the current " - "passphrase, attempting to use a mangled " - "passphrase to open the volume."), - self.dev_path) - self._unmangle_volume(key, passphrase, **kwargs) - self._open_volume(passphrase, **kwargs) - else: - raise - - # modify the original symbolic link to refer to the decrypted device - utils.execute('ln', '--symbolic', '--force', - '/dev/mapper/%s' % self.dev_name, self.symlink_path, - run_as_root=True, check_exit_code=True) - - def _close_volume(self, **kwargs): - """Closes the device (effectively removes the dm-crypt mapping).""" - LOG.debug("closing encrypted volume %s", self.dev_path) - utils.execute('cryptsetup', 'luksClose', self.dev_name, - run_as_root=True, check_exit_code=True, - attempts=3) diff --git a/nova/volume/encryptors/nop.py b/nova/volume/encryptors/nop.py deleted file mode 100644 index 2d91f9fbe0d6..000000000000 --- a/nova/volume/encryptors/nop.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from nova.volume.encryptors import base - - -class NoOpEncryptor(base.VolumeEncryptor): - """A VolumeEncryptor that does nothing. - - This class exists solely to wrap regular (i.e., unencrypted) volumes so - that they do not require special handling with respect to an encrypted - volume. This implementation performs no action when a volume is attached - or detached. - """ - def attach_volume(self, context): - pass - - def detach_volume(self): - pass