]>
Commit | Line | Data |
---|---|---|
9f95a23c TL |
1 | """ |
2 | Deploy and configure Barbican for Teuthology | |
3 | """ | |
4 | import argparse | |
5 | import contextlib | |
6 | import logging | |
e306af50 TL |
7 | import six |
8 | from six.moves import http_client | |
9 | from six.moves.urllib.parse import urlparse | |
9f95a23c | 10 | import json |
6d8e3169 FG |
11 | import time |
12 | import math | |
9f95a23c TL |
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) | |
e306af50 | 178 | (cclient, cconfig) = next(iter(config.items())) |
9f95a23c TL |
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) | |
e306af50 | 245 | (cclient, cconfig) = next(iter(config.items())) |
9f95a23c TL |
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 | |
e306af50 | 256 | token_req = http_client.HTTPConnection(keystone_host, keystone_port, timeout=30) |
9f95a23c TL |
257 | token_req.request( |
258 | 'POST', | |
e306af50 | 259 | '/v3/auth/tokens', |
9f95a23c | 260 | headers={'Content-Type':'application/json'}, |
e306af50 TL |
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 | } | |
9f95a23c | 279 | } |
e306af50 | 280 | })) |
9f95a23c TL |
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) | |
e306af50 TL |
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'] | |
9f95a23c TL |
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 | ||
e306af50 | 301 | token_req = http_client.HTTPConnection(keystone_host, keystone_port, timeout=30) |
9f95a23c TL |
302 | token_req.request( |
303 | 'POST', | |
e306af50 | 304 | '/v3/auth/tokens', |
9f95a23c | 305 | headers={'Content-Type':'application/json'}, |
e306af50 TL |
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 | } | |
9f95a23c TL |
323 | } |
324 | } | |
e306af50 | 325 | })) |
9f95a23c TL |
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 | ||
6d8e3169 FG |
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)) | |
e306af50 | 335 | token_id = token_resp.getheader('x-subject-token') |
9f95a23c TL |
336 | |
337 | key1_json = json.dumps( | |
338 | { | |
339 | "name": secret['name'], | |
6d8e3169 | 340 | "expiration": expiration, |
9f95a23c TL |
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 | ||
e306af50 | 349 | sec_req = http_client.HTTPConnection(barbican_host, barbican_port, timeout=30) |
9f95a23c TL |
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") | |
e306af50 | 367 | barbican_data = json.loads(six.ensure_str(barbican_sec_resp.read())) |
9f95a23c TL |
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 | }) | |
e306af50 | 380 | acl_req = http_client.HTTPConnection(secret_url_parsed.netloc, timeout=30) |
9f95a23c TL |
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: | |
e306af50 | 418 | sha1: 17.0.0.0rc2 |
9f95a23c | 419 | force-branch: master |
e306af50 | 420 | projects: |
9f95a23c TL |
421 | - name: rgwcrypt |
422 | description: Encryption Tenant | |
423 | - name: barbican | |
424 | description: Barbican | |
425 | - name: s3 | |
426 | description: S3 project | |
427 | users: | |
9f95a23c TL |
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 | |
e306af50 | 437 | roles: [ name: Member, name: creator ] |
9f95a23c | 438 | role-mappings: |
9f95a23c TL |
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 |