]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/barbican.py
update ceph source to reef 18.2.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'])
aee94f69
TL
139 admin_url = 'http://{host}:{port}/v3'.format(host=public_host,
140 port=public_port)
9f95a23c
TL
141 run_in_barbican_dir(ctx, cclient,
142 ['sed', '-i',
143 '/[[]filter:authtoken]/{p;s##'+'auth_url = {}'.format(admin_url)+'#;}',
144 'etc/barbican/barbican-api-paste.ini'])
145
146def fix_barbican_api_paste(ctx, cclient):
147 run_in_barbican_dir(ctx, cclient,
148 ['sed', '-i', '-n',
149 '/\\[pipeline:barbican_api]/ {p;n; /^pipeline =/ '+
150 '{ s/.*/pipeline = unauthenticated-context apiapp/;p;d } } ; p',
151 './etc/barbican/barbican-api-paste.ini'])
152
153def fix_barbican_api(ctx, cclient):
154 run_in_barbican_dir(ctx, cclient,
155 ['sed', '-i',
156 '/prop_dir =/ s#etc/barbican#{}/etc/barbican#'.format(get_barbican_dir(ctx)),
157 'bin/barbican-api'])
158
9f95a23c
TL
159def create_barbican_conf(ctx, cclient):
160 barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
161 barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
162 port=barbican_port)
163 log.info("barbican url=%s", barbican_url)
164
165 run_in_barbican_dir(ctx, cclient,
166 ['bash', '-c',
167 'echo -n -e "[DEFAULT]\nhost_href=' + barbican_url + '\n" ' + \
168 '>barbican.conf'])
169
39ae355f
TL
170 log.info("run barbican db upgrade")
171 config_path = get_barbican_dir(ctx) + '/barbican.conf'
172 run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path,
173 'db', 'upgrade'])
174 log.info("run barbican db sync_secret_stores")
175 run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path,
176 'db', 'sync_secret_stores'])
177
9f95a23c
TL
178@contextlib.contextmanager
179def configure_barbican(ctx, config):
180 """
181 Configure barbican paste-api and barbican-api.
182 """
183 assert isinstance(config, dict)
e306af50 184 (cclient, cconfig) = next(iter(config.items()))
9f95a23c
TL
185
186 keystone_role = cconfig.get('use-keystone-role', None)
187 if keystone_role is None:
188 raise ConfigError('use-keystone-role not defined in barbican task')
189
190 set_authtoken_params(ctx, cclient, cconfig)
191 fix_barbican_api(ctx, cclient)
192 fix_barbican_api_paste(ctx, cclient)
9f95a23c
TL
193 create_barbican_conf(ctx, cclient)
194 try:
195 yield
196 finally:
197 pass
198
199@contextlib.contextmanager
200def run_barbican(ctx, config):
201 assert isinstance(config, dict)
202 log.info('Running barbican...')
203
204 for (client, _) in config.items():
205 (remote,) = ctx.cluster.only(client).remotes.keys()
206 cluster_name, _, client_id = teuthology.split_role(client)
207
208 # start the public endpoint
209 client_public_with_id = 'barbican.public' + '.' + client_id
210
211 run_cmd = ['cd', get_barbican_dir(ctx), run.Raw('&&'),
212 '.', '.barbicanenv/bin/activate', run.Raw('&&'),
213 'HOME={}'.format(get_barbican_dir(ctx)), run.Raw('&&'),
214 'bin/barbican-api',
215 run.Raw('& { read; kill %1; }')]
216 #run.Raw('1>/dev/null')
217
218 run_cmd = 'cd ' + get_barbican_dir(ctx) + ' && ' + \
219 '. .barbicanenv/bin/activate && ' + \
220 'HOME={}'.format(get_barbican_dir(ctx)) + ' && ' + \
221 'exec bin/barbican-api & { read; kill %1; }'
222
223 ctx.daemons.add_daemon(
224 remote, 'barbican', client_public_with_id,
225 cluster=cluster_name,
226 args=['bash', '-c', run_cmd],
227 logger=log.getChild(client),
228 stdin=run.PIPE,
229 cwd=get_barbican_dir(ctx),
230 wait=False,
231 check_status=False,
232 )
233
234 # sleep driven synchronization
235 run_in_barbican_venv(ctx, client, ['sleep', '15'])
236 try:
237 yield
238 finally:
239 log.info('Stopping Barbican instance')
240 ctx.daemons.get_daemon('barbican', client_public_with_id,
241 cluster_name).stop()
242
243
244@contextlib.contextmanager
245def create_secrets(ctx, config):
246 """
247 Create a main and an alternate s3 user.
248 """
249 assert isinstance(config, dict)
e306af50 250 (cclient, cconfig) = next(iter(config.items()))
9f95a23c
TL
251
252 rgw_user = cconfig['rgw_user']
253
254 keystone_role = cconfig.get('use-keystone-role', None)
255 keystone_host, keystone_port = ctx.keystone.public_endpoints[keystone_role]
256 barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
257 barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
258 port=barbican_port)
259 log.info("barbican_url=%s", barbican_url)
260 #fetching user_id of user that gets secrets for radosgw
f67539c2 261 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
9f95a23c
TL
262 token_req.request(
263 'POST',
e306af50 264 '/v3/auth/tokens',
9f95a23c 265 headers={'Content-Type':'application/json'},
e306af50
TL
266 body=json.dumps({
267 "auth": {
268 "identity": {
269 "methods": ["password"],
270 "password": {
271 "user": {
272 "domain": {"id": "default"},
273 "name": rgw_user["username"],
274 "password": rgw_user["password"]
275 }
276 }
277 },
278 "scope": {
279 "project": {
280 "domain": {"id": "default"},
281 "name": rgw_user["tenantName"]
282 }
283 }
9f95a23c 284 }
e306af50 285 }))
9f95a23c
TL
286 rgw_access_user_resp = token_req.getresponse()
287 if not (rgw_access_user_resp.status >= 200 and
288 rgw_access_user_resp.status < 300):
289 raise Exception("Cannot authenticate user "+rgw_user["username"]+" for secret creation")
290 # baru_resp = json.loads(baru_req.data)
f67539c2 291 rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode())
e306af50 292 rgw_user_id = rgw_access_user_data['token']['user']['id']
9f95a23c
TL
293 if 'secrets' in cconfig:
294 for secret in cconfig['secrets']:
295 if 'name' not in secret:
296 raise ConfigError('barbican.secrets must have "name" field')
297 if 'base64' not in secret:
298 raise ConfigError('barbican.secrets must have "base64" field')
299 if 'tenantName' not in secret:
300 raise ConfigError('barbican.secrets must have "tenantName" field')
301 if 'username' not in secret:
302 raise ConfigError('barbican.secrets must have "username" field')
303 if 'password' not in secret:
304 raise ConfigError('barbican.secrets must have "password" field')
305
f67539c2 306 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
9f95a23c
TL
307 token_req.request(
308 'POST',
e306af50 309 '/v3/auth/tokens',
9f95a23c 310 headers={'Content-Type':'application/json'},
e306af50
TL
311 body=json.dumps({
312 "auth": {
313 "identity": {
314 "methods": ["password"],
315 "password": {
316 "user": {
317 "domain": {"id": "default"},
318 "name": secret["username"],
319 "password": secret["password"]
320 }
321 }
322 },
323 "scope": {
324 "project": {
325 "domain": {"id": "default"},
326 "name": secret["tenantName"]
327 }
9f95a23c
TL
328 }
329 }
e306af50 330 }))
9f95a23c
TL
331 token_resp = token_req.getresponse()
332 if not (token_resp.status >= 200 and
333 token_resp.status < 300):
334 raise Exception("Cannot authenticate user "+secret["username"]+" for secret creation")
335
f67539c2
TL
336 expire = time.time() + 5400 # now + 90m
337 (expire_fract,dummy) = math.modf(expire)
338 expire_format = "%%FT%%T.%06d" % (round(expire_fract*1000000))
339 expiration = time.strftime(expire_format, time.gmtime(expire))
e306af50 340 token_id = token_resp.getheader('x-subject-token')
9f95a23c
TL
341
342 key1_json = json.dumps(
343 {
344 "name": secret['name'],
f67539c2 345 "expiration": expiration,
9f95a23c
TL
346 "algorithm": "aes",
347 "bit_length": 256,
348 "mode": "cbc",
349 "payload": secret['base64'],
350 "payload_content_type": "application/octet-stream",
351 "payload_content_encoding": "base64"
352 })
353
f67539c2 354 sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30)
9f95a23c
TL
355 try:
356 sec_req.request(
357 'POST',
358 '/v1/secrets',
359 headers={'Content-Type': 'application/json',
360 'Accept': '*/*',
361 'X-Auth-Token': token_id},
362 body=key1_json
363 )
364 except:
365 log.info("catched exception!")
366 run_in_barbican_venv(ctx, cclient, ['sleep', '900'])
367
368 barbican_sec_resp = sec_req.getresponse()
369 if not (barbican_sec_resp.status >= 200 and
370 barbican_sec_resp.status < 300):
371 raise Exception("Cannot create secret")
f67539c2 372 barbican_data = json.loads(barbican_sec_resp.read().decode())
9f95a23c
TL
373 if 'secret_ref' not in barbican_data:
374 raise ValueError("Malformed secret creation response")
375 secret_ref = barbican_data["secret_ref"]
376 log.info("secret_ref=%s", secret_ref)
377 secret_url_parsed = urlparse(secret_ref)
378 acl_json = json.dumps(
379 {
380 "read": {
381 "users": [rgw_user_id],
382 "project-access": True
383 }
384 })
f67539c2 385 acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30)
9f95a23c
TL
386 acl_req.request(
387 'PUT',
388 secret_url_parsed.path+'/acl',
389 headers={'Content-Type': 'application/json',
390 'Accept': '*/*',
391 'X-Auth-Token': token_id},
392 body=acl_json
393 )
394 barbican_acl_resp = acl_req.getresponse()
395 if not (barbican_acl_resp.status >= 200 and
396 barbican_acl_resp.status < 300):
397 raise Exception("Cannot set ACL for secret")
398
399 key = {'id': secret_ref.split('secrets/')[1], 'payload': secret['base64']}
400 ctx.barbican.keys[secret['name']] = key
401
402 run_in_barbican_venv(ctx, cclient, ['sleep', '3'])
403 try:
404 yield
405 finally:
406 pass
407
408
409@contextlib.contextmanager
410def task(ctx, config):
411 """
412 Deploy and configure Keystone
413
414 Example of configuration:
415
416 tasks:
417 - local_cluster:
418 cluster_path: /home/adam/ceph-1/build
419 - local_rgw:
420 - tox: [ client.0 ]
421 - keystone:
422 client.0:
e306af50 423 sha1: 17.0.0.0rc2
9f95a23c 424 force-branch: master
e306af50 425 projects:
9f95a23c
TL
426 - name: rgwcrypt
427 description: Encryption Tenant
428 - name: barbican
429 description: Barbican
430 - name: s3
431 description: S3 project
432 users:
9f95a23c
TL
433 - name: rgwcrypt-user
434 password: rgwcrypt-pass
435 project: rgwcrypt
436 - name: barbican-user
437 password: barbican-pass
438 project: barbican
439 - name: s3-user
440 password: s3-pass
441 project: s3
e306af50 442 roles: [ name: Member, name: creator ]
9f95a23c 443 role-mappings:
9f95a23c
TL
444 - name: Member
445 user: rgwcrypt-user
446 project: rgwcrypt
447 - name: admin
448 user: barbican-user
449 project: barbican
450 - name: creator
451 user: s3-user
452 project: s3
453 services:
454 - name: keystone
455 type: identity
456 description: Keystone Identity Service
457 - barbican:
458 client.0:
459 force-branch: master
460 use-keystone-role: client.0
461 keystone_authtoken:
462 auth_plugin: password
463 username: barbican-user
464 password: barbican-pass
465 user_domain_name: Default
466 rgw_user:
467 tenantName: rgwcrypt
468 username: rgwcrypt-user
469 password: rgwcrypt-pass
470 secrets:
471 - name: my-key-1
472 base64: a2V5MS5GcWVxKzhzTGNLaGtzQkg5NGVpb1FKcFpGb2c=
473 tenantName: s3
474 username: s3-user
475 password: s3-pass
476 - name: my-key-2
477 base64: a2V5Mi5yNUNNMGFzMVdIUVZxcCt5NGVmVGlQQ1k4YWg=
478 tenantName: s3
479 username: s3-user
480 password: s3-pass
481 - s3tests:
482 client.0:
483 force-branch: master
484 kms_key: my-key-1
485 - rgw:
486 client.0:
487 use-keystone-role: client.0
488 use-barbican-role: client.0
489 """
490 assert config is None or isinstance(config, list) \
491 or isinstance(config, dict), \
492 "task keystone only supports a list or dictionary for configuration"
493 all_clients = ['client.{id}'.format(id=id_)
494 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
495 if config is None:
496 config = all_clients
497 if isinstance(config, list):
498 config = dict.fromkeys(config)
499
500 overrides = ctx.config.get('overrides', {})
501 # merge each client section, not the top level.
502 for client in config.keys():
503 if not config[client]:
504 config[client] = {}
505 teuthology.deep_merge(config[client], overrides.get('barbican', {}))
506
507 log.debug('Barbican config is %s', config)
508
509 if not hasattr(ctx, 'keystone'):
510 raise ConfigError('barbican must run after the keystone task')
511
512
513 ctx.barbican = argparse.Namespace()
514 ctx.barbican.endpoints = assign_ports(ctx, config, 9311)
515 ctx.barbican.keys = {}
516
517 with contextutil.nested(
518 lambda: download(ctx=ctx, config=config),
519 lambda: setup_venv(ctx=ctx, config=config),
520 lambda: configure_barbican(ctx=ctx, config=config),
521 lambda: run_barbican(ctx=ctx, config=config),
522 lambda: create_secrets(ctx=ctx, config=config),
523 ):
524 yield