]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | """ |
2 | Deploy and configure Keycloak for Teuthology | |
3 | """ | |
4 | import contextlib | |
5 | import logging | |
6 | import os | |
7 | ||
8 | from teuthology import misc as teuthology | |
9 | from teuthology import contextutil | |
10 | from teuthology.orchestra import run | |
11 | from teuthology.exceptions import ConfigError | |
12 | ||
13 | log = logging.getLogger(__name__) | |
14 | ||
15 | def get_keycloak_version(config): | |
16 | for client, client_config in config.items(): | |
17 | if 'keycloak_version' in client_config: | |
18 | keycloak_version = client_config.get('keycloak_version') | |
19 | return keycloak_version | |
20 | ||
21 | def get_keycloak_dir(ctx, config): | |
22 | keycloak_version = get_keycloak_version(config) | |
23 | current_version = 'keycloak-'+keycloak_version | |
24 | return '{tdir}/{ver}'.format(tdir=teuthology.get_testdir(ctx),ver=current_version) | |
25 | ||
26 | def run_in_keycloak_dir(ctx, client, config, args, **kwargs): | |
27 | return ctx.cluster.only(client).run( | |
28 | args=[ 'cd', get_keycloak_dir(ctx,config), run.Raw('&&'), ] + args, | |
29 | **kwargs | |
30 | ) | |
31 | ||
32 | def get_toxvenv_dir(ctx): | |
33 | return ctx.tox.venv_path | |
34 | ||
35 | def toxvenv_sh(ctx, remote, args, **kwargs): | |
36 | activate = get_toxvenv_dir(ctx) + '/bin/activate' | |
37 | return remote.sh(['source', activate, run.Raw('&&')] + args, **kwargs) | |
38 | ||
39 | @contextlib.contextmanager | |
40 | def install_packages(ctx, config): | |
41 | """ | |
42 | Downloading the two required tar files | |
43 | 1. Keycloak | |
44 | 2. Wildfly (Application Server) | |
45 | """ | |
46 | assert isinstance(config, dict) | |
47 | log.info('Installing packages for Keycloak...') | |
48 | ||
49 | for (client, _) in config.items(): | |
50 | (remote,) = ctx.cluster.only(client).remotes.keys() | |
51 | test_dir=teuthology.get_testdir(ctx) | |
52 | current_version = get_keycloak_version(config) | |
53 | link1 = 'https://downloads.jboss.org/keycloak/'+current_version+'/keycloak-'+current_version+'.tar.gz' | |
54 | toxvenv_sh(ctx, remote, ['wget', link1]) | |
55 | ||
56 | file1 = 'keycloak-'+current_version+'.tar.gz' | |
57 | toxvenv_sh(ctx, remote, ['tar', '-C', test_dir, '-xvzf', file1]) | |
58 | ||
59 | link2 ='https://downloads.jboss.org/keycloak/'+current_version+'/adapters/keycloak-oidc/keycloak-wildfly-adapter-dist-'+current_version+'.tar.gz' | |
60 | toxvenv_sh(ctx, remote, ['cd', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), 'wget', link2]) | |
61 | ||
62 | file2 = 'keycloak-wildfly-adapter-dist-'+current_version+'.tar.gz' | |
63 | toxvenv_sh(ctx, remote, ['tar', '-C', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config)), '-xvzf', '{tdr}/{file}'.format(tdr=get_keycloak_dir(ctx,config),file=file2)]) | |
64 | ||
65 | try: | |
66 | yield | |
67 | finally: | |
68 | log.info('Removing packaged dependencies of Keycloak...') | |
69 | for client in config: | |
20effc67 TL |
70 | current_version = get_keycloak_version(config) |
71 | ctx.cluster.only(client).run( | |
72 | args=['cd', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), 'rm', '-rf', 'keycloak-wildfly-adapter-dist-' + current_version + '.tar.gz'], | |
73 | ) | |
74 | ||
f67539c2 TL |
75 | ctx.cluster.only(client).run( |
76 | args=['rm', '-rf', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config))], | |
77 | ) | |
78 | ||
20effc67 TL |
79 | @contextlib.contextmanager |
80 | def download_conf(ctx, config): | |
81 | """ | |
82 | Downloads confi.py used in run_admin_cmds | |
83 | """ | |
84 | assert isinstance(config, dict) | |
85 | log.info('Downloading conf...') | |
86 | testdir = teuthology.get_testdir(ctx) | |
87 | conf_branch = 'main' | |
88 | conf_repo = 'https://github.com/TRYTOBE8TME/scripts.git' | |
89 | for (client, _) in config.items(): | |
90 | ctx.cluster.only(client).run( | |
91 | args=[ | |
92 | 'git', 'clone', | |
93 | '-b', conf_branch, | |
94 | conf_repo, | |
95 | '{tdir}/scripts'.format(tdir=testdir), | |
96 | ], | |
97 | ) | |
98 | try: | |
99 | yield | |
100 | finally: | |
101 | log.info('Removing conf...') | |
102 | testdir = teuthology.get_testdir(ctx) | |
103 | for client in config: | |
104 | ctx.cluster.only(client).run( | |
105 | args=[ | |
106 | 'rm', | |
107 | '-rf', | |
108 | '{tdir}/scripts'.format(tdir=testdir), | |
109 | ], | |
110 | ) | |
111 | ||
f67539c2 TL |
112 | @contextlib.contextmanager |
113 | def build(ctx,config): | |
114 | """ | |
115 | Build process which needs to be done before starting a server. | |
116 | """ | |
117 | assert isinstance(config, dict) | |
118 | log.info('Building Keycloak...') | |
119 | for (client,_) in config.items(): | |
120 | run_in_keycloak_dir(ctx, client, config,['cd', 'bin', run.Raw('&&'), './jboss-cli.sh', '--file=adapter-elytron-install-offline.cli']) | |
121 | try: | |
122 | yield | |
123 | finally: | |
124 | pass | |
125 | ||
126 | @contextlib.contextmanager | |
127 | def run_keycloak(ctx,config): | |
128 | """ | |
129 | This includes two parts: | |
130 | 1. Adding a user to keycloak which is actually used to log in when we start the server and check in browser. | |
131 | 2. Starting the server. | |
132 | """ | |
133 | assert isinstance(config, dict) | |
134 | log.info('Bringing up Keycloak...') | |
135 | for (client,_) in config.items(): | |
136 | (remote,) = ctx.cluster.only(client).remotes.keys() | |
137 | ||
138 | ctx.cluster.only(client).run( | |
139 | args=[ | |
140 | '{tdir}/bin/add-user-keycloak.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
141 | '-r', 'master', | |
142 | '-u', 'admin', | |
143 | '-p', 'admin', | |
144 | ], | |
145 | ) | |
146 | ||
147 | toxvenv_sh(ctx, remote, ['cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), './standalone.sh', run.Raw('&'), 'exit']) | |
148 | try: | |
149 | yield | |
150 | finally: | |
151 | log.info('Stopping Keycloak Server...') | |
152 | ||
153 | for (client, _) in config.items(): | |
154 | (remote,) = ctx.cluster.only(client).remotes.keys() | |
155 | toxvenv_sh(ctx, remote, ['cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), './jboss-cli.sh', '--connect', 'command=:shutdown']) | |
156 | ||
157 | @contextlib.contextmanager | |
158 | def run_admin_cmds(ctx,config): | |
159 | """ | |
160 | Running Keycloak Admin commands(kcadm commands) in order to get the token, aud value, thumbprint and realm name. | |
161 | """ | |
162 | assert isinstance(config, dict) | |
163 | log.info('Running admin commands...') | |
164 | for (client,_) in config.items(): | |
165 | (remote,) = ctx.cluster.only(client).remotes.keys() | |
166 | ||
167 | remote.run( | |
168 | args=[ | |
169 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
170 | 'config', 'credentials', | |
171 | '--server', 'http://localhost:8080/auth', | |
172 | '--realm', 'master', | |
173 | '--user', 'admin', | |
174 | '--password', 'admin', | |
175 | '--client', 'admin-cli', | |
176 | ], | |
177 | ) | |
178 | ||
179 | realm_name='demorealm' | |
180 | realm='realm={}'.format(realm_name) | |
181 | ||
182 | remote.run( | |
183 | args=[ | |
184 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
185 | 'create', 'realms', | |
186 | '-s', realm, | |
187 | '-s', 'enabled=true', | |
188 | '-s', 'accessTokenLifespan=1800', | |
189 | '-o', | |
190 | ], | |
191 | ) | |
192 | ||
193 | client_name='my_client' | |
194 | client='clientId={}'.format(client_name) | |
195 | ||
196 | remote.run( | |
197 | args=[ | |
198 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
199 | 'create', 'clients', | |
200 | '-r', realm_name, | |
201 | '-s', client, | |
20effc67 | 202 | '-s', 'directAccessGrantsEnabled=true', |
f67539c2 TL |
203 | '-s', 'redirectUris=["http://localhost:8080/myapp/*"]', |
204 | ], | |
205 | ) | |
206 | ||
207 | ans1= toxvenv_sh(ctx, remote, | |
208 | [ | |
209 | 'cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), | |
210 | './kcadm.sh', 'get', 'clients', | |
211 | '-r', realm_name, | |
212 | '-F', 'id,clientId', run.Raw('|'), | |
213 | 'jq', '-r', '.[] | select (.clientId == "my_client") | .id' | |
214 | ]) | |
215 | ||
216 | pre0=ans1.rstrip() | |
217 | pre1="clients/{}".format(pre0) | |
218 | ||
219 | remote.run( | |
220 | args=[ | |
221 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
222 | 'update', pre1, | |
223 | '-r', realm_name, | |
224 | '-s', 'enabled=true', | |
225 | '-s', 'serviceAccountsEnabled=true', | |
226 | '-s', 'redirectUris=["http://localhost:8080/myapp/*"]', | |
227 | ], | |
228 | ) | |
229 | ||
230 | ans2= pre1+'/client-secret' | |
231 | ||
232 | out2= toxvenv_sh(ctx, remote, | |
233 | [ | |
234 | 'cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), | |
235 | './kcadm.sh', 'get', ans2, | |
236 | '-r', realm_name, | |
237 | '-F', 'value' | |
238 | ]) | |
239 | ||
240 | ans0= '{client}:{secret}'.format(client=client_name,secret=out2[15:51]) | |
241 | ans3= 'client_secret={}'.format(out2[15:51]) | |
242 | clientid='client_id={}'.format(client_name) | |
243 | ||
20effc67 TL |
244 | proto_map = pre1+"/protocol-mappers/models" |
245 | uname = "username=testuser" | |
246 | upass = "password=testuser" | |
247 | ||
248 | remote.run( | |
249 | args=[ | |
250 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
251 | 'create', 'users', | |
252 | '-s', uname, | |
253 | '-s', 'enabled=true', | |
254 | '-s', 'attributes.\"https://aws.amazon.com/tags\"=\"{"principal_tags":{"Department":["Engineering", "Marketing"]}}\"', | |
255 | '-r', realm_name, | |
256 | ], | |
257 | ) | |
258 | ||
259 | sample = 'testuser' | |
260 | ||
261 | remote.run( | |
262 | args=[ | |
263 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
264 | 'set-password', | |
265 | '-r', realm_name, | |
266 | '--username', sample, | |
267 | '--new-password', sample, | |
268 | ], | |
269 | ) | |
270 | ||
271 | file_path = '{tdir}/scripts/confi.py'.format(tdir=teuthology.get_testdir(ctx)) | |
272 | ||
273 | remote.run( | |
274 | args=[ | |
275 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
276 | 'create', proto_map, | |
277 | '-r', realm_name, | |
278 | '-f', file_path, | |
279 | ], | |
280 | ) | |
281 | ||
282 | remote.run( | |
283 | args=[ | |
284 | '{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)), | |
285 | 'config', 'credentials', | |
286 | '--server', 'http://localhost:8080/auth', | |
287 | '--realm', realm_name, | |
288 | '--user', sample, | |
289 | '--password', sample, | |
290 | '--client', 'admin-cli', | |
291 | ], | |
292 | ) | |
293 | ||
294 | out9= toxvenv_sh(ctx, remote, | |
295 | [ | |
296 | 'curl', '-k', '-v', | |
297 | '-X', 'POST', | |
298 | '-H', 'Content-Type:application/x-www-form-urlencoded', | |
299 | '-d', 'scope=openid', | |
300 | '-d', 'grant_type=password', | |
301 | '-d', clientid, | |
302 | '-d', ans3, | |
303 | '-d', uname, | |
304 | '-d', upass, | |
305 | 'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token', run.Raw('|'), | |
306 | 'jq', '-r', '.access_token' | |
307 | ]) | |
308 | ||
309 | user_token_pre = out9.rstrip() | |
310 | user_token = '{}'.format(user_token_pre) | |
311 | ||
f67539c2 TL |
312 | out3= toxvenv_sh(ctx, remote, |
313 | [ | |
314 | 'curl', '-k', '-v', | |
315 | '-X', 'POST', | |
316 | '-H', 'Content-Type:application/x-www-form-urlencoded', | |
317 | '-d', 'scope=openid', | |
318 | '-d', 'grant_type=client_credentials', | |
319 | '-d', clientid, | |
320 | '-d', ans3, | |
321 | 'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token', run.Raw('|'), | |
322 | 'jq', '-r', '.access_token' | |
323 | ]) | |
324 | ||
325 | pre2=out3.rstrip() | |
326 | acc_token= 'token={}'.format(pre2) | |
327 | ans4= '{}'.format(pre2) | |
328 | ||
329 | out4= toxvenv_sh(ctx, remote, | |
330 | [ | |
331 | 'curl', '-k', '-v', | |
332 | '-X', 'GET', | |
333 | '-H', 'Content-Type:application/x-www-form-urlencoded', | |
334 | 'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/certs', run.Raw('|'), | |
335 | 'jq', '-r', '.keys[].x5c[]' | |
336 | ]) | |
337 | ||
338 | pre3=out4.rstrip() | |
339 | cert_value='{}'.format(pre3) | |
340 | start_value= "-----BEGIN CERTIFICATE-----\n" | |
341 | end_value= "\n-----END CERTIFICATE-----" | |
342 | user_data="" | |
343 | user_data+=start_value | |
344 | user_data+=cert_value | |
345 | user_data+=end_value | |
346 | ||
347 | remote.write_file( | |
348 | path='{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)), | |
349 | data=user_data | |
350 | ) | |
351 | ||
352 | out5= toxvenv_sh(ctx, remote, | |
353 | [ | |
354 | 'openssl', 'x509', | |
355 | '-in', '{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)), | |
356 | '--fingerprint', '--noout', '-sha1' | |
357 | ]) | |
358 | ||
359 | pre_ans= '{}'.format(out5[17:76]) | |
360 | ans5="" | |
361 | ||
362 | for character in pre_ans: | |
363 | if(character!=':'): | |
364 | ans5+=character | |
365 | ||
20effc67 TL |
366 | str1 = 'curl' |
367 | str2 = '-k' | |
368 | str3 = '-v' | |
369 | str4 = '-X' | |
370 | str5 = 'POST' | |
371 | str6 = '-u' | |
372 | str7 = '-d' | |
373 | str8 = 'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token/introspect' | |
374 | ||
375 | out6= toxvenv_sh(ctx, remote, | |
f67539c2 | 376 | [ |
20effc67 TL |
377 | str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.aud' |
378 | ]) | |
379 | ||
380 | out7= toxvenv_sh(ctx, remote, | |
381 | [ | |
382 | str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.sub' | |
383 | ]) | |
384 | ||
385 | out8= toxvenv_sh(ctx, remote, | |
386 | [ | |
387 | str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.azp' | |
f67539c2 TL |
388 | ]) |
389 | ||
390 | ans6=out6.rstrip() | |
20effc67 TL |
391 | ans7=out7.rstrip() |
392 | ans8=out8.rstrip() | |
f67539c2 TL |
393 | |
394 | os.environ['TOKEN']=ans4 | |
395 | os.environ['THUMBPRINT']=ans5 | |
396 | os.environ['AUD']=ans6 | |
20effc67 TL |
397 | os.environ['SUB']=ans7 |
398 | os.environ['AZP']=ans8 | |
399 | os.environ['USER_TOKEN']=user_token | |
f67539c2 TL |
400 | os.environ['KC_REALM']=realm_name |
401 | ||
402 | try: | |
403 | yield | |
404 | finally: | |
405 | log.info('Removing certificate.crt file...') | |
406 | for (client,_) in config.items(): | |
407 | (remote,) = ctx.cluster.only(client).remotes.keys() | |
408 | remote.run( | |
409 | args=['rm', '-f', | |
410 | '{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)), | |
411 | ], | |
412 | ) | |
413 | ||
20effc67 TL |
414 | remote.run( |
415 | args=['rm', '-f', | |
416 | '{tdir}/confi.py'.format(tdir=teuthology.get_testdir(ctx)), | |
417 | ], | |
418 | ) | |
419 | ||
f67539c2 TL |
420 | @contextlib.contextmanager |
421 | def task(ctx,config): | |
422 | """ | |
423 | To run keycloak the prerequisite is to run the tox task. Following is the way how to run | |
424 | tox and then keycloak:: | |
425 | ||
426 | tasks: | |
427 | - tox: [ client.0 ] | |
428 | - keycloak: | |
429 | client.0: | |
430 | keycloak_version: 11.0.0 | |
431 | ||
432 | To pass extra arguments to nose (e.g. to run a certain test):: | |
433 | ||
434 | tasks: | |
435 | - tox: [ client.0 ] | |
436 | - keycloak: | |
437 | client.0: | |
438 | keycloak_version: 11.0.0 | |
439 | - s3tests: | |
440 | client.0: | |
441 | extra_attrs: ['webidentity_test'] | |
442 | ||
443 | """ | |
444 | assert config is None or isinstance(config, list) \ | |
445 | or isinstance(config, dict), \ | |
446 | "task keycloak only supports a list or dictionary for configuration" | |
447 | ||
448 | if not hasattr(ctx, 'tox'): | |
449 | raise ConfigError('keycloak must run after the tox task') | |
450 | ||
451 | all_clients = ['client.{id}'.format(id=id_) | |
452 | for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')] | |
453 | if config is None: | |
454 | config = all_clients | |
455 | if isinstance(config, list): | |
456 | config = dict.fromkeys(config) | |
457 | ||
458 | log.debug('Keycloak config is %s', config) | |
459 | ||
460 | with contextutil.nested( | |
461 | lambda: install_packages(ctx=ctx, config=config), | |
462 | lambda: build(ctx=ctx, config=config), | |
463 | lambda: run_keycloak(ctx=ctx, config=config), | |
20effc67 | 464 | lambda: download_conf(ctx=ctx, config=config), |
f67539c2 TL |
465 | lambda: run_admin_cmds(ctx=ctx, config=config), |
466 | ): | |
467 | yield | |
468 |