]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/barbican.py
d43568c6103a0ff973a66421c3abcf3d4b95b79f
[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 copy_policy_json(ctx, cclient):
161 run_in_barbican_dir(ctx, cclient,
162 ['cp',
163 get_barbican_dir(ctx)+'/etc/barbican/policy.json',
164 get_barbican_dir(ctx)])
165
166 def create_barbican_conf(ctx, cclient):
167 barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
168 barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
169 port=barbican_port)
170 log.info("barbican url=%s", barbican_url)
171
172 run_in_barbican_dir(ctx, cclient,
173 ['bash', '-c',
174 'echo -n -e "[DEFAULT]\nhost_href=' + barbican_url + '\n" ' + \
175 '>barbican.conf'])
176
177 @contextlib.contextmanager
178 def configure_barbican(ctx, config):
179 """
180 Configure barbican paste-api and barbican-api.
181 """
182 assert isinstance(config, dict)
183 (cclient, cconfig) = next(iter(config.items()))
184
185 keystone_role = cconfig.get('use-keystone-role', None)
186 if keystone_role is None:
187 raise ConfigError('use-keystone-role not defined in barbican task')
188
189 set_authtoken_params(ctx, cclient, cconfig)
190 fix_barbican_api(ctx, cclient)
191 fix_barbican_api_paste(ctx, cclient)
192 copy_policy_json(ctx, cclient)
193 create_barbican_conf(ctx, cclient)
194 try:
195 yield
196 finally:
197 pass
198
199 @contextlib.contextmanager
200 def 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
245 def create_secrets(ctx, config):
246 """
247 Create a main and an alternate s3 user.
248 """
249 assert isinstance(config, dict)
250 (cclient, cconfig) = next(iter(config.items()))
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
261 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
262 token_req.request(
263 'POST',
264 '/v3/auth/tokens',
265 headers={'Content-Type':'application/json'},
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 }
284 }
285 }))
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)
291 rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode())
292 rgw_user_id = rgw_access_user_data['token']['user']['id']
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
306 token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
307 token_req.request(
308 'POST',
309 '/v3/auth/tokens',
310 headers={'Content-Type':'application/json'},
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 }
328 }
329 }
330 }))
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
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))
340 token_id = token_resp.getheader('x-subject-token')
341
342 key1_json = json.dumps(
343 {
344 "name": secret['name'],
345 "expiration": expiration,
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
354 sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30)
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")
372 barbican_data = json.loads(barbican_sec_resp.read().decode())
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 })
385 acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30)
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
410 def 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:
423 sha1: 17.0.0.0rc2
424 force-branch: master
425 projects:
426 - name: rgwcrypt
427 description: Encryption Tenant
428 - name: barbican
429 description: Barbican
430 - name: s3
431 description: S3 project
432 users:
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
442 roles: [ name: Member, name: creator ]
443 role-mappings:
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