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