]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume: support additional dmcrypt params 65321/head
authorGuillaume Abrioux <gabrioux@ibm.com>
Mon, 1 Sep 2025 13:13:43 +0000 (13:13 +0000)
committerGuillaume Abrioux <gabrioux@ibm.com>
Thu, 4 Sep 2025 13:08:33 +0000 (15:08 +0200)
Add support for passing extra options to dmcrypt commands.
This makes it possible to provide custom parameters to
`cryptsetup luksFormat` and `cryptsetup luksOpen`, using the same
syntax as the native cryptsetup CLI.

Fixes: https://tracker.ceph.com/issues/72801
Signed-off-by: Guillaume Abrioux <gabrioux@ibm.com>
src/ceph-volume/ceph_volume/devices/lvm/batch.py
src/ceph-volume/ceph_volume/devices/lvm/common.py
src/ceph-volume/ceph_volume/devices/lvm/migrate.py
src/ceph-volume/ceph_volume/objectstore/lvm.py
src/ceph-volume/ceph_volume/tests/objectstore/test_lvm.py
src/ceph-volume/ceph_volume/tests/util/test_encryption.py
src/ceph-volume/ceph_volume/util/encryption.py

index 8f766923bd19f912f27368afc4ab635506ea5ddd..9ed46d7afc324ffa78cc0c82b5aac895ebcf5bdc 100644 (file)
@@ -278,6 +278,18 @@ class Batch(object):
             help='Reuse existing OSD ids',
             type=arg_validators.valid_osd_id
         )
+        parser.add_argument(
+            '--dmcrypt-format-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksFormat options (use the same syntax as the cryptsetup CLI)",
+        )
+        parser.add_argument(
+            '--dmcrypt-open-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksOpen options (use the same syntax as the cryptsetup CLI)",
+        )
         self.args = parser.parse_args(argv)
         if self.args.bluestore:
             self.args.objectstore = 'bluestore'
@@ -379,6 +391,8 @@ class Batch(object):
             'with_tpm',
             'crush_device_class',
             'no_systemd',
+            'dmcrypt_format_opts',
+            'dmcrypt_open_opts',
         ]
         defaults.update({arg: getattr(self.args, arg) for arg in global_args})
         for osd in plan:
index a74931155c25aa933c6aea6f724719701addcae4..fa39ac6c96229f23d52601e86dc7984bc0d28e8b 100644 (file)
@@ -84,6 +84,14 @@ common_args: Dict[str, Any] = {
         'action': arg_validators.DmcryptAction,
         'help': 'Enable device encryption via dm-crypt',
     },
+    '--dmcrypt-format-opts': {
+        'default': None,
+        'type': Optional[str],
+    },
+    '--dmcrypt-open-opts': {
+        'default': None,
+        'type': Optional[str],
+    },
     '--with-tpm': {
         'dest': 'with_tpm',
         'help': 'Whether encrypted OSDs should be enrolled with TPM.',
index 83ed16845e773a3508ad0a50983378fe8fa6f81b..1e19d9afd3eb1ed44c082e22f0fa497135f20c44 100644 (file)
@@ -339,7 +339,11 @@ class Migrate(object):
             secret = encryption_utils.get_dmcrypt_key(osd_id, osd_fsid)
             mlogger.info(' preparing dmcrypt for {}, uuid {}'.format(target_lv.lv_path, target_lv.lv_uuid))
             target_path = encryption_utils.prepare_dmcrypt(
-                key=secret, device=target_path, mapping=target_lv.lv_uuid)
+                key=secret,
+                device=target_path,
+                mapping=target_lv.lv_uuid,
+                format_options=self.args.dmcrypt_format_opts,
+                open_options=self.args.dmcrypt_open_opts)
         try:
             # we need to update lvm tags for all the remaining volumes
             # and clear for ones which to be removed
@@ -511,6 +515,18 @@ class Migrate(object):
             action='store_true',
             help='Skip checking OSD systemd unit',
         )
+        parser.add_argument(
+            '--dmcrypt-format-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksFormat options (use the same syntax as the cryptsetup CLI)",
+        )
+        parser.add_argument(
+            '--dmcrypt-open-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksOpen options (use the same syntax as the cryptsetup CLI)",
+        )
         return parser
 
     def main(self):
@@ -602,6 +618,18 @@ class NewVolume(object):
             action='store_true',
             help='Skip checking OSD systemd unit',
         )
+        parser.add_argument(
+            '--dmcrypt-format-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksFormat options (use the same syntax as the cryptsetup CLI)",
+        )
+        parser.add_argument(
+            '--dmcrypt-open-opts',
+            type=str,
+            default=None,
+            help="Additional cryptsetup luksOpen options (use the same syntax as the cryptsetup CLI)",
+        )
         return parser
 
     @decorators.needs_root
@@ -617,7 +645,12 @@ class NewVolume(object):
             secret = encryption_utils.get_dmcrypt_key(osd_id, osd_fsid)
             mlogger.info(' preparing dmcrypt for {}, uuid {}'.format(target_lv.lv_path, target_lv.lv_uuid))
             target_path = encryption_utils.prepare_dmcrypt(
-                key=secret, device=target_path, mapping=target_lv.lv_uuid)
+                key=secret,
+                device=target_path,
+                mapping=target_lv.lv_uuid,
+                format_options=self.args.dmcrypt_format_opts,
+                open_options=self.args.dmcrypt_open_opts
+            )
 
         try:
             tag_tracker.update_tags_when_lv_create(self.create_type)
index 204f2b954e14d362d309ea0b63c1ba0941b7041f..e1c78c9483b0adbf5c21d3c8c129c539f4519f56 100644 (file)
@@ -181,7 +181,8 @@ class Lvm(BaseObjectStore):
         # format data device
         encryption_utils.luks_format(
             self.dmcrypt_key,
-            device
+            device,
+            self.args.dmcrypt_format_opts,
         )
 
         if self.with_tpm:
@@ -191,7 +192,8 @@ class Lvm(BaseObjectStore):
             self.dmcrypt_key,
             device,
             uuid,
-            self.with_tpm)
+            self.with_tpm,
+            self.args.dmcrypt_open_opts)
 
         return '/dev/mapper/%s' % uuid
 
index f23bfd0876e158248640ed67ad7298f38ccdc05f..3493f9930eb1a2de85494ac7ea64676949c93eb1 100644 (file)
@@ -10,7 +10,8 @@ from typing import Callable
 class TestLvm:
     @patch('ceph_volume.objectstore.lvm.prepare_utils.create_key', Mock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
     def setup_method(self, m_create_key):
-        self.lvm = Lvm([])
+        args = Namespace(dmcrypt_format_opts=None, dmcrypt_open_opts=None)
+        self.lvm = Lvm(args)
 
     @patch('ceph_volume.conf.cluster', 'ceph')
     @patch('ceph_volume.api.lvm.get_single_lv')
@@ -28,6 +29,7 @@ class TestLvm:
                                               lv_tags='',
                                               lv_uuid='fake-uuid')
         self.lvm.encrypted = True
+        self.lvm.with_tpm = 0
         self.lvm.dmcrypt_key = 'fake-dmcrypt-key'
         self.lvm.args = args
         self.lvm.objectstore = 'seastore'
@@ -108,6 +110,7 @@ class TestLvm:
                                                            lv_uuid='fake-uuid')
         self.lvm.encrypted = True
         self.lvm.dmcrypt_key = 'fake-dmcrypt-key'
+        self.lvm.with_tpm = 0
         self.lvm.args = args
         self.lvm.objectstore = 'seastore'
         self.lvm.pre_prepare()
index 3e5284d9af8e85cb6600a62ab7dd749609f50645..5c3d47a647871961c9e4ad4f51812c379c263249 100644 (file)
@@ -125,6 +125,23 @@ class TestLuksFormat(object):
         encryption.luks_format('abcd', '/dev/foo')
         assert m_call.call_args[0][0] == expected
 
+    @patch('ceph_volume.util.encryption.process.call')
+    def test_luks_format_with_extra_option(self, m_call, conf_ceph_stub):
+        conf_ceph_stub('[global]\nfsid=abcd')
+        expected = [
+            'cryptsetup',
+            '--batch-mode',
+            '--key-size',
+            '512',
+            '--key-file',
+            '-',
+            'luksFormat',
+            '--fake-custom-opt1',
+            '/dev/foo'
+        ]
+        encryption.luks_format('abcd', '/dev/foo', '--fake-custom-opt1')
+        assert m_call.call_args[0][0] == expected
+
     @patch('ceph_volume.util.encryption.process.call')
     def test_luks_format_command_with_custom_size(self, m_call, conf_ceph_stub):
         conf_ceph_stub('[global]\nfsid=abcd\n[osd]\nosd_dmcrypt_key_size=256')
@@ -179,6 +196,25 @@ class TestLuksOpen(object):
         encryption.luks_open('abcd', '/dev/foo', '/dev/bar')
         assert m_call.call_args[0][0] == expected
 
+    @patch('ceph_volume.util.encryption.bypass_workqueue', return_value=False)
+    @patch('ceph_volume.util.encryption.process.call')
+    def test_luks_format_with_extra_option(self, m_call, m_bypass_workqueue, conf_ceph_stub):
+        conf_ceph_stub('[global]\nfsid=abcd')
+        expected = [
+            'cryptsetup',
+            '--key-size',
+            '512',
+            '--key-file',
+            '-',
+            '--allow-discards',
+            'luksOpen',
+            '--fake-custom-opt1',
+            '/dev/foo',
+            '/dev/bar'
+        ]
+        encryption.luks_open('abcd', '/dev/foo', '/dev/bar', options='--fake-custom-opt1')
+        assert m_call.call_args[0][0] == expected
+
     @patch('ceph_volume.util.encryption.bypass_workqueue', return_value=False)
     @patch('ceph_volume.util.encryption.process.call')
     def test_luks_open_command_with_tpm(self, m_call, m_bypass_workqueue, conf_ceph_stub):
index 5de77d21a9a104e1a231f2832f3f52556aee1a8e..659163a0b064e10a430a7ea8904891372fedf8c0 100644 (file)
@@ -3,13 +3,14 @@ import os
 import logging
 import re
 import json
+import shlex
 from ceph_volume import process, conf, terminal
 from ceph_volume.util import constants, system
 from ceph_volume.util.device import Device
 from .prepare import write_keyring
 from .disk import lsblk, device_family, get_part_entry_type, _dd_read
 from packaging import version
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
 
 logger = logging.getLogger(__name__)
 mlogger = terminal.MultiLogger(__name__)
@@ -93,23 +94,27 @@ def create_dmcrypt_key() -> str:
     return key
 
 
-def luks_format(key: str, device: str) -> None:
+def luks_format(key: str, device: str, options: Optional[str] = None) -> None:
     """
     Decrypt (open) an encrypted device, previously prepared with cryptsetup
 
     :param key: dmcrypt secret key, will be used for decrypting
     :param device: Absolute path to device
+    :param options: A list of additional cryptsetup args
     """
-    command = [
+    extra_args: List[str] = []
+    base_cmd: List[str] = [
         'cryptsetup',
         '--batch-mode', # do not prompt
-        '--key-size',
-        get_key_size_from_conf(),
-        '--key-file', # misnomer, should be key
-        '-',          # because we indicate stdin for the key here
-        'luksFormat',
-        device,
+        '--key-size', get_key_size_from_conf(),
+        '--key-file', '-',
     ]
+
+    if options is not None:
+        extra_args: List[str] = shlex.split(options)
+
+    command: List[str] = base_cmd + ["luksFormat"] + extra_args + [device]
+
     process.call(command, stdin=key, terminal_verbose=True, show_command=True)
 
 
@@ -182,7 +187,8 @@ def rename_mapper(current: str, new: str) -> None:
 def luks_open(key: str,
               device: str,
               mapping: str,
-              with_tpm: int = 0) -> None:
+              with_tpm: int = 0,
+              options: Optional[str] = None) -> None:
     """
     Decrypt (open) an encrypted device, previously prepared with cryptsetup
 
@@ -192,8 +198,10 @@ def luks_open(key: str,
     :param device: absolute path to device
     :param mapping: mapping name used to correlate device. Usually a UUID
     :param with_tpm: whether to use tpm2 token enrollment.
+    :param options: A list of additional cryptsetup args
     """
     command: List[str] = []
+    extra_args: List[str] = []
     if with_tpm:
         command = ['/usr/lib/systemd/systemd-cryptsetup',
                    'attach',
@@ -204,17 +212,20 @@ def luks_open(key: str,
         if bypass_workqueue(device):
             command[-1] += ',no-read-workqueue,no-write-workqueue'
     else:
-        command = [
-            'cryptsetup',
-            '--key-size',
-            get_key_size_from_conf(),
-            '--key-file',
-            '-',
-            '--allow-discards',  # allow discards (aka TRIM) requests for device
-            'luksOpen',
-            device,
-            mapping,
+        base_cmd: List[str] = [
+            "cryptsetup",
+            "--key-size", str(get_key_size_from_conf()),
+            "--key-file", "-",
+            "--allow-discards",
         ]
+        if options is not None:
+            extra_args = shlex.split(options)
+        command: List[str] = (
+            base_cmd
+            + ["luksOpen"]
+            + extra_args
+            + [device, mapping]
+        )
 
         if bypass_workqueue(device):
             command.extend(['--perf-no_read_workqueue',
@@ -395,7 +406,11 @@ def legacy_encrypted(device):
             break
     return metadata
 
-def prepare_dmcrypt(key, device, mapping):
+def prepare_dmcrypt(key: str,
+                    device: str,
+                    mapping: str,
+                    format_options: Optional[str] = None,
+                    open_options: Optional[str] = None):
     """
     Helper for devices that are encrypted. The operations needed for
     block, db, wal, or data/journal devices are all the same
@@ -405,12 +420,14 @@ def prepare_dmcrypt(key, device, mapping):
     # format data device
     luks_format(
         key,
-        device
+        device,
+        format_options
     )
     luks_open(
         key,
         device,
-        mapping
+        mapping,
+        options=open_options
     )
     return '/dev/mapper/%s' % mapping