]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/encryption.py
cefd6094bd09eb7d4fcaafbc8e74f4819e4621ac
4 from ceph_volume
import process
, conf
, terminal
5 from ceph_volume
.util
import constants
, system
6 from ceph_volume
.util
.device
import Device
7 from .prepare
import write_keyring
8 from .disk
import lsblk
, device_family
, get_part_entry_type
10 logger
= logging
.getLogger(__name__
)
11 mlogger
= terminal
.MultiLogger(__name__
)
13 def get_key_size_from_conf():
15 Return the osd dmcrypt key size from config file.
18 default_key_size
= '512'
19 key_size
= conf
.ceph
.get_safe(
21 'osd_dmcrypt_key_size',
22 default
='512', check_valid
=False)
24 if key_size
not in ['256', '512']:
25 logger
.warning(("Invalid value set for osd_dmcrypt_key_size ({}). "
26 "Falling back to {}bits".format(key_size
, default_key_size
)))
27 return default_key_size
31 def create_dmcrypt_key():
33 Create the secret dm-crypt key (KEK) used to encrypt/decrypt the Volume Key.
35 random_string
= os
.urandom(128)
36 key
= base64
.b64encode(random_string
).decode('utf-8')
40 def luks_format(key
, device
):
42 Decrypt (open) an encrypted device, previously prepared with cryptsetup
44 :param key: dmcrypt secret key, will be used for decrypting
45 :param device: Absolute path to device
49 '--batch-mode', # do not prompt
51 get_key_size_from_conf(),
52 '--key-file', # misnomer, should be key
53 '-', # because we indicate stdin for the key here
57 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
60 def plain_open(key
, device
, mapping
):
62 Decrypt (open) an encrypted device, previously prepared with cryptsetup in plain mode
64 .. note: ceph-disk will require an additional b64decode call for this to work
66 :param key: dmcrypt secret key
67 :param device: absolute path to device
68 :param mapping: mapping name used to correlate device. Usually a UUID
74 '--allow-discards', # allow discards (aka TRIM) requests for device
82 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
85 def luks_open(key
, device
, mapping
):
87 Decrypt (open) an encrypted device, previously prepared with cryptsetup
89 .. note: ceph-disk will require an additional b64decode call for this to work
91 :param key: dmcrypt secret key
92 :param device: absolute path to device
93 :param mapping: mapping name used to correlate device. Usually a UUID
98 get_key_size_from_conf(),
101 '--allow-discards', # allow discards (aka TRIM) requests for device
106 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
109 def dmcrypt_close(mapping
):
111 Encrypt (close) a device, previously decrypted with cryptsetup
115 if not os
.path
.exists(mapping
):
116 logger
.debug('device mapper path does not exist %s' % mapping
)
117 logger
.debug('will skip cryptsetup removal')
119 # don't be strict about the remove call, but still warn on the terminal if it fails
120 process
.run(['cryptsetup', 'remove', mapping
], stop_on_error
=False)
123 def get_dmcrypt_key(osd_id
, osd_fsid
, lockbox_keyring
=None):
125 Retrieve the dmcrypt (secret) key stored initially on the monitor. The key
126 is sent initially with JSON, and the Monitor then mangles the name to
127 ``dm-crypt/osd/<fsid>/luks``
129 The ``lockbox.keyring`` file is required for this operation, and it is
130 assumed it will exist on the path for the same OSD that is being activated.
131 To support scanning, it is optionally configurable to a custom location
132 (e.g. inside a lockbox partition mounted in a temporary location)
134 if lockbox_keyring
is None:
135 lockbox_keyring
= '/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)
136 name
= 'client.osd-lockbox.%s' % osd_fsid
137 config_key
= 'dm-crypt/osd/%s/luks' % osd_fsid
139 mlogger
.info(f
'Running ceph config-key get {config_key}')
140 stdout
, stderr
, returncode
= process
.call(
143 '--cluster', conf
.cluster
,
145 '--keyring', lockbox_keyring
,
151 logfile_verbose
=False
154 raise RuntimeError('Unable to retrieve dmcrypt secret')
155 return ' '.join(stdout
).strip()
158 def write_lockbox_keyring(osd_id
, osd_fsid
, secret
):
160 Helper to write the lockbox keyring. This is needed because the bluestore OSD will
161 not persist the keyring, and it can't be stored in the data device for filestore because
162 at the time this is needed, the device is encrypted.
164 For bluestore: A tmpfs filesystem is mounted, so the path can get written
165 to, but the files are ephemeral, which requires this file to be created
166 every time it is activated.
167 For filestore: The path for the OSD would exist at this point even if no
168 OSD data device is mounted, so the keyring is written to fetch the key, and
169 then the data device is mounted on that directory, making the keyring
172 if os
.path
.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)):
175 name
= 'client.osd-lockbox.%s' % osd_fsid
179 keyring_name
='lockbox.keyring',
186 Capture the metadata information of a possibly encrypted device, returning
187 a dictionary with all the values found (if any).
189 An encrypted device will contain information about a device. Example
190 successful output looks like::
192 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
193 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
195 cipher: aes-xts-plain64
199 size: 20740063 sectors
202 As long as the mapper device is in 'open' state, the ``status`` call will work.
204 :param device: Absolute path or UUID of the device mapper
211 out
, err
, code
= process
.call(command
, show_command
=True, verbose_on_failure
=False)
215 logger
.warning('failed to detect device mapper information')
218 # get rid of lines that might not be useful to construct the report:
219 if not line
.startswith(' '):
222 column
, value
= line
.split(': ')
225 metadata
[column
.strip()] = value
.strip().strip('"')
229 def legacy_encrypted(device
):
231 Detect if a device was encrypted with ceph-disk or not. In the case of
232 encrypted devices, include the type of encryption (LUKS, or PLAIN), and
233 infer what the lockbox partition is.
235 This function assumes that ``device`` will be a partition.
237 if os
.path
.isdir(device
):
238 mounts
= system
.Mounts(paths
=True).get_mounts()
239 # yes, rebind the device variable here because a directory isn't going
240 # to help with parsing
241 device
= mounts
.get(device
, [None])[0]
243 raise RuntimeError('unable to determine the device mounted at %s' % device
)
244 metadata
= {'encrypted': False, 'type': None, 'lockbox': '', 'device': device
}
245 # check if the device is online/decrypted first
246 active_mapper
= status(device
)
248 # normalize a bit to ensure same values regardless of source
249 metadata
['type'] = active_mapper
['type'].lower().strip('12') # turn LUKS1 or LUKS2 into luks
250 metadata
['encrypted'] = True if metadata
['type'] in ['plain', 'luks'] else False
251 # The true device is now available to this function, so it gets
252 # re-assigned here for the lockbox checks to succeed (it is not
253 # possible to guess partitions from a device mapper device otherwise
254 device
= active_mapper
.get('device', device
)
255 metadata
['device'] = device
257 uuid
= get_part_entry_type(device
)
258 guid_match
= constants
.ceph_disk_guids
.get(uuid
, {})
259 encrypted_guid
= guid_match
.get('encrypted', False)
261 metadata
['encrypted'] = True
262 metadata
['type'] = guid_match
['encryption_type']
264 # Lets find the lockbox location now, to do this, we need to find out the
265 # parent device name for the device so that we can query all of its
266 # associated devices and *then* look for one that has the 'lockbox' label
267 # on it. Thanks for being awesome ceph-disk
268 disk_meta
= lsblk(device
, abspath
=True)
271 parent_device
= disk_meta
['PKNAME']
272 # With the parent device set, we can now look for the lockbox listing associated devices
273 devices
= [Device(i
['NAME']) for i
in device_family(parent_device
)]
275 if d
.ceph_disk
.type == 'lockbox':
276 metadata
['lockbox'] = d
.path