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