]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/encryption.py
4 from ceph_volume
import process
, conf
5 from ceph_volume
.util
import constants
, system
6 from .prepare
import write_keyring
7 from .disk
import lsblk
, device_family
, get_part_entry_type
9 logger
= logging
.getLogger(__name__
)
12 def create_dmcrypt_key():
14 Create the secret dm-crypt key used to decrypt a device.
16 # get the customizable dmcrypt key size (in bits) from ceph.conf fallback
17 # to the default of 1024
18 dmcrypt_key_size
= conf
.ceph
.get_safe(
20 'osd_dmcrypt_key_size',
23 # The size of the key is defined in bits, so we must transform that
24 # value to bytes (dividing by 8) because we read in bytes, not bits
25 random_string
= os
.urandom(dmcrypt_key_size
/ 8)
26 key
= base64
.b64encode(random_string
).decode('utf-8')
30 def luks_format(key
, device
):
32 Decrypt (open) an encrypted device, previously prepared with cryptsetup
34 :param key: dmcrypt secret key, will be used for decrypting
35 :param device: Absolute path to device
39 '--batch-mode', # do not prompt
40 '--key-file', # misnomer, should be key
41 '-', # because we indicate stdin for the key here
45 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
48 def plain_open(key
, device
, mapping
):
50 Decrypt (open) an encrypted device, previously prepared with cryptsetup in plain mode
52 .. note: ceph-disk will require an additional b64decode call for this to work
54 :param key: dmcrypt secret key
55 :param device: absolute path to device
56 :param mapping: mapping name used to correlate device. Usually a UUID
69 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
72 def luks_open(key
, device
, mapping
):
74 Decrypt (open) an encrypted device, previously prepared with cryptsetup
76 .. note: ceph-disk will require an additional b64decode call for this to work
78 :param key: dmcrypt secret key
79 :param device: absolute path to device
80 :param mapping: mapping name used to correlate device. Usually a UUID
90 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
93 def dmcrypt_close(mapping
):
95 Encrypt (close) a device, previously decrypted with cryptsetup
99 if not os
.path
.exists(mapping
):
100 logger
.debug('device mapper path does not exist %s' % mapping
)
101 logger
.debug('will skip cryptsetup removal')
103 process
.run(['cryptsetup', 'remove', mapping
])
106 def get_dmcrypt_key(osd_id
, osd_fsid
, lockbox_keyring
=None):
108 Retrieve the dmcrypt (secret) key stored initially on the monitor. The key
109 is sent initially with JSON, and the Monitor then mangles the name to
110 ``dm-crypt/osd/<fsid>/luks``
112 The ``lockbox.keyring`` file is required for this operation, and it is
113 assumed it will exist on the path for the same OSD that is being activated.
114 To support scanning, it is optionally configurable to a custom location
115 (e.g. inside a lockbox partition mounted in a temporary location)
117 if lockbox_keyring
is None:
118 lockbox_keyring
= '/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)
119 name
= 'client.osd-lockbox.%s' % osd_fsid
120 config_key
= 'dm-crypt/osd/%s/luks' % osd_fsid
122 stdout
, stderr
, returncode
= process
.call(
125 '--cluster', conf
.cluster
,
127 '--keyring', lockbox_keyring
,
135 raise RuntimeError('Unable to retrieve dmcrypt secret')
136 return ' '.join(stdout
).strip()
139 def write_lockbox_keyring(osd_id
, osd_fsid
, secret
):
141 Helper to write the lockbox keyring. This is needed because the bluestore OSD will
142 not persist the keyring, and it can't be stored in the data device for filestore because
143 at the time this is needed, the device is encrypted.
145 For bluestore: A tmpfs filesystem is mounted, so the path can get written
146 to, but the files are ephemeral, which requires this file to be created
147 every time it is activated.
148 For filestore: The path for the OSD would exist at this point even if no
149 OSD data device is mounted, so the keyring is written to fetch the key, and
150 then the data device is mounted on that directory, making the keyring
153 if os
.path
.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)):
156 name
= 'client.osd-lockbox.%s' % osd_fsid
160 keyring_name
='lockbox.keyring',
167 Capture the metadata information of a possibly encrypted device, returning
168 a dictionary with all the values found (if any).
170 An encrypted device will contain information about a device. Example
171 successful output looks like::
173 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
174 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
176 cipher: aes-xts-plain64
180 size: 20740063 sectors
183 As long as the mapper device is in 'open' state, the ``status`` call will work.
185 :param device: Absolute path or UUID of the device mapper
192 out
, err
, code
= process
.call(command
, show_command
=True)
195 logger
.warning('failed to detect device mapper information')
198 # get rid of lines that might not be useful to construct the report:
199 if not line
.startswith(' '):
202 column
, value
= line
.split(': ')
205 metadata
[column
.strip()] = value
.strip().strip('"')
209 def legacy_encrypted(device
):
211 Detect if a device was encrypted with ceph-disk or not. In the case of
212 encrypted devices, include the type of encryption (LUKS, or PLAIN), and
213 infer what the lockbox partition is.
215 This function assumes that ``device`` will be a partition.
217 if os
.path
.isdir(device
):
218 mounts
= system
.get_mounts(paths
=True)
219 # yes, rebind the device variable here because a directory isn't going
220 # to help with parsing
221 device
= mounts
.get(device
, [None])[0]
223 raise RuntimeError('unable to determine the device mounted at %s' % device
)
224 metadata
= {'encrypted': False, 'type': None, 'lockbox': '', 'device': device
}
225 # check if the device is online/decrypted first
226 active_mapper
= status(device
)
228 # normalize a bit to ensure same values regardless of source
229 metadata
['type'] = active_mapper
['type'].lower().strip('12') # turn LUKS1 or LUKS2 into luks
230 metadata
['encrypted'] = True if metadata
['type'] in ['plain', 'luks'] else False
231 # The true device is now available to this function, so it gets
232 # re-assigned here for the lockbox checks to succeed (it is not
233 # possible to guess partitions from a device mapper device otherwise
234 device
= active_mapper
.get('device', device
)
235 metadata
['device'] = device
237 uuid
= get_part_entry_type(device
)
238 guid_match
= constants
.ceph_disk_guids
.get(uuid
, {})
239 encrypted_guid
= guid_match
.get('encrypted', False)
241 metadata
['encrypted'] = True
242 metadata
['type'] = guid_match
['encryption_type']
244 # Lets find the lockbox location now, to do this, we need to find out the
245 # parent device name for the device so that we can query all of its
246 # associated devices and *then* look for one that has the 'lockbox' label
247 # on it. Thanks for being awesome ceph-disk
248 disk_meta
= lsblk(device
, abspath
=True)
251 parent_device
= disk_meta
['PKNAME']
252 # With the parent device set, we can now look for the lockbox listing associated devices
253 devices
= device_family(parent_device
)
255 if 'lockbox' in i
.get('PARTLABEL', ''):
256 metadata
['lockbox'] = i
['NAME']