Skip to content

Commit 2b06a57

Browse files
authored
Merge pull request ceph#58140 from guits/cv-tpm2-support
ceph-volume: add TPM2 token enrollment support for encrypted OSDs
2 parents 287e733 + fec896b commit 2b06a57

File tree

29 files changed

+1121
-279
lines changed

29 files changed

+1121
-279
lines changed

doc/ceph-volume/lvm/prepare.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ For enabling :ref:`encryption <ceph-volume-lvm-encryption>`, the ``--dmcrypt`` f
6161

6262
ceph-volume lvm prepare --bluestore --dmcrypt --data vg/lv
6363

64+
Starting with Ceph Squid, you can opt for TPM2 token enrollment for the created LUKS2 devices with the ``--with-tpm`` flag:
65+
66+
.. prompt:: bash #
67+
68+
ceph-volume lvm prepare --bluestore --dmcrypt --with-tpm --data vg/lv
69+
6470
If a ``block.db`` device or a ``block.wal`` device is needed, it can be
6571
specified with ``--block.db`` or ``--block.wal``. These can be physical
6672
devices, partitions, or logical volumes. ``block.db`` and ``block.wal`` are

doc/cephadm/services/osd.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,21 @@ This example would deploy all OSDs with encryption enabled.
666666
all: true
667667
encrypted: true
668668
669+
Ceph Squid onwards support tpm2 token enrollment to LUKS2 devices.
670+
You can add the `tpm2` to your OSD spec:
671+
672+
.. code-block:: yaml
673+
674+
service_type: osd
675+
service_id: example_osd_spec_with_tpm2
676+
placement:
677+
host_pattern: '*'
678+
spec:
679+
data_devices:
680+
all: true
681+
encrypted: true
682+
tpm2: true
683+
669684
See a full list in the DriveGroupSpecs
670685

671686
.. py:currentmodule:: ceph.deployment.drive_group

src/ceph-volume/ceph_volume/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
1+
import os
2+
import logging
13
from collections import namedtuple
24

35

46
sys_info = namedtuple('sys_info', ['devices'])
57
sys_info.devices = dict()
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class AllowLoopDevices:
12+
allow = False
13+
warned = False
14+
15+
@classmethod
16+
def __call__(cls) -> bool:
17+
val = os.environ.get("CEPH_VOLUME_ALLOW_LOOP_DEVICES", "false").lower()
18+
if val not in ("false", 'no', '0'):
19+
cls.allow = True
20+
if not cls.warned:
21+
logger.warning(
22+
"CEPH_VOLUME_ALLOW_LOOP_DEVICES is set in your "
23+
"environment, so we will allow the use of unattached loop"
24+
" devices as disks. This feature is intended for "
25+
"development purposes only and will never be supported in"
26+
" production. Issues filed based on this behavior will "
27+
"likely be ignored."
28+
)
29+
cls.warned = True
30+
return cls.allow
631

732

833
class UnloadedConfig(object):
@@ -14,6 +39,8 @@ class UnloadedConfig(object):
1439
def __getattr__(self, *a):
1540
raise RuntimeError("No valid ceph configuration file was loaded.")
1641

42+
43+
allow_loop_devices = AllowLoopDevices()
1744
conf = namedtuple('config', ['ceph', 'cluster', 'verbosity', 'path', 'log_path', 'dmcrypt_no_workqueue'])
1845
conf.ceph = UnloadedConfig()
1946
conf.dmcrypt_no_workqueue = None

src/ceph-volume/ceph_volume/devices/lvm/batch.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ def __init__(self, argv):
267267
action=arg_validators.DmcryptAction,
268268
help='Enable device encryption via dm-crypt',
269269
)
270+
parser.add_argument(
271+
'--with-tpm',
272+
dest='with_tpm',
273+
help='Whether encrypted OSDs should be enrolled with TPM.',
274+
action='store_true'
275+
)
270276
parser.add_argument(
271277
'--crush-device-class',
272278
dest='crush_device_class',
@@ -423,6 +429,7 @@ def _execute(self, plan):
423429
global_args = [
424430
'bluestore',
425431
'dmcrypt',
432+
'with_tpm',
426433
'crush_device_class',
427434
'no_systemd',
428435
]

src/ceph-volume/ceph_volume/devices/lvm/common.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ def rollback_osd(args, osd_id=None):
8383
'action': arg_validators.DmcryptAction,
8484
'help': 'Enable device encryption via dm-crypt',
8585
},
86+
'--with-tpm': {
87+
'dest': 'with_tpm',
88+
'help': 'Whether encrypted OSDs should be enrolled with TPM.',
89+
'action': 'store_true'
90+
},
8691
'--no-systemd': {
8792
'dest': 'no_systemd',
8893
'action': 'store_true',

src/ceph-volume/ceph_volume/devices/raw/activate.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,19 @@ def main(self):
3939
'--device',
4040
help='The device for the OSD to start'
4141
)
42+
parser.add_argument(
43+
'--devices',
44+
help='The device for the OSD to start',
45+
nargs='*',
46+
default=[]
47+
)
4248
parser.add_argument(
4349
'--osd-id',
4450
help='OSD ID to activate'
4551
)
4652
parser.add_argument(
4753
'--osd-uuid',
54+
dest='osd_fsid',
4855
help='OSD UUID to active'
4956
)
5057
parser.add_argument(
@@ -82,15 +89,11 @@ def main(self):
8289
return
8390
self.args = parser.parse_args(self.argv)
8491

85-
devs = []
8692
if self.args.device:
87-
devs = [self.args.device]
88-
if self.args.block_wal:
89-
devs.append(self.args.block_wal)
90-
if self.args.block_db:
91-
devs.append(self.args.block_db)
93+
if self.args.devices is None:
94+
self.args.devices = [self.args.device]
95+
else:
96+
self.args.devices.append(self.args.device)
97+
9298
self.objectstore = objectstore.mapping['RAW'][self.args.objectstore](args=self.args)
93-
self.objectstore.activate(devs=devs,
94-
start_osd_id=self.args.osd_id,
95-
start_osd_uuid=self.args.osd_uuid,
96-
tmpfs=not self.args.no_tmpfs)
99+
self.objectstore.activate()

src/ceph-volume/ceph_volume/devices/raw/common.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import argparse
22
from ceph_volume.util import arg_validators
33

4-
def create_parser(prog, description):
4+
def create_parser(prog: str, description: str) -> argparse.ArgumentParser:
55
"""
66
Both prepare and create share the same parser, those are defined here to
77
avoid duplication
@@ -58,6 +58,12 @@ def create_parser(prog, description):
5858
action=arg_validators.DmcryptAction,
5959
help='Enable device encryption via dm-crypt',
6060
)
61+
parser.add_argument(
62+
'--with-tpm',
63+
dest='with_tpm',
64+
help='Whether encrypted OSDs should be enrolled with TPM.',
65+
action='store_true'
66+
),
6167
parser.add_argument(
6268
'--osd-id',
6369
help='Reuse an existing OSD id',

src/ceph-volume/ceph_volume/devices/raw/list.py

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from textwrap import dedent
66
from ceph_volume import decorators, process
77
from ceph_volume.util import disk
8-
from typing import Any, Dict, List
8+
from typing import Any, Dict, List as _List
99

1010
logger = logging.getLogger(__name__)
1111

@@ -20,50 +20,35 @@ def direct_report(devices):
2020
_list = List([])
2121
return _list.generate(devices)
2222

23-
def _get_bluestore_info(dev):
23+
def _get_bluestore_info(dev: str) -> Dict[str, Any]:
24+
result: Dict[str, Any] = {}
2425
out, err, rc = process.call([
2526
'ceph-bluestore-tool', 'show-label',
2627
'--dev', dev], verbose_on_failure=False)
2728
if rc:
2829
# ceph-bluestore-tool returns an error (below) if device is not bluestore OSD
2930
# > unable to read label for <device>: (2) No such file or directory
3031
# but it's possible the error could be for a different reason (like if the disk fails)
31-
logger.debug('assuming device {} is not BlueStore; ceph-bluestore-tool failed to get info from device: {}\n{}'.format(dev, out, err))
32-
return None
33-
oj = json.loads(''.join(out))
34-
if dev not in oj:
35-
# should be impossible, so warn
36-
logger.warning('skipping device {} because it is not reported in ceph-bluestore-tool output: {}'.format(dev, out))
37-
return None
38-
try:
39-
r = {
40-
'osd_uuid': oj[dev]['osd_uuid'],
41-
}
42-
if oj[dev]['description'] == 'main':
43-
whoami = oj[dev]['whoami']
44-
r.update({
45-
'type': 'bluestore',
46-
'osd_id': int(whoami),
47-
'ceph_fsid': oj[dev]['ceph_fsid'],
48-
'device': dev,
49-
})
50-
elif oj[dev]['description'] == 'bluefs db':
51-
r['device_db'] = dev
52-
elif oj[dev]['description'] == 'bluefs wal':
53-
r['device_wal'] = dev
54-
return r
55-
except KeyError as e:
56-
# this will appear for devices that have a bluestore header but aren't valid OSDs
57-
# for example, due to incomplete rollback of OSDs: https://tracker.ceph.com/issues/51869
58-
logger.error('device {} does not have all BlueStore data needed to be a valid OSD: {}\n{}'.format(dev, out, e))
59-
return None
32+
logger.debug(f'assuming device {dev} is not BlueStore; ceph-bluestore-tool failed to get info from device: {out}\n{err}')
33+
else:
34+
oj = json.loads(''.join(out))
35+
if dev not in oj:
36+
# should be impossible, so warn
37+
logger.warning(f'skipping device {dev} because it is not reported in ceph-bluestore-tool output: {out}')
38+
try:
39+
result = disk.bluestore_info(dev, oj)
40+
except KeyError as e:
41+
# this will appear for devices that have a bluestore header but aren't valid OSDs
42+
# for example, due to incomplete rollback of OSDs: https://tracker.ceph.com/issues/51869
43+
logger.error(f'device {dev} does not have all BlueStore data needed to be a valid OSD: {out}\n{e}')
44+
return result
6045

6146

6247
class List(object):
6348

6449
help = 'list BlueStore OSDs on raw devices'
6550

66-
def __init__(self, argv):
51+
def __init__(self, argv: _List[str]) -> None:
6752
self.argv = argv
6853

6954
def is_atari_partitions(self, _lsblk: Dict[str, Any]) -> bool:
@@ -81,7 +66,7 @@ def is_atari_partitions(self, _lsblk: Dict[str, Any]) -> bool:
8166
return True
8267
return False
8368

84-
def exclude_atari_partitions(self, _lsblk_all: Dict[str, Any]) -> List[Dict[str, Any]]:
69+
def exclude_atari_partitions(self, _lsblk_all: Dict[str, Any]) -> _List[Dict[str, Any]]:
8570
return [_lsblk for _lsblk in _lsblk_all if not self.is_atari_partitions(_lsblk)]
8671

8772
def generate(self, devs=None):
@@ -113,7 +98,7 @@ def generate(self, devs=None):
11398
logger.debug('inspecting devices: {}'.format(devs))
11499
for info_device in info_devices:
115100
bs_info = _get_bluestore_info(info_device['NAME'])
116-
if bs_info is None:
101+
if not bs_info:
117102
# None is also returned in the rare event that there is an issue reading info from
118103
# a BlueStore disk, so be sure to log our assumption that it isn't bluestore
119104
logger.info('device {} does not have BlueStore information'.format(info_device['NAME']))

src/ceph-volume/ceph_volume/devices/raw/prepare.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ def main(self):
4242
self.args = parser.parse_args(self.argv)
4343
if self.args.bluestore:
4444
self.args.objectstore = 'bluestore'
45-
if self.args.dmcrypt and not os.getenv('CEPH_VOLUME_DMCRYPT_SECRET'):
46-
terminal.error('encryption was requested (--dmcrypt) but environment variable ' \
47-
'CEPH_VOLUME_DMCRYPT_SECRET is not set, you must set ' \
48-
'this variable to provide a dmcrypt secret.')
49-
raise SystemExit(1)
45+
if self.args.dmcrypt:
46+
if not self.args.with_tpm and not os.getenv('CEPH_VOLUME_DMCRYPT_SECRET'):
47+
terminal.error('encryption was requested (--dmcrypt) but environment variable ' \
48+
'CEPH_VOLUME_DMCRYPT_SECRET is not set, you must set ' \
49+
'this variable to provide a dmcrypt secret or use --with-tpm ' \
50+
'in order to enroll a tpm2 token.')
51+
raise SystemExit(1)
5052

5153
self.objectstore = objectstore.mapping['RAW'][self.args.objectstore](args=self.args)
5254
self.objectstore.safe_prepare(self.args)

src/ceph-volume/ceph_volume/objectstore/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from . import lvmbluestore
22
from . import rawbluestore
3+
from typing import Any, Dict
34

4-
mapping = {
5+
6+
mapping: Dict[str, Any] = {
57
'LVM': {
68
'bluestore': lvmbluestore.LvmBlueStore
79
},

0 commit comments

Comments
 (0)