]>
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(int(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
63 '--allow-discards', # allow discards (aka TRIM) requests for device
71 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
74 def luks_open(key
, device
, mapping
):
76 Decrypt (open) an encrypted device, previously prepared with cryptsetup
78 .. note: ceph-disk will require an additional b64decode call for this to work
80 :param key: dmcrypt secret key
81 :param device: absolute path to device
82 :param mapping: mapping name used to correlate device. Usually a UUID
88 '--allow-discards', # allow discards (aka TRIM) requests for device
93 process
.call(command
, stdin
=key
, terminal_verbose
=True, show_command
=True)
96 def dmcrypt_close(mapping
):
98 Encrypt (close) a device, previously decrypted with cryptsetup
102 if not os
.path
.exists(mapping
):
103 logger
.debug('device mapper path does not exist %s' % mapping
)
104 logger
.debug('will skip cryptsetup removal')
106 process
.run(['cryptsetup', 'remove', mapping
])
109 def get_dmcrypt_key(osd_id
, osd_fsid
, lockbox_keyring
=None):
111 Retrieve the dmcrypt (secret) key stored initially on the monitor. The key
112 is sent initially with JSON, and the Monitor then mangles the name to
113 ``dm-crypt/osd/<fsid>/luks``
115 The ``lockbox.keyring`` file is required for this operation, and it is
116 assumed it will exist on the path for the same OSD that is being activated.
117 To support scanning, it is optionally configurable to a custom location
118 (e.g. inside a lockbox partition mounted in a temporary location)
120 if lockbox_keyring
is None:
121 lockbox_keyring
= '/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)
122 name
= 'client.osd-lockbox.%s' % osd_fsid
123 config_key
= 'dm-crypt/osd/%s/luks' % osd_fsid
125 stdout
, stderr
, returncode
= process
.call(
128 '--cluster', conf
.cluster
,
130 '--keyring', lockbox_keyring
,
138 raise RuntimeError('Unable to retrieve dmcrypt secret')
139 return ' '.join(stdout
).strip()
142 def write_lockbox_keyring(osd_id
, osd_fsid
, secret
):
144 Helper to write the lockbox keyring. This is needed because the bluestore OSD will
145 not persist the keyring, and it can't be stored in the data device for filestore because
146 at the time this is needed, the device is encrypted.
148 For bluestore: A tmpfs filesystem is mounted, so the path can get written
149 to, but the files are ephemeral, which requires this file to be created
150 every time it is activated.
151 For filestore: The path for the OSD would exist at this point even if no
152 OSD data device is mounted, so the keyring is written to fetch the key, and
153 then the data device is mounted on that directory, making the keyring
156 if os
.path
.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf
.cluster
, osd_id
)):
159 name
= 'client.osd-lockbox.%s' % osd_fsid
163 keyring_name
='lockbox.keyring',
170 Capture the metadata information of a possibly encrypted device, returning
171 a dictionary with all the values found (if any).
173 An encrypted device will contain information about a device. Example
174 successful output looks like::
176 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
177 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
179 cipher: aes-xts-plain64
183 size: 20740063 sectors
186 As long as the mapper device is in 'open' state, the ``status`` call will work.
188 :param device: Absolute path or UUID of the device mapper
195 out
, err
, code
= process
.call(command
, show_command
=True, verbose_on_failure
=False)
199 logger
.warning('failed to detect device mapper information')
202 # get rid of lines that might not be useful to construct the report:
203 if not line
.startswith(' '):
206 column
, value
= line
.split(': ')
209 metadata
[column
.strip()] = value
.strip().strip('"')
213 def legacy_encrypted(device
):
215 Detect if a device was encrypted with ceph-disk or not. In the case of
216 encrypted devices, include the type of encryption (LUKS, or PLAIN), and
217 infer what the lockbox partition is.
219 This function assumes that ``device`` will be a partition.
221 if os
.path
.isdir(device
):
222 mounts
= system
.get_mounts(paths
=True)
223 # yes, rebind the device variable here because a directory isn't going
224 # to help with parsing
225 device
= mounts
.get(device
, [None])[0]
227 raise RuntimeError('unable to determine the device mounted at %s' % device
)
228 metadata
= {'encrypted': False, 'type': None, 'lockbox': '', 'device': device
}
229 # check if the device is online/decrypted first
230 active_mapper
= status(device
)
232 # normalize a bit to ensure same values regardless of source
233 metadata
['type'] = active_mapper
['type'].lower().strip('12') # turn LUKS1 or LUKS2 into luks
234 metadata
['encrypted'] = True if metadata
['type'] in ['plain', 'luks'] else False
235 # The true device is now available to this function, so it gets
236 # re-assigned here for the lockbox checks to succeed (it is not
237 # possible to guess partitions from a device mapper device otherwise
238 device
= active_mapper
.get('device', device
)
239 metadata
['device'] = device
241 uuid
= get_part_entry_type(device
)
242 guid_match
= constants
.ceph_disk_guids
.get(uuid
, {})
243 encrypted_guid
= guid_match
.get('encrypted', False)
245 metadata
['encrypted'] = True
246 metadata
['type'] = guid_match
['encryption_type']
248 # Lets find the lockbox location now, to do this, we need to find out the
249 # parent device name for the device so that we can query all of its
250 # associated devices and *then* look for one that has the 'lockbox' label
251 # on it. Thanks for being awesome ceph-disk
252 disk_meta
= lsblk(device
, abspath
=True)
255 parent_device
= disk_meta
['PKNAME']
256 # With the parent device set, we can now look for the lockbox listing associated devices
257 devices
= [Device(i
['NAME']) for i
in device_family(parent_device
)]
259 if d
.ceph_disk
.type == 'lockbox':
260 metadata
['lockbox'] = d
.abspath