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>
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',
'bluestore',
'dmcrypt',
'with_tpm',
+ 'tpm2_pcrs',
'crush_device_class',
'no_systemd',
'dmcrypt_format_opts',
'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',
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',
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
"""
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.
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,
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([])