]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume: make TPM2 PCR policy configurable (default to PCR 7) 68681/head
authorGuillaume Abrioux <gabrioux@ibm.com>
Wed, 29 Apr 2026 09:17:23 +0000 (11:17 +0200)
committerGuillaume Abrioux <gabrioux@ibm.com>
Thu, 30 Apr 2026 08:18:13 +0000 (08:18 +0000)
tpm enrollment for dmcrypt OSDs is hardcoded to systemd-cryptenroll
--tpm2-pcrs 9+12 which ties the LUKS key to initrd and kernel
command line measurements, which is brittle on RHEL image mode
systems: after a bootc switch, the kernel, initrd, or cmdline often
change, the PCRs move, and the volume won't unlock until you re-enroll
or fall back to another key.

typical error:

```
Apr 27 14:17:25 ceph-jx5fq20u bash[4289]: Running command: nsenter --mount=/rootfs/proc/1/ns/mnt --ipc=/rootfs/proc/1/ns/ipc --net=/rootfs/proc/1/ns/net --uts=/rootfs/proc/1/ns/uts /usr/lib/systemd/systemd-cryptsetup attach M3zE7r-qsGZ-xs0T-610d-SJNZ-U89x-J0cJq8 /dev/ceph-cac05fb6-51d3-4a60-9fc1-4958c568b433/osd-block-b1a495a0-e1a4-4888-baf9-7990f45f1e56 - tpm2-device=auto,discard,headless=true,nofail
Apr 27 14:17:26 ceph-jx5fq20u ceph-e5520e2c-420d-11f1-a7b9-5254001191fb-osd-0-activate[4300]:  stderr: Failed to unseal secret using TPM2: Operation not permitted
Apr 27 14:17:26 ceph-jx5fq20u bash[4289]:  stderr: Failed to unseal secret using TPM2: Operation not permitted
```

The patch makes the PCR set configurable and defaults to 7 so bootc style
deployments behave correctly.

Fixes: https://tracker.ceph.com/issues/76318
Signed-off-by: Guillaume Abrioux <gabrioux@ibm.com>
(cherry picked from commit ab7015c693a5e156e677e908378631d1bbdcb4f6)

src/ceph-volume/ceph_volume/devices/lvm/batch.py
src/ceph-volume/ceph_volume/devices/lvm/common.py
src/ceph-volume/ceph_volume/devices/raw/common.py
src/ceph-volume/ceph_volume/objectstore/baseobjectstore.py
src/ceph-volume/ceph_volume/tests/objectstore/test_baseobjectstore.py

index ff97009a61d95b435a25faf5dead53846f316cea..75e59dae83995d5e44fdef182cccd01c582393c6 100644 (file)
@@ -215,6 +215,14 @@ class Batch(object):
             help='Whether encrypted OSDs should be enrolled with TPM.',
             action='store_true'
         )
+        parser.add_argument(
+            '--tpm2-pcrs',
+            dest='tpm2_pcrs',
+            help=('PCRs for systemd-cryptenroll --tpm2-pcrs when using --with-tpm '
+                  '(default binds to Secure Boot policy, see systemd-cryptenroll(1)).'),
+            default='7',
+            type=str,
+        )
         parser.add_argument(
             '--crush-device-class',
             dest='crush_device_class',
@@ -389,6 +397,7 @@ class Batch(object):
             'bluestore',
             'dmcrypt',
             'with_tpm',
+            'tpm2_pcrs',
             'crush_device_class',
             'no_systemd',
             'dmcrypt_format_opts',
index fa39ac6c96229f23d52601e86dc7984bc0d28e8b..b9c488d14d29ead8cd3379ed4ecbc43de5d05ad4 100644 (file)
@@ -97,6 +97,13 @@ common_args: Dict[str, Any] = {
         'help': 'Whether encrypted OSDs should be enrolled with TPM.',
         'action': 'store_true'
     },
+    '--tpm2-pcrs': {
+        'dest': 'tpm2_pcrs',
+        'help': ('PCRs for systemd-cryptenroll --tpm2-pcrs when using --with-tpm '
+                 '(default binds to Secure Boot policy, see systemd-cryptenroll(1)).'),
+        'default': '7',
+        'type': str,
+    },
     '--no-systemd': {
         'dest': 'no_systemd',
         'action': 'store_true',
index 77db2f7daf9893764eceaf7d8f57f1094ae90bf1..e738bdc6e3cfd1270a67887a5e65da0a766d0cdc 100644 (file)
@@ -64,6 +64,14 @@ def create_parser(prog: str, description: str) -> argparse.ArgumentParser:
         help='Whether encrypted OSDs should be enrolled with TPM.',
         action='store_true'
     ),
+    parser.add_argument(
+        '--tpm2-pcrs',
+        dest='tpm2_pcrs',
+        help=('PCRs for systemd-cryptenroll --tpm2-pcrs when using --with-tpm '
+              '(default binds to Secure Boot policy, see systemd-cryptenroll(1)).'),
+        default='7',
+        type=str,
+    ),
     parser.add_argument(
         '--osd-id',
         help='Reuse an existing OSD id',
index e9b0ac9d630eda1ab8f9582867afe60a9e71b77c..72a83df7f43372bb8e53828a4fc9e0a5d0d85278 100644 (file)
@@ -37,6 +37,7 @@ class BaseObjectStore:
         self.block_device_path: str = ''
         self.dmcrypt_key: str = encryption_utils.create_dmcrypt_key()
         self.with_tpm: int = int(getattr(self.args, 'with_tpm', False))
+        self.tpm2_pcrs: str = getattr(self.args, 'tpm2_pcrs', '7')
         self.method: str = ''
         self.osd_path: str = ''
         self.key: Optional[str] = None
@@ -223,6 +224,7 @@ class BaseObjectStore:
         """
         Enrolls a device with TPM2 (Trusted Platform Module 2.0) using systemd-cryptenroll.
         This method creates a temporary file to store the dmcrypt key and uses it to enroll the device.
+        PCR selection follows `--tpm2-pcrs` on the ceph-volume CLI (`self.tpm2_pcrs`, default is "7").
 
         Args:
             device (str): The device path to be enrolled with TPM2.
@@ -236,7 +238,7 @@ class BaseObjectStore:
                 temp_file_name: str = temp_file.name.replace('/rootfs', '', 1)
                 cmd: List[str] = ['systemd-cryptenroll', '--tpm2-device=auto',
                                   device, '--unlock-key-file', temp_file_name,
-                                  '--tpm2-pcrs', '9+12', '--wipe-slot', 'tpm2']
+                                  '--tpm2-pcrs', self.tpm2_pcrs, '--wipe-slot', 'tpm2']
                 process.call(cmd, run_on_host=True, show_command=True)
 
     def add_label(self, key: str,
index fc5705c6ae5388ee4bffdfc67c96dc7abe45b7fa..5a254cbd4bd28a98af80fd1238cd79164c70353b 100644 (file)
@@ -212,6 +212,39 @@ class TestBaseObjectStore:
         with pytest.raises(NotImplementedError):
             BaseObjectStore([]).activate()
 
+    def test_enroll_tpm2_default_pcrs(self, monkeypatch, factory):
+        captured: dict = {}
+
+        def fake_call(cmd, **kwargs):
+            captured['cmd'] = cmd
+            return ([], '', 0)
+
+        monkeypatch.setattr(
+            'ceph_volume.objectstore.baseobjectstore.process.call', fake_call)
+        args = factory(with_tpm=True)
+        bo = BaseObjectStore(args)
+        bo.dmcrypt_key = 'sekrit'
+        bo.enroll_tpm2('/dev/sdz')
+        assert '--tpm2-pcrs' in captured['cmd']
+        i = captured['cmd'].index('--tpm2-pcrs')
+        assert captured['cmd'][i + 1] == '7'
+
+    def test_enroll_tpm2_custom_pcrs(self, monkeypatch, factory):
+        captured: dict = {}
+
+        def fake_call(cmd, **kwargs):
+            captured['cmd'] = cmd
+            return ([], '', 0)
+
+        monkeypatch.setattr(
+            'ceph_volume.objectstore.baseobjectstore.process.call', fake_call)
+        args = factory(with_tpm=True, tpm2_pcrs='9+12')
+        bo = BaseObjectStore(args)
+        bo.dmcrypt_key = 'sekrit'
+        bo.enroll_tpm2('/dev/sdz')
+        i = captured['cmd'].index('--tpm2-pcrs')
+        assert captured['cmd'][i + 1] == '9+12'
+
     @patch('ceph_volume.objectstore.baseobjectstore.prepare_utils.create_key', Mock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
     def setup_method(self, m_create_key):
         self.b = BaseObjectStore([])