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