encryptors: Introduce support for LUKS2

LUKS2 support was introduced into cryptsetup 2.0.0 [1] and offers various
improvements over the original format now referred to as LUKS1.

This change introduces an encryptor to os-brick mostly using the
existing LuksEncryptor class with the only difference being the `--type`
switch supplied to cryptsetup when formatting a volume. As such the bulk
of the _format_volume method from the original class has been extracted
into a new _format_luks_volume method both the original and new
Luks2Encryptor class can now reuse.

[1] https://www.saout.de/pipermail/dm-crypt/2017-December/005771.html

Change-Id: I09fb2b2be1e376f8ec0f49741c855cfd54ee27f0
This commit is contained in:
Lee Yarwood 2019-06-07 14:32:42 +01:00 committed by Brian Rosmaita
parent 80da84a09d
commit 6a01bacda7
4 changed files with 108 additions and 2 deletions

View File

@ -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'
}

View File

@ -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)

View File

@ -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)

View File

@ -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.