diff --git a/os_brick/encryptors/__init__.py b/os_brick/encryptors/__init__.py index 20304be7d..fc5e1036e 100644 --- a/os_brick/encryptors/__init__.py +++ b/os_brick/encryptors/__init__.py @@ -22,10 +22,12 @@ from oslo_utils import strutils LOG = logging.getLogger(__name__) LUKS = "luks" +LUKS2 = "luks2" PLAIN = "plain" FORMAT_TO_FRONTEND_ENCRYPTOR_MAP = { LUKS: 'os_brick.encryptors.luks.LuksEncryptor', + LUKS2: 'os_brick.encryptors.luks.Luks2Encryptor', PLAIN: 'os_brick.encryptors.cryptsetup.CryptsetupEncryptor' } diff --git a/os_brick/encryptors/luks.py b/os_brick/encryptors/luks.py index 0faae28c7..765e857dc 100644 --- a/os_brick/encryptors/luks.py +++ b/os_brick/encryptors/luks.py @@ -61,15 +61,29 @@ class LuksEncryptor(cryptsetup.CryptsetupEncryptor): *args, **kwargs) def _format_volume(self, passphrase, **kwargs): - """Creates a LUKS header on the volume. + """Creates a LUKS v1 header on the volume. :param passphrase: the passphrase used to access the volume """ + self._format_luks_volume(passphrase, 'luks1', **kwargs) + + def _format_luks_volume(self, passphrase, version, **kwargs): + """Creates a LUKS header of a given version or type on the volume. + + :param passphrase: the passphrase used to access the volume + :param version: the LUKS version or type to use: one of `luks`, + `luks1`, or `luks2`. Be aware that `luks` gives you + the default LUKS format preferred by the particular + cryptsetup being used (depends on version and compile + time parameters), which could be either LUKS1 or + LUKS2, so it's better to be specific about what you + want here + """ 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", "--type", "luks1", + cmd = ["cryptsetup", "--batch-mode", "luksFormat", "--type", version, "--key-file=-"] cipher = kwargs.get("cipher", None) @@ -191,3 +205,28 @@ class LuksEncryptor(cryptsetup.CryptsetupEncryptor): run_as_root=True, check_exit_code=[0, 4], root_helper=self._root_helper, attempts=3) + + +class Luks2Encryptor(LuksEncryptor): + """A VolumeEncryptor based on LUKS v2. + + This VolumeEncryptor uses dm-crypt to encrypt the specified volume. + """ + def __init__(self, root_helper, + connection_info, + keymgr, + execute=None, + *args, **kwargs): + super(Luks2Encryptor, self).__init__( + root_helper=root_helper, + connection_info=connection_info, + keymgr=keymgr, + execute=execute, + *args, **kwargs) + + def _format_volume(self, passphrase, **kwargs): + """Creates a LUKS v2 header on the volume. + + :param passphrase: the passphrase used to access the volume + """ + self._format_luks_volume(passphrase, 'luks2', **kwargs) diff --git a/os_brick/tests/encryptors/test_luks.py b/os_brick/tests/encryptors/test_luks.py index 191acbc30..408aec88c 100644 --- a/os_brick/tests/encryptors/test_luks.py +++ b/os_brick/tests/encryptors/test_luks.py @@ -253,3 +253,62 @@ class LuksEncryptorTestCase(test_cryptsetup.CryptsetupEncryptorTestCase): check_exit_code=True), ], any_order=False) self.assertEqual(9, mock_execute.call_count) + + +class Luks2EncryptorTestCase(LuksEncryptorTestCase): + def _create(self): + return luks.Luks2Encryptor(root_helper=self.root_helper, + connection_info=self.connection_info, + keymgr=self.keymgr) + + @mock.patch('os_brick.executor.Executor._execute') + def test__format_volume(self, mock_execute): + self.encryptor._format_volume("passphrase") + + mock_execute.assert_has_calls([ + mock.call('cryptsetup', '--batch-mode', 'luksFormat', + '--type', 'luks2', '--key-file=-', self.dev_path, + process_input='passphrase', + root_helper=self.root_helper, + run_as_root=True, check_exit_code=True, attempts=3), + ]) + + @mock.patch('os_brick.executor.Executor._execute') + def test_attach_volume_not_formatted(self, mock_execute): + fake_key = 'bc37c5eccebe403f9cc2d0dd20dac2bc' + self.encryptor._get_key = mock.MagicMock() + self.encryptor._get_key.return_value = ( + test_cryptsetup.fake__get_key(None, fake_key)) + + mock_execute.side_effect = [ + putils.ProcessExecutionError(exit_code=1), # luksOpen + putils.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, + root_helper=self.root_helper, + run_as_root=True, check_exit_code=True), + mock.call('cryptsetup', 'isLuks', '--verbose', self.dev_path, + root_helper=self.root_helper, + run_as_root=True, check_exit_code=True), + mock.call('cryptsetup', '--batch-mode', 'luksFormat', + '--type', 'luks2', '--key-file=-', self.dev_path, + process_input=fake_key, + root_helper=self.root_helper, + 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, + root_helper=self.root_helper, + run_as_root=True, check_exit_code=True), + mock.call('ln', '--symbolic', '--force', + '/dev/mapper/%s' % self.dev_name, self.symlink_path, + root_helper=self.root_helper, + run_as_root=True, check_exit_code=True), + ], any_order=False) diff --git a/releasenotes/notes/add-luks2-support-13563cfe83aba69c.yaml b/releasenotes/notes/add-luks2-support-13563cfe83aba69c.yaml new file mode 100644 index 000000000..2445bc156 --- /dev/null +++ b/releasenotes/notes/add-luks2-support-13563cfe83aba69c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + A LUKS2 encryptor has been introduced providing support for this latest + version of the Linux Unified Key Setup disk encryption format. This + requires ``cryptsetup`` version 2.0.0 or greater.