curtin-dev team mailing list archive
-
curtin-dev team
-
Mailing list archive
-
Message #03253
[Merge] ~dbungert/curtin:cryptoswap into curtin:master
Dan Bungert has proposed merging ~dbungert/curtin:cryptoswap into curtin:master.
Commit message:
Add features to the dm_crypt action to support cryptoswap
Requested reviews:
curtin developers (curtin-dev)
For more details, see:
https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/458460
--
Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:cryptoswap into curtin:master.
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index ebae27c..8ba7a55 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -1618,6 +1618,8 @@ def dm_crypt_handler(info, storage_config, context):
keysize = info.get('keysize')
cipher = info.get('cipher')
dm_name = info.get('dm_name')
+ options = ','.join(info.get('options', ['luks']))
+ crypttab_keyfile = 'none'
if not dm_name:
dm_name = info.get('id')
dmcrypt_dev = os.path.join("/dev", "mapper", dm_name)
@@ -1634,8 +1636,13 @@ def dm_crypt_handler(info, storage_config, context):
if 'keyfile' in info:
if 'key' in info:
raise ValueError("cannot specify both key and keyfile")
- keyfile_is_tmp = False
keyfile = info['keyfile']
+ if keyfile in ("/dev/random", "/dev/urandom"):
+ crypttab_keyfile = keyfile
+ keyfile = tempfile.mkstemp()[1]
+ keyfile_is_tmp = True
+ else:
+ keyfile_is_tmp = False
elif 'key' in info:
# TODO: this is insecure, find better way to do this
key = info.get('key')
@@ -1727,8 +1734,9 @@ def dm_crypt_handler(info, storage_config, context):
state_dir = os.path.dirname(state['fstab'])
crypt_tab_location = os.path.join(state_dir, "crypttab")
uuid = block.get_volume_uuid(volume_path)
- util.write_file(crypt_tab_location,
- "%s UUID=%s none luks\n" % (dm_name, uuid), omode="a")
+ util.write_file(
+ crypt_tab_location,
+ f"{dm_name} UUID={uuid} {crypttab_keyfile} {options}\n", omode="a")
else:
LOG.info("fstab configuration is not present in environment, so \
cannot locate an appropriate directory to write crypttab in \
diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst
index 45b9bbe..30e52aa 100644
--- a/doc/topics/storage.rst
+++ b/doc/topics/storage.rst
@@ -886,6 +886,15 @@ system will prompt for this password in order to mount the disk.
The ``keyfile`` contains the password of the encryption key. The target
system will prompt for this password in order to mount the disk.
+A special case of ``keyfile`` are the values ``/dev/urandom`` and
+``/dev/random``, which indicate that this ``keyfile`` value will be used in
+enterity to decrypt the device. In this case, the keyfile is passed along to
+the crypttab, as the third field.
+
+.. note::
+ ``/dev/urandom`` and ``/dev/random`` are functionally equivalent on modern
+ systems.
+
Exactly one of **key** and **keyfile** must be supplied.
**preserve**: *true, false*
@@ -893,13 +902,16 @@ Exactly one of **key** and **keyfile** must be supplied.
If the ``preserve`` option is True, curtin will verify the dm-crypt device
specified is composed of the device specified in ``volume``.
-
**wipe**: *superblock, superblock-recursive, pvremove, zero, random*
If ``wipe`` option is set, and ``preserve`` is False, curtin will wipe the
contents of the dm-crypt device. Curtin skips wipe settings if it creates
the dm-crypt volume.
+**options**: *<list of man 5 options strings>*
+
+Options to pass to crypttab, as the fourth field. See man 5 crypttab for
+details. The default is ``luks``.
.. note::
diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py
index acd12e1..d7dcc87 100644
--- a/tests/integration/test_block_meta.py
+++ b/tests/integration/test_block_meta.py
@@ -6,6 +6,7 @@ import contextlib
import json
import os
from parameterized import parameterized
+from pathlib import Path
import re
import sys
from typing import Optional
@@ -192,6 +193,15 @@ class StorageConfigBuilder:
for action in self.config:
action['preserve'] = True
+ def add_dmcrypt(self, *, volume, dm_name=None, **kw):
+ if dm_name is None:
+ dm_name = CiTestCase.random_string()
+ return self._add(
+ type='dm_crypt',
+ volume=volume['id'],
+ dm_name=dm_name,
+ **kw)
+
class TestBlockMeta(IntegrationTestCase):
def setUp(self):
@@ -239,15 +249,17 @@ class TestBlockMeta(IntegrationTestCase):
with open(config_path, 'w') as fp:
yaml.dump(config, fp)
+ self.fstab_dir = self.tmp_dir()
cmd_env = kwargs.pop('env', {})
cmd_env.update({
'PATH': os.environ['PATH'],
'CONFIG': config_path,
'WORKING_DIR': '/tmp',
- 'OUTPUT_FSTAB': self.tmp_path('fstab'),
+ 'OUTPUT_FSTAB': self.tmp_path('fstab', _dir=self.fstab_dir),
'OUTPUT_INTERFACES': '',
'OUTPUT_NETWORK_STATE': '',
'OUTPUT_NETWORK_CONFIG': '',
+ 'TARGET_MOUNT_POINT': self.tmp_dir(),
})
cmd = [
@@ -1285,6 +1297,38 @@ table-length: 256'''.encode()
partition_type='82'))
@parameterized.expand(((1,), (2,)))
+ def test_cryptoswap(self, sv=2):
+ self.img = self.tmp_path('image.img')
+ config = StorageConfigBuilder(version=sv)
+ config.add_image(path=self.img, create=True, size='200M',
+ ptable='msdos')
+ p1 = config.add_part(
+ number=1, offset=1 << 20, size=19 << 20, flag='swap'
+ )
+ cryptoswap = f"cryptoswap-{self.random_string(length=6)}"
+ dmc1 = config.add_dmcrypt(
+ volume=p1,
+ dm_name=cryptoswap,
+ keyfile="/dev/urandom",
+ options=["swap", "initramfs"],
+ )
+ config.add_format(part=dmc1, fstype="swap")
+ self.run_bm(config.render())
+
+ self.assertPartitions(
+ PartData(number=1, offset=1 << 20, size=19 << 20, boot=False,
+ partition_type='82'))
+
+ crypttab_path = Path(self.fstab_dir) / "crypttab"
+ with open(crypttab_path) as fp:
+ crypttab = fp.read()
+ tokens = re.split(r'\s', crypttab)
+ self.assertEqual(cryptoswap, tokens[0])
+ self.assertTrue(tokens[1].startswith("UUID="))
+ self.assertEqual("/dev/urandom", tokens[2])
+ self.assertEqual("swap,initramfs", tokens[3])
+
+ @parameterized.expand(((1,), (2,)))
def test_msftres(self, sv):
self.img = self.tmp_path('image.img')
config = StorageConfigBuilder(version=sv)
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index 9d7d0f3..5adb46c 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -2030,10 +2030,10 @@ class TestLvmPartitionHandler(CiTestCase):
self.assertEqual(0, self.m_subp.call_count)
-class TestDmCryptHandler(CiTestCase):
+class DmCryptCommon(CiTestCase):
def setUp(self):
- super(TestDmCryptHandler, self).setUp()
+ super().setUp()
basepath = 'curtin.commands.block_meta.'
self.add_patch(basepath + 'get_path_to_storage_volume', 'm_getpath')
@@ -2047,7 +2047,26 @@ class TestDmCryptHandler(CiTestCase):
self.keyfile = self.random_string()
self.cipher = self.random_string()
self.keysize = self.random_string()
- self.config = {
+ self.m_block.zkey_supported.return_value = False
+ self.block_uuids = [random_uuid() for unused in range(2)]
+ self.m_block.get_volume_uuid.side_effect = self.block_uuids
+
+ self.m_which.return_value = False
+ self.fstab = self.tmp_path('fstab')
+ self.crypttab = os.path.join(os.path.dirname(self.fstab), 'crypttab')
+ self.m_load_env.return_value = {'fstab': self.fstab,
+ 'target': self.target}
+
+ def setUpStorageConfig(self, config):
+ self.config = config
+ self.storage_config = block_meta.extract_storage_ordered_dict(config)
+
+
+class TestDmCryptHandler(DmCryptCommon):
+
+ def setUp(self):
+ super().setUp()
+ self.setUpStorageConfig({
'storage': {
'version': 1,
'config': [
@@ -2073,15 +2092,7 @@ class TestDmCryptHandler(CiTestCase):
'keyfile': self.keyfile},
],
}
- }
- self.storage_config = (
- block_meta.extract_storage_ordered_dict(self.config))
- self.m_block.zkey_supported.return_value = False
- self.m_which.return_value = False
- self.fstab = self.tmp_path('fstab')
- self.crypttab = os.path.join(os.path.dirname(self.fstab), 'crypttab')
- self.m_load_env.return_value = {'fstab': self.fstab,
- 'target': self.target}
+ })
def test_dm_crypt_calls_cryptsetup(self):
""" verify dm_crypt calls (format, open) w/ correct params"""
@@ -2402,6 +2413,109 @@ class TestDmCryptHandler(CiTestCase):
info, self.storage_config, empty_context)
+class TestCrypttab(DmCryptCommon):
+
+ def test_multi_dm_crypt(self):
+ """ verify that multiple dm_crypt calls result in the data for both
+ being present in crypttab"""
+
+ self.setUpStorageConfig({
+ 'storage': {
+ 'version': 1,
+ 'config': [
+ {'grub_device': True,
+ 'id': 'sda',
+ 'name': 'sda',
+ 'path': '/wark/xxx',
+ 'ptable': 'msdos',
+ 'type': 'disk',
+ 'wipe': 'superblock'},
+ {'device': 'sda',
+ 'id': 'sda-part1',
+ 'name': 'sda-part1',
+ 'number': 1,
+ 'size': '511705088B',
+ 'type': 'partition'},
+ {'id': 'dmcrypt0',
+ 'type': 'dm_crypt',
+ 'dm_name': 'cryptroot',
+ 'volume': 'sda-part1',
+ 'cipher': self.cipher,
+ 'keysize': self.keysize,
+ 'keyfile': self.keyfile},
+ {'device': 'sda',
+ 'id': 'sda-part2',
+ 'name': 'sda-part2',
+ 'number': 2,
+ 'size': '511705088B',
+ 'type': 'partition'},
+ {'id': 'dmcrypt1',
+ 'type': 'dm_crypt',
+ 'dm_name': 'cryptfoo',
+ 'volume': 'sda-part2',
+ 'cipher': self.cipher,
+ 'keysize': self.keysize,
+ 'keyfile': self.keyfile},
+ ],
+ }
+ })
+
+ for i in range(2):
+ self.m_getpath.return_value = self.random_string()
+ info = self.storage_config['dmcrypt' + str(i)]
+ block_meta.dm_crypt_handler(
+ info, self.storage_config, empty_context
+ )
+
+ data = util.load_file(self.crypttab)
+ self.assertEqual(
+ f"cryptroot UUID={self.block_uuids[0]} none luks\n"
+ f"cryptfoo UUID={self.block_uuids[1]} none luks\n",
+ data
+ )
+
+ def test_cryptoswap(self):
+ """ Check the keyfile and options features needed for cryptoswap """
+
+ self.setUpStorageConfig({
+ 'storage': {
+ 'version': 1,
+ 'config': [
+ {'grub_device': True,
+ 'id': 'sda',
+ 'name': 'sda',
+ 'path': '/wark/xxx',
+ 'ptable': 'msdos',
+ 'type': 'disk',
+ 'wipe': 'superblock'},
+ {'device': 'sda',
+ 'id': 'sda-part1',
+ 'name': 'sda-part1',
+ 'number': 1,
+ 'size': '511705088B',
+ 'type': 'partition'},
+ {'id': 'dmcrypt0',
+ 'type': 'dm_crypt',
+ 'dm_name': 'cryptswap',
+ 'volume': 'sda-part1',
+ 'options': ["swap", "initramfs"],
+ 'keyfile': "/dev/urandom"},
+ ],
+ }
+ })
+
+ self.m_getpath.return_value = self.random_string()
+ block_meta.dm_crypt_handler(
+ self.storage_config['dmcrypt0'], self.storage_config, empty_context
+ )
+
+ uuid = self.block_uuids[0]
+ self.assertEqual(
+ f"cryptswap UUID={uuid} /dev/urandom swap,initramfs\n",
+ util.load_file(self.crypttab),
+ )
+
+
class TestRaidHandler(CiTestCase):
def setUp(self):
diff --git a/tools/vmtest-system-setup b/tools/vmtest-system-setup
index 5172741..2f82464 100755
--- a/tools/vmtest-system-setup
+++ b/tools/vmtest-system-setup
@@ -14,6 +14,7 @@ esac
DEPS=(
build-essential
cloud-image-utils
+ cryptsetup
git
make
net-tools
Follow ups