diff --git a/doc/source/image_description/elements.rst b/doc/source/image_description/elements.rst index 43249a8e61e..d8dcb5a87ef 100644 --- a/doc/source/image_description/elements.rst +++ b/doc/source/image_description/elements.rst @@ -566,15 +566,34 @@ root_clone="number" boot_clone="number" Same as `root_clone` but applied to the boot partition if present -luks="passphrase|file:///path/to/keyfile": +luks="passphrase|file:///path/to/keyfile|random": Supplying a value will trigger the encryption of the partition serving the root filesystem using the LUKS extension. The supplied value represents either the passphrase string or the location of - a key file if specified as `file://...` resource. When using - a key file it is in the responsibility of the user how - this key file is actually being used. By default any - distribution will just open an interactive dialog asking - for the credentials at boot time ! + a key file if specified as `file://...` resource or the reserved + name `random`. When using a passphrase the system will interactively + ask for that passphrase on first boot unless it is set empty. + In case of an empty passphrase the system cannot be considered secure. + When using a key file the information from the file is read and + used as a passphrase. The given key file is **not automatically** + placed into the system or added to the `etc/crypttab` which means + the passphrase in the key file is by default requested from an + interactive dialog at boot time. When using the reserved word + `random`, kiwi will create a key file with a random passphrase + and place this information into `etc/crypttab`. This allows + the system to boot without user interaction but also requires + the initrd to be protected in some way because it will contain + the keyfile. The use of `random` is therefore only secure if + the image adds additional security that encrypts the initrd + like it is e.g. done in the IBM secure execution process. + If the encryption of the system is combined with the attribute + `bootpartition="false"` it's important to understand that this + will place `/boot` into the encrypted area of the system and + leaves reading boot data from it as a responsibility to the + bootloader. Not every bootloader can cope with that and those + that can e.g. grub will then open an interactive dialog at + the bootloader level asking for the credentials to decrypt the + root filesystem. luks_version="luks|luks1|luks2": Specify which `LUKS` version should be used. If not set and by diff --git a/kiwi/builder/disk.py b/kiwi/builder/disk.py index 52f07be3742..15856dcb9ed 100644 --- a/kiwi/builder/disk.py +++ b/kiwi/builder/disk.py @@ -667,7 +667,7 @@ def _map_luks( # LUKS pool without asking # luks_need_keyfile = \ - True if self.boot_is_crypto or self.luks == '' else False + True if self.boot_is_crypto or self.luks == '' or self.luks == 'random' else False luks_root.create_crypto_luks( passphrase=self.luks or '', osname=self.luks_os, diff --git a/kiwi/storage/luks_device.py b/kiwi/storage/luks_device.py index ba90ad9ce28..ab72b7d9a87 100644 --- a/kiwi/storage/luks_device.py +++ b/kiwi/storage/luks_device.py @@ -108,6 +108,13 @@ def create_crypto_luks( if not passphrase: log.warning('Using an empty passphrase for the key setup') + if keyfile: + self.luks_keyfile = keyfile + keyfile_path = os.path.normpath( + os.sep.join([root_dir, self.luks_keyfile]) + ) + LuksDevice.create_random_keyfile(keyfile_path) + if randomize: log.info('--> Randomizing...') storage_size_mbytes = self.storage_provider.get_byte_size( @@ -123,12 +130,26 @@ def create_crypto_luks( log.info('--> Creating LUKS map') - if passphrase: + if passphrase and passphrase == 'random': + # In random mode use the generated keyfile as the only + # key to decrypt. This is only secure if the generated + # initrd also gets protected, e.g through encryption + # like it is done with the secure linux execution on + # zSystems + passphrase_file = keyfile_path + # Do not add an additional keyfile + keyfile = '' + elif passphrase: + # Setup a passphrase file for which the system will + # ask for in an interactive dialog passphrase_file_tmp = Temporary().new_file() with open(passphrase_file_tmp.name, 'w') as credentials: credentials.write(passphrase) passphrase_file = passphrase_file_tmp.name else: + # Setup an empty passphrase, insecure and only useful + # for initial deployment which then applies a process + # to secure the image e.g reencrypt passphrase_file_zero = '/dev/zero' extra_options = [ '--keyfile-size', '32' @@ -142,12 +163,8 @@ def create_crypto_luks( 'luksFormat', storage_device ] ) + if keyfile: - self.luks_keyfile = keyfile - keyfile_path = os.path.normpath( - os.sep.join([root_dir, self.luks_keyfile]) - ) - LuksDevice.create_random_keyfile(keyfile_path) Command.run( [ 'cryptsetup', '--key-file', passphrase_file diff --git a/test/unit/storage/luks_device_test.py b/test/unit/storage/luks_device_test.py index b0248f3b753..69c41c6f423 100644 --- a/test/unit/storage/luks_device_test.py +++ b/test/unit/storage/luks_device_test.py @@ -95,6 +95,49 @@ def test_create_crypto_luks_empty_passphrase( ] self.luks.luks_device = None + @patch('kiwi.storage.luks_device.LuksDevice') + @patch('kiwi.storage.luks_device.Command.run') + @patch('kiwi.storage.luks_device.Temporary.new_file') + @patch('os.chmod') + def test_create_crypto_luks_random_passphrase( + self, mock_os_chmod, mock_tmpfile, mock_command, mock_LuksDevice + ): + tmpfile = Mock() + tmpfile.name = 'tmpfile' + mock_tmpfile.return_value = tmpfile + with patch('builtins.open', create=True): + self.luks.create_crypto_luks( + passphrase='random', osname='sle12', + keyfile='some-keyfile', root_dir='root' + ) + assert mock_command.call_args_list == [ + call( + [ + 'dd', 'if=/dev/urandom', 'bs=1M', 'count=1', + 'of=/dev/some-device' + ] + ), + call( + [ + 'cryptsetup', '-q', '--key-file', 'root/some-keyfile', + '--cipher', 'aes-xts-plain64', + '--key-size', '256', '--hash', 'sha1', + 'luksFormat', '/dev/some-device' + ] + ), + call( + [ + 'cryptsetup', '--key-file', 'root/some-keyfile', 'luksOpen', + '/dev/some-device', 'luksRoot' + ] + ) + ] + mock_LuksDevice.create_random_keyfile.assert_called_once_with( + 'root/some-keyfile' + ) + assert self.luks.luks_keyfile == 'some-keyfile' + self.luks.luks_device = '' + @patch('kiwi.storage.luks_device.LuksDevice') @patch('kiwi.storage.luks_device.Command.run') @patch('kiwi.storage.luks_device.Temporary.new_file')