]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/encryption.py
update sources to 12.2.10
[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(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 'open',
64 device,
65 mapping,
66 '--type', 'plain',
67 '--key-size', '256',
68 ]
69
70 process.call(command, stdin=key, terminal_verbose=True, show_command=True)
71
72
73 def luks_open(key, device, mapping):
74 """
75 Decrypt (open) an encrypted device, previously prepared with cryptsetup
76
77 .. note: ceph-disk will require an additional b64decode call for this to work
78
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
82 """
83 command = [
84 'cryptsetup',
85 '--key-file',
86 '-',
87 'luksOpen',
88 device,
89 mapping,
90 ]
91 process.call(command, stdin=key, terminal_verbose=True, show_command=True)
92
93
94 def dmcrypt_close(mapping):
95 """
96 Encrypt (close) a device, previously decrypted with cryptsetup
97
98 :param mapping:
99 """
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')
103 return
104 process.run(['cryptsetup', 'remove', mapping])
105
106
107 def get_dmcrypt_key(osd_id, osd_fsid, lockbox_keyring=None):
108 """
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``
112
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)
117 """
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
122
123 stdout, stderr, returncode = process.call(
124 [
125 'ceph',
126 '--cluster', conf.cluster,
127 '--name', name,
128 '--keyring', lockbox_keyring,
129 'config-key',
130 'get',
131 config_key
132 ],
133 show_command=True
134 )
135 if returncode != 0:
136 raise RuntimeError('Unable to retrieve dmcrypt secret')
137 return ' '.join(stdout).strip()
138
139
140 def write_lockbox_keyring(osd_id, osd_fsid, secret):
141 """
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.
145
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
152 "dissapear".
153 """
154 if os.path.exists('/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf.cluster, osd_id)):
155 return
156
157 name = 'client.osd-lockbox.%s' % osd_fsid
158 write_keyring(
159 osd_id,
160 secret,
161 keyring_name='lockbox.keyring',
162 name=name
163 )
164
165
166 def status(device):
167 """
168 Capture the metadata information of a possibly encrypted device, returning
169 a dictionary with all the values found (if any).
170
171 An encrypted device will contain information about a device. Example
172 successful output looks like::
173
174 $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4
175 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use.
176 type: LUKS1
177 cipher: aes-xts-plain64
178 keysize: 256 bits
179 device: /dev/sdc2
180 offset: 4096 sectors
181 size: 20740063 sectors
182 mode: read/write
183
184 As long as the mapper device is in 'open' state, the ``status`` call will work.
185
186 :param device: Absolute path or UUID of the device mapper
187 """
188 command = [
189 'cryptsetup',
190 'status',
191 device,
192 ]
193 out, err, code = process.call(command, show_command=True, verbose_on_failure=False)
194
195 metadata = {}
196 if code != 0:
197 logger.warning('failed to detect device mapper information')
198 return metadata
199 for line in out:
200 # get rid of lines that might not be useful to construct the report:
201 if not line.startswith(' '):
202 continue
203 try:
204 column, value = line.split(': ')
205 except ValueError:
206 continue
207 metadata[column.strip()] = value.strip().strip('"')
208 return metadata
209
210
211 def legacy_encrypted(device):
212 """
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.
216
217 This function assumes that ``device`` will be a partition.
218 """
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]
224 if not device:
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)
229 if active_mapper:
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
238 else:
239 uuid = get_part_entry_type(device)
240 guid_match = constants.ceph_disk_guids.get(uuid, {})
241 encrypted_guid = guid_match.get('encrypted', False)
242 if encrypted_guid:
243 metadata['encrypted'] = True
244 metadata['type'] = guid_match['encryption_type']
245
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)
251 if not disk_meta:
252 return metadata
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)]
256 for d in devices:
257 if d.ceph_disk.type == 'lockbox':
258 metadata['lockbox'] = d.abspath
259 break
260 return metadata