blob: 34aca589f44a871ce45d7c8278666d16cfbdfb38 [file] [log] [blame]
Oleksiy Petrenko95664c02018-04-19 17:05:16 +03001import logging
2import tempfile
3import shutil
4import os
5
6try:
7 from urllib.parse import urlsplit
8except ImportError:
9 from urlparse import urlsplit
10
11GLANCE_LOADED = False
12CMD_LOADED = False
13
14
15def __virtual__():
16 if 'glancev2.image_list' in __salt__:
17 global GLANCE_LOADED
18 GLANCE_LOADED = True
19 if 'cmd.run_all' in __salt__:
20 global CMD_LOADED
21 CMD_LOADED = True
22 return 'barbicanv1' if 'barbicanv1.secret_list' in __salt__ else False
23
24
25log = logging.getLogger(__name__)
26
27
28def _barbicanv1_call(fname, *args, **kwargs):
29 return __salt__['barbicanv1.{}'.format(fname)](*args, **kwargs)
30
31
32def _glancev2_call(fname, *args, **kwargs):
33 return __salt__['glancev2.{}'.format(fname)](*args, **kwargs)
34
35
36def _cmd_call(fname, *args, **kwargs):
37 return __salt__['cmd.{}'.format(fname)](*args, **kwargs)
38
39
40def secret_present(name, cloud_name, **kwargs):
41 try:
42 exact_secret = _barbicanv1_call(
43 'secret_get_details', name=name, cloud_name=cloud_name
44 )
45 except Exception as e:
46 if 'ResourceNotFound' in repr(e):
47 try:
48 if not kwargs:
49 kwargs = {}
50 resp = _barbicanv1_call(
51 'secret_create', name=name, cloud_name=cloud_name, **kwargs
52 )
53 except Exception as e:
54 log.error('Barbicanv1 create secret failed with {}'.format(e))
55 return _create_failed(name, 'secret')
56 return _created(name, 'secret', resp)
57 if 'MultipleResourcesFound' in repr(e):
58 return _find_failed(name, 'secret')
59 if 'payload' in kwargs:
60 try:
61 _barbicanv1_call(
62 'secret_payload_get', name=name, cloud_name=cloud_name
63 )
64 except Exception:
65 try:
66 _barbicanv1_call(
Oleh Hryhorovde4523f2018-09-11 10:11:06 +030067 'secret_payload_set', name=name,
Oleksiy Petrenko95664c02018-04-19 17:05:16 +030068 cloud_name=cloud_name, **kwargs
69 )
70 except Exception as e:
71 log.error(
72 'Barbicanv1 Secret set payload failed with {}'.format(e)
73 )
74 return _update_failed(name, 'secret_payload')
75 return _updated(
76 name, 'secret_payload', {'payload': kwargs['payload']}
77 )
78 return _no_changes(name, 'secret')
79
80
81def secret_absent(name, cloud_name, **kwargs):
82 try:
83 secret = _barbicanv1_call(
84 'secret_get_details', name=name, cloud_name=cloud_name
85 )
86 except Exception as e:
87 if 'ResourceNotFound' in repr(e):
88 return _absent(name, 'secret')
89 if 'MultipleResourcesFound' in repr(e):
90 return _find_failed(name, 'secret')
91 try:
92 _barbicanv1_call('secret_delete', name=name, cloud_name=cloud_name)
93 except Exception as e:
94 log.error('Barbicanv1 delete failed with {}'.format(e))
95 return _delete_failed(name, 'secret')
96 return _deleted(name, 'secret')
97
98
Vasyl Saienko45b427b2019-01-21 08:03:11 +000099def glance_image_signed(name, secret_name, pk_fname, out_fname,
100 cloud_name, file_name=None, force_resign=False,
101 image_name=None):
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300102 """
103
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000104 :param name: The name of the image to sign
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300105 :param secret_name: Secret's name with certificate
106 :param pk_fname: private_key file name
107 :param out_fname: output filename for signature
108 :param cloud_name: name of the cloud in cloud_yaml
109 :param file_name: name of the file where downloaded image is.
110 :param force_resign: if the image is already signed, resign it.
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000111 :param image_name: The name of the image to sign DEPRECATED, use name instead.
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300112 """
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000113
114 # TODO(vsaeinko): remove once deprecation period passed in Q2.2019.
115 if image_name is not None:
116 name = image_name
117
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300118 if not GLANCE_LOADED or not CMD_LOADED:
119 return {
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000120 'name': name,
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300121 'changes': {},
122 'comment': 'Cant sign an image, glancev2 and/or cmd module '
123 'are/is absent',
124 'result': False,
125 }
126 try:
127 image = _glancev2_call(
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000128 'image_get_details', name=name, cloud_name=cloud_name
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300129 )
130 except Exception as e:
131 log.error('Barbicanv1 sign_image find image failed with {}'.format(e))
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000132 return _create_failed(name, 'image')
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300133
134 sign_properties = (
135 'img_signature', 'img_signature_certificate_uuid',
136 'img_signature_hash_method', 'img_signature_key_type',
137 )
138
139 if not force_resign and all(key in image for key in sign_properties):
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000140 return _no_changes(name, 'image_signature')
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300141
142 file_name = file_name or image['id']
143 dir_path = tempfile.mkdtemp()
144 try:
145 file_path = os.path.join(dir_path, file_name)
146
147 _glancev2_call(
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000148 'image_download', name=name,
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300149 file_name=file_path,
150 cloud_name=cloud_name
151 )
152 except Exception as e:
153 log.error(
154 "Barbicanv1 sign image can't download image."
155 " failed with {}".format(e)
156 )
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000157 return _create_failed(name, 'downloading_image')
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300158
159 try:
160 retcode = _cmd_call(
161 'run_all',
162 'openssl dgst -sha256 -sign {} '.format(pk_fname) +
163 '-sigopt rsa_padding_mode:pss -out {} '.format(out_fname) +
164 file_path
165 )['retcode']
166 if not retcode == 0:
167 raise Exception('Cant sign image')
168 image_signature = _cmd_call(
169 'run_all', 'base64 -w 0 {}'.format(out_fname)
170 )['stdout']
171 except Exception as e:
172 log.error(
173 'Barbicanv1 sign image failed because of cmd with {}'.format(e)
174 )
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000175 return _create_failed(name, 'cmd_module')
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300176 shutil.rmtree(dir_path)
177
178 secret_ref = _barbicanv1_call(
179 'secret_get_details', name=secret_name, cloud_name=cloud_name
180 )['secret_ref']
181
182 def _parse_secret_href(href):
183 return urlsplit(href).path.split('/')[-1]
184
185 secret_uuid = _parse_secret_href(secret_ref)
186
187 to_update = [
188 {
189 'op': 'add',
190 'path': '/img_signature',
191 'value': image_signature,
192 },
193 {
194 'op': 'add',
195 'path': '/img_signature_certificate_uuid',
196 'value': secret_uuid,
197 },
198 {
199 'op': 'add',
200 'path': '/img_signature_hash_method',
201 'value': 'SHA-256',
202 },
203 {
204 'op': 'add',
205 'path': '/img_signature_key_type',
206 'value': 'RSA-PSS'
207 }
208
209 ]
210 try:
211 resp = _glancev2_call(
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000212 'image_update', name, to_update, cloud_name=cloud_name,
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300213 headers={
214 "Content-Type": "application/openstack-images-v2.1-json-patch"
215 }
216 )
217 except Exception as e:
218 log.error('Barbicanv1 sign image failed with {}'.format(e))
Vasyl Saienko45b427b2019-01-21 08:03:11 +0000219 return _create_failed(name, 'sign_image')
220 return _created(name, 'sign_image', resp)
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300221
222
Ann Taraday96dbd892018-11-19 18:22:45 +0400223def secret_acl_present(name, cloud_name, **kwargs):
224 try:
225 secret = _barbicanv1_call(
226 'secret_get_details', name=name, cloud_name=cloud_name
227 )
228 except Exception as e:
229 if 'ResourceNotFound' in repr(e):
230 return _absent(name, 'secret')
231 if 'MultipleResourcesFound' in repr(e):
232 return _find_failed(name, 'secret')
233 try:
234 resp = _barbicanv1_call('secret_acl_get', name=name,
235 cloud_name=cloud_name)
236 except Exception as e:
237 if 'ResourceNotFound' in repr(e):
238 resp = _barbicanv1_call('secret_acl_put', name=name,
239 cloud_name=cloud_name, **kwargs)
240 return _created(name, 'acl', resp)
241 else:
242 log.error('Add acl for user faild with {}'.format(e))
243 return _create_failed(name, 'acl')
244
245 missing_users = [user_id
246 for user_id in kwargs.get('users', [])
Ann Kamyshnikova575a6482018-12-11 13:05:35 +0400247 if user_id not in resp['read'].get('users', [])]
Ann Taraday96dbd892018-11-19 18:22:45 +0400248 if missing_users:
249 kwargs['users'] = missing_users
250 resp = _barbicanv1_call('secret_acl_put', name=name,
251 cloud_name=cloud_name, **kwargs)
252 return _created(name, 'acl', resp)
253 return _no_changes(name, 'acl')
254
255
Oleksiy Petrenko95664c02018-04-19 17:05:16 +0300256def _created(name, resource, resource_definition):
257 changes_dict = {
258 'name': name,
259 'changes': resource_definition,
260 'result': True,
261 'comment': '{}{} created'.format(resource, name)
262 }
263 return changes_dict
264
265
266def _updated(name, resource, resource_definition):
267 changes_dict = {
268 'name': name,
269 'changes': resource_definition,
270 'result': True,
271 'comment': '{}{} updated'.format(resource, name)
272 }
273 return changes_dict
274
275
276def _no_changes(name, resource):
277 changes_dict = {
278 'name': name,
279 'changes': {},
280 'result': True,
281 'comment': '{}{} is in desired state'.format(resource, name)
282 }
283 return changes_dict
284
285
286def _deleted(name, resource):
287 changes_dict = {
288 'name': name,
289 'changes': {},
290 'result': True,
291 'comment': '{}{} removed'.format(resource, name)
292 }
293 return changes_dict
294
295
296def _absent(name, resource):
297 changes_dict = {'name': name,
298 'changes': {},
299 'comment': '{0} {1} not present'.format(resource, name),
300 'result': True}
301 return changes_dict
302
303
304def _delete_failed(name, resource):
305 changes_dict = {'name': name,
306 'changes': {},
307 'comment': '{0} {1} failed to delete'.format(resource,
308 name),
309 'result': False}
310 return changes_dict
311
312
313def _create_failed(name, resource):
314 changes_dict = {'name': name,
315 'changes': {},
316 'comment': '{0} {1} failed to create'.format(resource,
317 name),
318 'result': False}
319 return changes_dict
320
321
322def _update_failed(name, resource):
323 changes_dict = {'name': name,
324 'changes': {},
325 'comment': '{0} {1} failed to update'.format(resource,
326 name),
327 'result': False}
328 return changes_dict
329
330
331def _find_failed(name, resource):
332 changes_dict = {
333 'name': name,
334 'changes': {},
335 'comment': '{0} {1} found multiple {0}'.format(resource, name),
336 'result': False,
337 }
338 return changes_dict