]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/encryption.py
update source to 12.2.11
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / encryption.py
1 import base64
2 import os
3 import logging
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
9
10 logger = logging.getLogger(__name__)
11
12
13 def create_dmcrypt_key():
14 """
15 Create the secret dm-crypt key used to decrypt a device.
16 """
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(
20 'osd',
21 'osd_dmcrypt_key_size',
22 default=1024,
23 )
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')
28 return key
29
30
31 def luks_format(key, device):
32 """
33 Decrypt (open) an encrypted device, previously prepared with cryptsetup
34
35 :param key: dmcrypt secret key, will be used for decrypting
36 :param device: Absolute path to device
37 """
38 command = [
39 'cryptsetup',
40 '--batch-mode', # do not prompt
41 '--key-file', # misnomer, should be key
42 '-', # because we indicate stdin for the key here
43 'luksFormat',
44 device,
45 ]
46 process.call(command, stdin=key, terminal_verbose=True, show_command=True)
47
48
49 def plain_open(key, device, mapping):
50 """
51 Decrypt (open) an encrypted device, previously prepared with cryptsetup in plain mode
52
53 .. note: ceph-disk will require an additional b64decode call for this to work
54
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
58 """
59 command = [
60 'cryptsetup',
61 '--key-file',
62 '-',
63 '--allow-discards', # allow discards (aka TRIM) requests for device
64 'open',
65 device,
66 mapping,
67 '--type', 'plain',
68 '--key-size', '256',
69 ]
70
71 process.call(command, stdin=key, terminal_verbose=True, show_command=True)
72
73
74 def luks_open(key, device, mapping):
75 """
76 Decrypt (open) an encrypted device, previously prepared with cryptsetup
77
78 .. note: ceph-disk will require an additional b64decode call for this to work
79
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
83 """
84 command = [
85 'cryptsetup',
86 '--key-file',
87 '-',
88 '--allow-discards', # allow discards (aka TRIM) requests for device
89 'luksOpen',
90 device,
91 mapping,
92 ]
93 process.call(command, stdin=key, terminal_verbose=True, show_command=True)
94
95
96 def dmcrypt_close(mapping):
97 """
98 Encrypt (close) a device, previously decrypted with cryptsetup
99
100 :param mapping:
101 """
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')
105 return
106 process.run(['cryptsetup', 'remove', mapping])
107
108
109 def get_dmcrypt_key(osd_id, osd_fsid, lockbox_keyring=None):
110 """
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``
114
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)
119 """
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
124
125 stdout, stderr, returncode = process.call(
126 [
127 'ceph',
128 '--cluster', conf.cluster,
129 '--name', name,
130 '--keyring', lockbox_keyring,
131 'config-key',
132 'get',
133 config_key
134 ],
135 show_command=True
136 )
137 if returncode != 0:
138 raise RuntimeError('Unable to retrieve dmcrypt secret')
139 return ' '.join(stdout).strip()
140
141
142 def write_lockbox_keyring(osd_id, osd_fsid, secret):
143 """
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.
147
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
154 "dissapear".
155 """
156 if os.path.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf.cluster, osd_id)):
157 return
158
159 name = 'client.osd-lockbox.%s' % osd_fsid
160 write_keyring(
161 osd_id,
162 secret,
163 keyring_name='lockbox.keyring',
164 name=name
165 )
166
167
168 def status(device):
169 """
170 Capture the metadata information of a possibly encrypted device, returning
171 a dictionary with all the values found (if any).
172
173 An encrypted device will contain information about a device. Example
174 successful output looks like::
175
176 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
177 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
178 type: LUKS1
179 cipher: aes-xts-plain64
180 keysize: 256 bits
181 device: /dev/sdc2
182 offset: 4096 sectors
183 size: 20740063 sectors
184 mode: read/write
185
186 As long as the mapper device is in 'open' state, the ``status`` call will work.
187
188 :param device: Absolute path or UUID of the device mapper
189 """
190 command = [
191 'cryptsetup',
192 'status',
193 device,
194 ]
195 out, err, code = process.call(command, show_command=True, verbose_on_failure=False)
196
197 metadata = {}
198 if code != 0:
199 logger.warning('failed to detect device mapper information')
200 return metadata
201 for line in out:
202 # get rid of lines that might not be useful to construct the report:
203 if not line.startswith(' '):
204 continue
205 try:
206 column, value = line.split(': ')
207 except ValueError:
208 continue
209 metadata[column.strip()] = value.strip().strip('"')
210 return metadata
211
212
213 def legacy_encrypted(device):
214 """
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.
218
219 This function assumes that ``device`` will be a partition.
220 """
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]
226 if not device:
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)
231 if active_mapper:
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
240 else:
241 uuid = get_part_entry_type(device)
242 guid_match = constants.ceph_disk_guids.get(uuid, {})
243 encrypted_guid = guid_match.get('encrypted', False)
244 if encrypted_guid:
245 metadata['encrypted'] = True
246 metadata['type'] = guid_match['encryption_type']
247
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)
253 if not disk_meta:
254 return metadata
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)]
258 for d in devices:
259 if d.ceph_disk.type == 'lockbox':
260 metadata['lockbox'] = d.abspath
261 break
262 return metadata