]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/barbican.py
add stop-gap to fix compat with CPUs not supporting SSE 4.1
[ceph.git] / ceph / qa / tasks / barbican.py
1 """
2 Deploy and configure Barbican for Teuthology
3 """
4 import argparse
5 import contextlib
6 import logging
7 import http
8 import json
9 import time
10 import math
11
12 from urllib.parse import urlparse
13
14 from teuthology import misc as teuthology
15 from teuthology import contextutil
16 from teuthology.orchestra import run
17 from teuthology.exceptions import ConfigError
18
19 log = logging.getLogger(__name__)
20
21
22 @contextlib.contextmanager
23 def download(ctx, config):
24 """
25 Download the Barbican from github.
26 Remove downloaded file upon exit.
27
28 The context passed in should be identical to the context
29 passed in to the main task.
30 """
31 assert isinstance(config, dict)
32 log.info('Downloading barbican...')
33 testdir = teuthology.get_testdir(ctx)
34 for (client, cconf) in config.items():
35 branch = cconf.get('force-branch', 'master')
36 log.info("Using branch '%s' for barbican", branch)
37
38 sha1 = cconf.get('sha1')
39 log.info('sha1=%s', sha1)
40
41 ctx.cluster.only(client).run(
42 args=[
43 'bash', '-l'
44 ],
45 )
46 ctx.cluster.only(client).run(
47 args=[
48 'git', 'clone',
49 '-b', branch,
50 'https://github.com/openstack/barbican.git',
51 '{tdir}/barbican'.format(tdir=testdir),
52 ],
53 )
54 if sha1 is not None:
55 ctx.cluster.only(client).run(
56 args=[
57 'cd', '{tdir}/barbican'.format(tdir=testdir),
58 run.Raw('&&'),
59 'git', 'reset', '--hard', sha1,
60 ],
61 )
62 try:
63 yield
64 finally:
65 log.info('Removing barbican...')
66 testdir = teuthology.get_testdir(ctx)
67 for client in config:
68 ctx.cluster.only(client).run(
69 args=[
70 'rm',
71 '-rf',
72 '{tdir}/barbican'.format(tdir=testdir),
73 ],
74 )
75
76 def get_barbican_dir(ctx):
77 return '{tdir}/barbican'.format(tdir=teuthology.get_testdir(ctx))
78
79 def run_in_barbican_dir(ctx, client, args):
80 ctx.cluster.only(client).run(
81 args=['cd', get_barbican_dir(ctx), run.Raw('&&'), ] + args,
82 )
83
84 def run_in_barbican_venv(ctx, client, args):
85 run_in_barbican_dir(ctx, client,
86 ['.',
87 '.barbicanenv/bin/activate',
88 run.Raw('&&')
89 ] + args)
90
91 @contextlib.contextmanager
92 def setup_venv(ctx, config):
93 """
94 Setup the virtualenv for Barbican using pip.
95 """
96 assert isinstance(config, dict)
97 log.info('Setting up virtualenv for barbican...')
98 for (client, _) in config.items():
99 run_in_barbican_dir(ctx, client,
100 ['python3', '-m', 'venv', '.barbicanenv'])
101 run_in_barbican_venv(ctx, client,
102 ['pip', 'install', '--upgrade', 'pip'])
103 run_in_barbican_venv(ctx, client,
104 ['pip', 'install', 'pytz',
105 '-e', get_barbican_dir(ctx)])
106 yield
107
108 def assign_ports(ctx, config, initial_port):
109 """
110 Assign port numbers starting from @initial_port
111 """
112 port = initial_port
113 role_endpoints = {}
114 for remote, roles_for_host in ctx.cluster.remotes.items():
115 for role in roles_for_host:
116 if role in config:
117 role_endpoints[role] = (remote.name.split('@')[1], port)
118 port += 1
119
120 return role_endpoints
121
122 def set_authtoken_params(ctx, cclient, cconfig):
123 section_config_list = cconfig['keystone_authtoken'].items()
124 for config in section_config_list:
125 (name, val) = config
126 run_in_barbican_dir(ctx, cclient,
127 ['sed', '-i',
128 '/[[]filter:authtoken]/{p;s##'+'{} = {}'.format(name, val)+'#;}',
129 'etc/barbican/barbican-api-paste.ini'])
130
131 keystone_role = cconfig.get('use-keystone-role', None)
132 public_host, public_port = ctx.keystone.public_endpoints[keystone_role]
133 url = 'http://{host}:{port}/v3'.format(host=public_host,
134 port=public_port)
135 run_in_barbican_dir(ctx, cclient,
136 ['sed', '-i',
137 '/[[]filter:authtoken]/{p;s##'+'auth_uri = {}'.format(url)+'#;}',
138 'etc/barbican/barbican-api-paste.ini'])
139 admin_host, admin_port = ctx.keystone.admin_endpoints[keystone_role]
140 admin_url = 'http://{host}:{port}/v3'.format(host=admin_host,
141 port=admin_port)
142 run_in_barbican_dir(ctx, cclient,
143 ['sed', '-i',
144 '/[[]filter:authtoken]/{p;s##'+'auth_url = {}'.format(admin_url)+'#;}',
145 'etc/barbican/barbican-api-paste.ini'])
146
147 def fix_barbican_api_paste(ctx, cclient):
148 run_in_barbican_dir(ctx, cclient,
149 ['sed', '-i', '-n',
150 '/\\[pipeline:barbican_api]/ {p;n; /^pipeline =/ '+
151 '{ s/.*/pipeline = unauthenticated-context apiapp/;p;d } } ; p',
152 './etc/barbican/barbican-api-paste.ini'])
153
154 def fix_barbican_api(ctx, cclient):
155 run_in_barbican_dir(ctx, cclient,
156 ['sed', '-i',
157 '/prop_dir =/ s#etc/barbican#{}/etc/barbican#'.format(get_barbican_dir(ctx)),
158 'bin/barbican-api'])
159
160 def create_barbican_conf(ctx, cclient):
161 barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
162 barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
163 port=barbican_port)
164 log.info("barbican url=%s", barbican_url)
165
166 run_in_barbican_dir(ctx, cclient,
167 ['bash', '-c',
168 'echo -n -e "[DEFAULT]\nhost_href=' + barbican_url + '\n" ' + \
169 '>barbican.conf'])
170
171 log.info("run barbican db upgrade")
172 config_path = get_barbican_dir(ctx) + '/barbican.conf'
173 run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path,
174 'db', 'upgrade'])
175 log.info("run barbican db sync_secret_stores")
176 run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path,
177 'db', 'sync_secret_stores'])
178
179 @contextlib.contextmanager
180 def configure_barbican(ctx, config):
181 """
182 Configure barbican paste-api and barbican-api.
183 """
184 assert isinstance(config, dict)
185 (cclient, cconfig) = next(iter(config.items()))
186
187 keystone_role = cconfig.get('use-keystone-role', None)
188 if keystone_role is None:
189 raise ConfigError('use-keystone-role not defined in barbican task')
190
191 set_authtoken_params(ctx, cclient, cconfig)
192 fix_barbican_api(ctx, cclient)
193 fix_barbican_api_paste(ctx, cclient)
194 create_barbican_conf(ctx, cclient)
195 try:
196 yield
197 finally:
198 pass
199
200 @contextlib.contextmanager
201 def run_barbican(ctx, config):
202 assert isinstance(config, dict)
203 log.info('Running barbican...')
204
205 for (client, _) in config.items():
206 (remote,) = ctx.cluster.only(client).remotes.keys()
207 cluster_name, _, client_id = teuthology.split_role(client)
208
209 # start the public endpoint
210 client_public_with_id = 'barbican.public' + '.' + client_id
211
212 run_cmd = ['cd', get_barbican_dir(ctx), run.Raw('&&'),
213 '.', '.barbicanenv/bin/activate', run.Raw('&&'),
214 'HOME={}'.format(get_barbican_dir(ctx)), run.Raw('&&'),
215 'bin/barbican-api',
216 run.Raw('& { read; kill %1; }')]
217 #run.Raw('1>/dev/null')
218
219 run_cmd = 'cd ' + get_barbican_dir(ctx) + ' && ' + \
220 '. .barbicanenv/bin/activate && ' + \
221 'HOME={}'.format(get_barbican_dir(ctx)) + ' && ' + \
222 'exec bin/barbican-api & { read; kill %1; }'
223
224 ctx.daemons.add_daemon(
225 remote, 'barbican', client_public_with_id,
226 cluster=cluster_name,
227 args=['bash', '-c', run_cmd],
228 logger=log.getChild(client),
229 stdin=run.PIPE,
230 cwd=get_barbican_dir(ctx),
231 wait=False,
232 check_status=False,
233 )
234
235 # sleep driven synchronization
236 run_in_barbican_venv(ctx, client, ['sleep', '15'])
237 try:
238 yield
239 finally:
240 log.info('Stopping Barbican instance')
241 ctx.daemons.get_daemon('barbican', client_public_with_id,
242 cluster_name).stop()
243
244
245 @contextlib.contextmanager
246 def create_secrets(ctx, config):
247 """
248 Create a main and an alternate s3 user.
249 """
250 assert isinstance(config, dict)
251 (cclient, cconfig) = next(iter(config.items()))
252
253 rgw_user = cconfig['rgw_user']
254
255 keystone_role = cconfig.get('use-keystone-role', None)
256 keystone_host, keystone_port = ctx.keystone.public_endpoints[keystone_role]
257 barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
258 barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
259 port=barbican_port)
260 log.info("barbican_url=%s", barbican_url)
261 #fetching user_id of user that gets secrets for radosgw
262 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
263 token_req.request(
264 'POST',
265 '/v3/auth/tokens',
266 headers={'Content-Type':'application/json'},
267 body=json.dumps({
268 "auth": {
269 "identity": {
270 "methods": ["password"],
271 "password": {
272 "user": {
273 "domain": {"id": "default"},
274 "name": rgw_user["username"],
275 "password": rgw_user["password"]
276 }
277 }
278 },
279 "scope": {
280 "project": {
281 "domain": {"id": "default"},
282 "name": rgw_user["tenantName"]
283 }
284 }
285 }
286 }))
287 rgw_access_user_resp = token_req.getresponse()
288 if not (rgw_access_user_resp.status >= 200 and
289 rgw_access_user_resp.status < 300):
290 raise Exception("Cannot authenticate user "+rgw_user["username"]+" for secret creation")
291 # baru_resp = json.loads(baru_req.data)
292 rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode())
293 rgw_user_id = rgw_access_user_data['token']['user']['id']
294 if 'secrets' in cconfig:
295 for secret in cconfig['secrets']:
296 if 'name' not in secret:
297 raise ConfigError('barbican.secrets must have "name" field')
298 if 'base64' not in secret:
299 raise ConfigError('barbican.secrets must have "base64" field')
300 if 'tenantName' not in secret:
301 raise ConfigError('barbican.secrets must have "tenantName" field')
302 if 'username' not in secret:
303 raise ConfigError('barbican.secrets must have "username" field')
304 if 'password' not in secret:
305 raise ConfigError('barbican.secrets must have "password" field')
306
307 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
308 token_req.request(
309 'POST',
310 '/v3/auth/tokens',
311 headers={'Content-Type':'application/json'},
312 body=json.dumps({
313 "auth": {
314 "identity": {
315 "methods": ["password"],
316 "password": {
317 "user": {
318 "domain": {"id": "default"},
319 "name": secret["username"],
320 "password": secret["password"]
321 }
322 }
323 },
324 "scope": {
325 "project": {
326 "domain": {"id": "default"},
327 "name": secret["tenantName"]
328 }
329 }
330 }
331 }))
332 token_resp = token_req.getresponse()
333 if not (token_resp.status >= 200 and
334 token_resp.status < 300):
335 raise Exception("Cannot authenticate user "+secret["username"]+" for secret creation")
336
337 expire = time.time() + 5400 # now + 90m
338 (expire_fract,dummy) = math.modf(expire)
339 expire_format = "%%FT%%T.%06d" % (round(expire_fract*1000000))
340 expiration = time.strftime(expire_format, time.gmtime(expire))
341 token_id = token_resp.getheader('x-subject-token')
342
343 key1_json = json.dumps(
344 {
345 "name": secret['name'],
346 "expiration": expiration,
347 "algorithm": "aes",
348 "bit_length": 256,
349 "mode": "cbc",
350 "payload": secret['base64'],
351 "payload_content_type": "application/octet-stream",
352 "payload_content_encoding": "base64"
353 })
354
355 sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30)
356 try:
357 sec_req.request(
358 'POST',
359 '/v1/secrets',
360 headers={'Content-Type': 'application/json',
361 'Accept': '*/*',
362 'X-Auth-Token': token_id},
363 body=key1_json
364 )
365 except:
366 log.info("catched exception!")
367 run_in_barbican_venv(ctx, cclient, ['sleep', '900'])
368
369 barbican_sec_resp = sec_req.getresponse()
370 if not (barbican_sec_resp.status >= 200 and
371 barbican_sec_resp.status < 300):
372 raise Exception("Cannot create secret")
373 barbican_data = json.loads(barbican_sec_resp.read().decode())
374 if 'secret_ref' not in barbican_data:
375 raise ValueError("Malformed secret creation response")
376 secret_ref = barbican_data["secret_ref"]
377 log.info("secret_ref=%s", secret_ref)
378 secret_url_parsed = urlparse(secret_ref)
379 acl_json = json.dumps(
380 {
381 "read": {
382 "users": [rgw_user_id],
383 "project-access": True
384 }
385 })
386 acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30)
387 acl_req.request(
388 'PUT',
389 secret_url_parsed.path+'/acl',
390 headers={'Content-Type': 'application/json',
391 'Accept': '*/*',
392 'X-Auth-Token': token_id},
393 body=acl_json
394 )
395 barbican_acl_resp = acl_req.getresponse()
396 if not (barbican_acl_resp.status >= 200 and
397 barbican_acl_resp.status < 300):
398 raise Exception("Cannot set ACL for secret")
399
400 key = {'id': secret_ref.split('secrets/')[1], 'payload': secret['base64']}
401 ctx.barbican.keys[secret['name']] = key
402
403 run_in_barbican_venv(ctx, cclient, ['sleep', '3'])
404 try:
405 yield
406 finally:
407 pass
408
409
410 @contextlib.contextmanager
411 def task(ctx, config):
412 """
413 Deploy and configure Keystone
414
415 Example of configuration:
416
417 tasks:
418 - local_cluster:
419 cluster_path: /home/adam/ceph-1/build
420 - local_rgw:
421 - tox: [ client.0 ]
422 - keystone:
423 client.0:
424 sha1: 17.0.0.0rc2
425 force-branch: master
426 projects:
427 - name: rgwcrypt
428 description: Encryption Tenant
429 - name: barbican
430 description: Barbican
431 - name: s3
432 description: S3 project
433 users:
434 - name: rgwcrypt-user
435 password: rgwcrypt-pass
436 project: rgwcrypt
437 - name: barbican-user
438 password: barbican-pass
439 project: barbican
440 - name: s3-user
441 password: s3-pass
442 project: s3
443 roles: [ name: Member, name: creator ]
444 role-mappings:
445 - name: Member
446 user: rgwcrypt-user
447 project: rgwcrypt
448 - name: admin
449 user: barbican-user
450 project: barbican
451 - name: creator
452 user: s3-user
453 project: s3
454 services:
455 - name: keystone
456 type: identity
457 description: Keystone Identity Service
458 - barbican:
459 client.0:
460 force-branch: master
461 use-keystone-role: client.0
462 keystone_authtoken:
463 auth_plugin: password
464 username: barbican-user
465 password: barbican-pass
466 user_domain_name: Default
467 rgw_user:
468 tenantName: rgwcrypt
469 username: rgwcrypt-user
470 password: rgwcrypt-pass
471 secrets:
472 - name: my-key-1
473 base64: a2V5MS5GcWVxKzhzTGNLaGtzQkg5NGVpb1FKcFpGb2c=
474 tenantName: s3
475 username: s3-user
476 password: s3-pass
477 - name: my-key-2
478 base64: a2V5Mi5yNUNNMGFzMVdIUVZxcCt5NGVmVGlQQ1k4YWg=
479 tenantName: s3
480 username: s3-user
481 password: s3-pass
482 - s3tests:
483 client.0:
484 force-branch: master
485 kms_key: my-key-1
486 - rgw:
487 client.0:
488 use-keystone-role: client.0
489 use-barbican-role: client.0
490 """
491 assert config is None or isinstance(config, list) \
492 or isinstance(config, dict), \
493 "task keystone only supports a list or dictionary for configuration"
494 all_clients = ['client.{id}'.format(id=id_)
495 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
496 if config is None:
497 config = all_clients
498 if isinstance(config, list):
499 config = dict.fromkeys(config)
500
501 overrides = ctx.config.get('overrides', {})
502 # merge each client section, not the top level.
503 for client in config.keys():
504 if not config[client]:
505 config[client] = {}
506 teuthology.deep_merge(config[client], overrides.get('barbican', {}))
507
508 log.debug('Barbican config is %s', config)
509
510 if not hasattr(ctx, 'keystone'):
511 raise ConfigError('barbican must run after the keystone task')
512
513
514 ctx.barbican = argparse.Namespace()
515 ctx.barbican.endpoints = assign_ports(ctx, config, 9311)
516 ctx.barbican.keys = {}
517
518 with contextutil.nested(
519 lambda: download(ctx=ctx, config=config),
520 lambda: setup_venv(ctx=ctx, config=config),
521 lambda: configure_barbican(ctx=ctx, config=config),
522 lambda: run_barbican(ctx=ctx, config=config),
523 lambda: create_secrets(ctx=ctx, config=config),
524 ):
525 yield