]>
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 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__
)
13 def create_dmcrypt_key():
15 Create the secret dm-crypt key used to decrypt a device.
17 # get the customizable dmcrypt key size (in bits) from ceph.conf fallback
18 # to the default of 1024
19 dmcrypt_key_size
= conf
.ceph
.get_safe(
21 'osd_dmcrypt_key_size',
24 # The size of the key is defined in bits, so we must transform that
25 # value to bytes (dividing by 8) because we read in bytes, not bits
26 random_string
= os
.urandom(dmcrypt_key_size
/ 8)
27 key
= base64
.b64encode(random_string
).decode('utf-8')
31 def luks_format(key
, device
):
33 Decrypt (open) an encrypted device, previously prepared with cryptsetup
35 :param key: dmcrypt secret key, will be used for decrypting
36 :param device: Absolute path to device
40 '--batch-mode', # do not prompt
41 '--key-file', # misnomer, should be key
42 '-', # because we indicate stdin for the key here
46 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
49 def plain_open(key
, device
, mapping
):
51 Decrypt (open) an encrypted device, previously prepared with cryptsetup in plain mode
53 .. note: ceph-disk will require an additional b64decode call for this to work
55 :param key: dmcrypt secret key
56 :param device: absolute path to device
57 :param mapping: mapping name used to correlate device. Usually a UUID
70 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
73 def luks_open(key
, device
, mapping
):
75 Decrypt (open) an encrypted device, previously prepared with cryptsetup
77 .. note: ceph-disk will require an additional b64decode call for this to work
79 :param key: dmcrypt secret key
80 :param device: absolute path to device
81 :param mapping: mapping name used to correlate device. Usually a UUID
91 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
94 def dmcrypt_close(mapping
):
96 Encrypt (close) a device, previously decrypted with cryptsetup
100 if not os
.path
.exists(mapping
):
101 logger
.debug('device mapper path does not exist %s' % mapping
)
102 logger
.debug('will skip cryptsetup removal')
104 process
.run(['cryptsetup', 'remove', mapping
])
107 def get_dmcrypt_key(osd_id
, osd_fsid
, lockbox_keyring
=None):
109 Retrieve the dmcrypt (secret) key stored initially on the monitor. The key
110 is sent initially with JSON, and the Monitor then mangles the name to
111 ``dm-crypt/osd/<fsid>/luks``
113 The ``lockbox.keyring`` file is required for this operation, and it is
114 assumed it will exist on the path for the same OSD that is being activated.
115 To support scanning, it is optionally configurable to a custom location
116 (e.g. inside a lockbox partition mounted in a temporary location)
118 if lockbox_keyring
is None:
119 lockbox_keyring
= '/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)
120 name
= 'client.osd-lockbox.%s' % osd_fsid
121 config_key
= 'dm-crypt/osd/%s/luks' % osd_fsid
123 stdout
, stderr
, returncode
= process
.call(
126 '--cluster', conf
.cluster
,
128 '--keyring', lockbox_keyring
,
136 raise RuntimeError('Unable to retrieve dmcrypt secret')
137 return ' '.join(stdout
).strip()
140 def write_lockbox_keyring(osd_id
, osd_fsid
, secret
):
142 Helper to write the lockbox keyring. This is needed because the bluestore OSD will
143 not persist the keyring, and it can't be stored in the data device for filestore because
144 at the time this is needed, the device is encrypted.
146 For bluestore: A tmpfs filesystem is mounted, so the path can get written
147 to, but the files are ephemeral, which requires this file to be created
148 every time it is activated.
149 For filestore: The path for the OSD would exist at this point even if no
150 OSD data device is mounted, so the keyring is written to fetch the key, and
151 then the data device is mounted on that directory, making the keyring
154 if os
.path
.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)):
157 name
= 'client.osd-lockbox.%s' % osd_fsid
161 keyring_name
='lockbox.keyring',
168 Capture the metadata information of a possibly encrypted device, returning
169 a dictionary with all the values found (if any).
171 An encrypted device will contain information about a device. Example
172 successful output looks like::
174 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
175 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
177 cipher: aes-xts-plain64
181 size: 20740063 sectors
184 As long as the mapper device is in 'open' state, the ``status`` call will work.
186 :param device: Absolute path or UUID of the device mapper
193 out
, err
, code
= process
.call(command
, show_command
=True, verbose_on_failure
=False)
197 logger
.warning('failed to detect device mapper information')
200 # get rid of lines that might not be useful to construct the report:
201 if not line
.startswith(' '):
204 column
, value
= line
.split(': ')
207 metadata
[column
.strip()] = value
.strip().strip('"')
211 def legacy_encrypted(device
):
213 Detect if a device was encrypted with ceph-disk or not. In the case of
214 encrypted devices, include the type of encryption (LUKS, or PLAIN), and
215 infer what the lockbox partition is.
217 This function assumes that ``device`` will be a partition.
219 if os
.path
.isdir(device
):
220 mounts
= system
.get_mounts(paths
=True)
221 # yes, rebind the device variable here because a directory isn't going
222 # to help with parsing
223 device
= mounts
.get(device
, [None])[0]
225 raise RuntimeError('unable to determine the device mounted at %s' % device
)
226 metadata
= {'encrypted': False, 'type': None, 'lockbox': '', 'device': device
}
227 # check if the device is online/decrypted first
228 active_mapper
= status(device
)
230 # normalize a bit to ensure same values regardless of source
231 metadata
['type'] = active_mapper
['type'].lower().strip('12') # turn LUKS1 or LUKS2 into luks
232 metadata
['encrypted'] = True if metadata
['type'] in ['plain', 'luks'] else False
233 # The true device is now available to this function, so it gets
234 # re-assigned here for the lockbox checks to succeed (it is not
235 # possible to guess partitions from a device mapper device otherwise
236 device
= active_mapper
.get('device', device
)
237 metadata
['device'] = device
239 uuid
= get_part_entry_type(device
)
240 guid_match
= constants
.ceph_disk_guids
.get(uuid
, {})
241 encrypted_guid
= guid_match
.get('encrypted', False)
243 metadata
['encrypted'] = True
244 metadata
['type'] = guid_match
['encryption_type']
246 # Lets find the lockbox location now, to do this, we need to find out the
247 # parent device name for the device so that we can query all of its
248 # associated devices and *then* look for one that has the 'lockbox' label
249 # on it. Thanks for being awesome ceph-disk
250 disk_meta
= lsblk(device
, abspath
=True)
253 parent_device
= disk_meta
['PKNAME']
254 # With the parent device set, we can now look for the lockbox listing associated devices
255 devices
= [Device(i
['NAME']) for i
in device_family(parent_device
)]
257 if d
.ceph_disk
.type == 'lockbox':
258 metadata
['lockbox'] = d
.abspath