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