From 159c30d29f009c07e51a4bd7cd5ecea8cf6c8739 Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Fri, 24 Apr 2020 16:45:02 +0200 Subject: [PATCH] ceph-volume: add dmcrypt support in raw mode This commit adds the dmcrypt support in `ceph-volume raw` mode. Note about `ceph-volume raw list` change: Given `lsblk -J` (json output) option isn't available on all OS, I came up with adding '--inverse' option to the existing command which allows us to get the mapper devices list in that command output. Not listing root devices containing partitions shouldn't have side effect since we are in `ceph-volume raw` context. example: running `lsblk --paths --nodeps --output=NAME --noheadings` doesn't allow to get the mapper list because the output is like following : $ lsblk --paths --nodeps --output=NAME --noheadings /dev/sda /dev/sdb /dev/sdc /dev/sdd the dmcrypt mappers are hidden because of the `--nodeps` given they are displayed as a dependency. $ lsblk --paths --output=NAME --noheadings /dev/sda |-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-block-dmcrypt `-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-db-dmcrypt /dev/sdb /dev/sdc /dev/sdd adding `--inverse` is a trick to get around this issue, the counterpart is that we can't list root devices if they contain at least one partition but this shouldn't be an issue in `ceph-volume raw` context given we only deal with raw devices. Fixes: https://tracker.ceph.com/issues/45959 Signed-off-by: Guillaume Abrioux --- .../ceph_volume/devices/raw/common.py | 5 ++ .../ceph_volume/devices/raw/list.py | 28 ++++++++++- .../ceph_volume/devices/raw/prepare.py | 48 +++++++++++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/ceph-volume/ceph_volume/devices/raw/common.py b/src/ceph-volume/ceph_volume/devices/raw/common.py index d34a2941d1684..197af3cc848f8 100644 --- a/src/ceph-volume/ceph_volume/devices/raw/common.py +++ b/src/ceph-volume/ceph_volume/devices/raw/common.py @@ -45,4 +45,9 @@ def create_parser(prog, description): dest='block_wal', help='Path to bluestore block.wal block device' ) + parser.add_argument( + '--dmcrypt', + action='store_true', + help='Enable device encryption via dm-crypt', + ) return parser diff --git a/src/ceph-volume/ceph_volume/devices/raw/list.py b/src/ceph-volume/ceph_volume/devices/raw/list.py index b04f55cd8da0a..bb15bf199012b 100644 --- a/src/ceph-volume/ceph_volume/devices/raw/list.py +++ b/src/ceph-volume/ceph_volume/devices/raw/list.py @@ -30,8 +30,34 @@ class List(object): if not devs: logger.debug('Listing block devices via lsblk...') devs = [] + # adding '--inverse' allows us to get the mapper devices list in that command output. + # not listing root devices containing partitions shouldn't have side effect since we are + # in `ceph-volume raw` context. + # + # example: + # running `lsblk --paths --nodeps --output=NAME --noheadings` doesn't allow to get the mapper list + # because the output is like following : + # + # $ lsblk --paths --nodeps --output=NAME --noheadings + # /dev/sda + # /dev/sdb + # /dev/sdc + # /dev/sdd + # + # the dmcrypt mappers are hidden because of the `--nodeps` given they are displayed as a dependency. + # + # $ lsblk --paths --output=NAME --noheadings + # /dev/sda + # |-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-block-dmcrypt + # `-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-db-dmcrypt + # /dev/sdb + # /dev/sdc + # /dev/sdd + # + # adding `--inverse` is a trick to get around this issue, the counterpart is that we can't list root devices if they contain + # at least one partition but this shouldn't be an issue in `ceph-volume raw` context given we only deal with raw devices. out, err, ret = process.call([ - 'lsblk', '--paths', '--nodeps', '--output=NAME', '--noheadings' + 'lsblk', '--paths', '--nodeps', '--output=NAME', '--noheadings', '--inverse' ]) assert not ret devs = out diff --git a/src/ceph-volume/ceph_volume/devices/raw/prepare.py b/src/ceph-volume/ceph_volume/devices/raw/prepare.py index cb5c59ce40f53..d0d740c1c84b0 100644 --- a/src/ceph-volume/ceph_volume/devices/raw/prepare.py +++ b/src/ceph-volume/ceph_volume/devices/raw/prepare.py @@ -1,8 +1,11 @@ from __future__ import print_function import json import logging +import os from textwrap import dedent from ceph_volume.util import prepare as prepare_utils +from ceph_volume.util import encryption as encryption_utils +from ceph_volume.util import disk from ceph_volume.util import system from ceph_volume import conf, decorators, terminal from ceph_volume.devices.lvm.common import rollback_osd @@ -10,6 +13,27 @@ from .common import create_parser logger = logging.getLogger(__name__) +def prepare_dmcrypt(key, device, device_type, fsid): + """ + Helper for devices that are encrypted. The operations needed for + block, db, wal, or data/journal devices are all the same + """ + if not device: + return '' + kname = disk.lsblk(device)['KNAME'] + mapping = 'ceph-{}-{}-{}-dmcrypt'.format(fsid, kname, device_type) + # format data device + encryption_utils.luks_format( + key, + device + ) + encryption_utils.luks_open( + key, + device, + mapping + ) + + return '/dev/mapper/{}'.format(mapping) def prepare_bluestore(block, wal, db, secrets, osd_id, fsid, tmpfs): """ @@ -22,6 +46,12 @@ def prepare_bluestore(block, wal, db, secrets, osd_id, fsid, tmpfs): """ cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key()) + if secrets.get('dmcrypt_key'): + key = secrets['dmcrypt_key'] + block = prepare_dmcrypt(key, block, 'block', fsid) + wal = prepare_dmcrypt(key, wal, 'wal', fsid) + db = prepare_dmcrypt(key, db, 'db', fsid) + # create the directory prepare_utils.create_osd_path(osd_id, tmpfs=tmpfs) # symlink the block @@ -64,7 +94,8 @@ class Prepare(object): logger.info('will rollback OSD ID creation') rollback_osd(self.args, self.osd_id) raise - terminal.success("ceph-volume raw prepare successful for: %s" % self.args.data) + dmcrypt_log = 'dmcrypt' if args.dmcrypt else 'clear' + terminal.success("ceph-volume raw {} prepare successful for: {}".format(dmcrypt_log, self.args.data)) def get_cluster_fsid(self): """ @@ -79,6 +110,13 @@ class Prepare(object): @decorators.needs_root def prepare(self): secrets = {'cephx_secret': prepare_utils.create_key()} + encrypted = 1 if self.args.dmcrypt else 0 + cephx_lockbox_secret = '' if not encrypted else prepare_utils.create_key() + + if encrypted: + secrets['dmcrypt_key'] = os.getenv('CEPH_VOLUME_DMCRYPT_SECRET') + secrets['cephx_lockbox_secret'] = cephx_lockbox_secret # dummy value to make `ceph osd new` not complaining + osd_fsid = system.generate_uuid() crush_device_class = self.args.crush_device_class if crush_device_class: @@ -94,6 +132,7 @@ class Prepare(object): # reuse a given ID if it exists, otherwise create a new ID self.osd_id = prepare_utils.create_id( osd_fsid, json.dumps(secrets)) + prepare_bluestore( self.args.data, wal, @@ -112,8 +151,6 @@ class Prepare(object): Once the OSD is ready, an ad-hoc systemd unit will be enabled so that it can later get activated and the OSD daemon can get started. - Encryption is not supported. - ceph-volume raw prepare --bluestore --data {device} DB and WAL devices are supported. @@ -132,5 +169,10 @@ class Prepare(object): if not self.args.bluestore: terminal.error('must specify --bluestore (currently the only supported backend)') raise SystemExit(1) + if self.args.dmcrypt and not os.getenv('CEPH_VOLUME_DMCRYPT_SECRET'): + terminal.error('encryption was requested (--dmcrypt) but environment variable ' \ + 'CEPH_VOLUME_DMCRYPT_SECRET is not set, you must set ' \ + 'this variable to provide a dmcrypt secret.') + raise SystemExit(1) self.safe_prepare(self.args) -- 2.39.5