]>
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']) | |
aee94f69 TL |
139 | admin_url = 'http://{host}:{port}/v3'.format(host=public_host, |
140 | port=public_port) | |
9f95a23c TL |
141 | run_in_barbican_dir(ctx, cclient, |
142 | ['sed', '-i', | |
143 | '/[[]filter:authtoken]/{p;s##'+'auth_url = {}'.format(admin_url)+'#;}', | |
144 | 'etc/barbican/barbican-api-paste.ini']) | |
145 | ||
146 | def fix_barbican_api_paste(ctx, cclient): | |
147 | run_in_barbican_dir(ctx, cclient, | |
148 | ['sed', '-i', '-n', | |
149 | '/\\[pipeline:barbican_api]/ {p;n; /^pipeline =/ '+ | |
150 | '{ s/.*/pipeline = unauthenticated-context apiapp/;p;d } } ; p', | |
151 | './etc/barbican/barbican-api-paste.ini']) | |
152 | ||
153 | def fix_barbican_api(ctx, cclient): | |
154 | run_in_barbican_dir(ctx, cclient, | |
155 | ['sed', '-i', | |
156 | '/prop_dir =/ s#etc/barbican#{}/etc/barbican#'.format(get_barbican_dir(ctx)), | |
157 | 'bin/barbican-api']) | |
158 | ||
9f95a23c TL |
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 | ||
39ae355f TL |
170 | log.info("run barbican db upgrade") |
171 | config_path = get_barbican_dir(ctx) + '/barbican.conf' | |
172 | run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path, | |
173 | 'db', 'upgrade']) | |
174 | log.info("run barbican db sync_secret_stores") | |
175 | run_in_barbican_venv(ctx, cclient, ['barbican-manage', '--config-file', config_path, | |
176 | 'db', 'sync_secret_stores']) | |
177 | ||
9f95a23c TL |
178 | @contextlib.contextmanager |
179 | def configure_barbican(ctx, config): | |
180 | """ | |
181 | Configure barbican paste-api and barbican-api. | |
182 | """ | |
183 | assert isinstance(config, dict) | |
e306af50 | 184 | (cclient, cconfig) = next(iter(config.items())) |
9f95a23c TL |
185 | |
186 | keystone_role = cconfig.get('use-keystone-role', None) | |
187 | if keystone_role is None: | |
188 | raise ConfigError('use-keystone-role not defined in barbican task') | |
189 | ||
190 | set_authtoken_params(ctx, cclient, cconfig) | |
191 | fix_barbican_api(ctx, cclient) | |
192 | fix_barbican_api_paste(ctx, cclient) | |
9f95a23c TL |
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) | |
e306af50 | 250 | (cclient, cconfig) = next(iter(config.items())) |
9f95a23c TL |
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 | |
f67539c2 | 261 | token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30) |
9f95a23c TL |
262 | token_req.request( |
263 | 'POST', | |
e306af50 | 264 | '/v3/auth/tokens', |
9f95a23c | 265 | headers={'Content-Type':'application/json'}, |
e306af50 TL |
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 | } | |
9f95a23c | 284 | } |
e306af50 | 285 | })) |
9f95a23c TL |
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) | |
f67539c2 | 291 | rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode()) |
e306af50 | 292 | rgw_user_id = rgw_access_user_data['token']['user']['id'] |
9f95a23c TL |
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 | ||
f67539c2 | 306 | token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30) |
9f95a23c TL |
307 | token_req.request( |
308 | 'POST', | |
e306af50 | 309 | '/v3/auth/tokens', |
9f95a23c | 310 | headers={'Content-Type':'application/json'}, |
e306af50 TL |
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 | } | |
9f95a23c TL |
328 | } |
329 | } | |
e306af50 | 330 | })) |
9f95a23c TL |
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 | ||
f67539c2 TL |
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)) | |
e306af50 | 340 | token_id = token_resp.getheader('x-subject-token') |
9f95a23c TL |
341 | |
342 | key1_json = json.dumps( | |
343 | { | |
344 | "name": secret['name'], | |
f67539c2 | 345 | "expiration": expiration, |
9f95a23c TL |
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 | ||
f67539c2 | 354 | sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30) |
9f95a23c TL |
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") | |
f67539c2 | 372 | barbican_data = json.loads(barbican_sec_resp.read().decode()) |
9f95a23c TL |
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 | }) | |
f67539c2 | 385 | acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30) |
9f95a23c TL |
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: | |
e306af50 | 423 | sha1: 17.0.0.0rc2 |
9f95a23c | 424 | force-branch: master |
e306af50 | 425 | projects: |
9f95a23c TL |
426 | - name: rgwcrypt |
427 | description: Encryption Tenant | |
428 | - name: barbican | |
429 | description: Barbican | |
430 | - name: s3 | |
431 | description: S3 project | |
432 | users: | |
9f95a23c TL |
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 | |
e306af50 | 442 | roles: [ name: Member, name: creator ] |
9f95a23c | 443 | role-mappings: |
9f95a23c TL |
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 |