]> git.proxmox.com Git - ceph.git/blame - 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
CommitLineData
9f95a23c
TL
1"""
2Deploy and configure Barbican for Teuthology
3"""
4import argparse
5import contextlib
6import logging
f67539c2 7import http
9f95a23c 8import json
f67539c2
TL
9import time
10import math
11
12from urllib.parse import urlparse
9f95a23c
TL
13
14from teuthology import misc as teuthology
15from teuthology import contextutil
16from teuthology.orchestra import run
17from teuthology.exceptions import ConfigError
18
19log = logging.getLogger(__name__)
20
21
22@contextlib.contextmanager
23def 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
76def get_barbican_dir(ctx):
77 return '{tdir}/barbican'.format(tdir=teuthology.get_testdir(ctx))
78
79def 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
84def 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
92def 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():
a4b75251
TL
99 run_in_barbican_dir(ctx, client,
100 ['python3', '-m', 'venv', '.barbicanenv'])
20effc67
TL
101 run_in_barbican_venv(ctx, client,
102 ['pip', 'install', '--upgrade', 'pip'])
a4b75251
TL
103 run_in_barbican_venv(ctx, client,
104 ['pip', 'install', 'pytz',
105 '-e', get_barbican_dir(ctx)])
9f95a23c
TL
106 yield
107
108def 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
122def 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
147def 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
154def 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
9f95a23c
TL
160def 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
39ae355f
TL
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
9f95a23c
TL
179@contextlib.contextmanager
180def configure_barbican(ctx, config):
181 """
182 Configure barbican paste-api and barbican-api.
183 """
184 assert isinstance(config, dict)
e306af50 185 (cclient, cconfig) = next(iter(config.items()))
9f95a23c
TL
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)
9f95a23c
TL
194 create_barbican_conf(ctx, cclient)
195 try:
196 yield
197 finally:
198 pass
199
200@contextlib.contextmanager
201def 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
246def create_secrets(ctx, config):
247 """
248 Create a main and an alternate s3 user.
249 """
250 assert isinstance(config, dict)
e306af50 251 (cclient, cconfig) = next(iter(config.items()))
9f95a23c
TL
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
f67539c2 262 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
9f95a23c
TL
263 token_req.request(
264 'POST',
e306af50 265 '/v3/auth/tokens',
9f95a23c 266 headers={'Content-Type':'application/json'},
e306af50
TL
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 }
9f95a23c 285 }
e306af50 286 }))
9f95a23c
TL
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)
f67539c2 292 rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode())
e306af50 293 rgw_user_id = rgw_access_user_data['token']['user']['id']
9f95a23c
TL
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
f67539c2 307 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
9f95a23c
TL
308 token_req.request(
309 'POST',
e306af50 310 '/v3/auth/tokens',
9f95a23c 311 headers={'Content-Type':'application/json'},
e306af50
TL
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 }
9f95a23c
TL
329 }
330 }
e306af50 331 }))
9f95a23c
TL
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
f67539c2
TL
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))
e306af50 341 token_id = token_resp.getheader('x-subject-token')
9f95a23c
TL
342
343 key1_json = json.dumps(
344 {
345 "name": secret['name'],
f67539c2 346 "expiration": expiration,
9f95a23c
TL
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
f67539c2 355 sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30)
9f95a23c
TL
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")
f67539c2 373 barbican_data = json.loads(barbican_sec_resp.read().decode())
9f95a23c
TL
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 })
f67539c2 386 acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30)
9f95a23c
TL
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
411def 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:
e306af50 424 sha1: 17.0.0.0rc2
9f95a23c 425 force-branch: master
e306af50 426 projects:
9f95a23c
TL
427 - name: rgwcrypt
428 description: Encryption Tenant
429 - name: barbican
430 description: Barbican
431 - name: s3
432 description: S3 project
433 users:
9f95a23c
TL
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
e306af50 443 roles: [ name: Member, name: creator ]
9f95a23c 444 role-mappings:
9f95a23c
TL
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