ceph/qa/tasks/keycloak.py

469 lines
16 KiB
Python

"""
Deploy and configure Keycloak for Teuthology
"""
import contextlib
import logging
import os
from teuthology import misc as teuthology
from teuthology import contextutil
from teuthology.orchestra import run
from teuthology.exceptions import ConfigError
log = logging.getLogger(__name__)
def get_keycloak_version(config):
for client, client_config in config.items():
if 'keycloak_version' in client_config:
keycloak_version = client_config.get('keycloak_version')
return keycloak_version
def get_keycloak_dir(ctx, config):
keycloak_version = get_keycloak_version(config)
current_version = 'keycloak-'+keycloak_version
return '{tdir}/{ver}'.format(tdir=teuthology.get_testdir(ctx),ver=current_version)
def run_in_keycloak_dir(ctx, client, config, args, **kwargs):
return ctx.cluster.only(client).run(
args=[ 'cd', get_keycloak_dir(ctx,config), run.Raw('&&'), ] + args,
**kwargs
)
def get_toxvenv_dir(ctx):
return ctx.tox.venv_path
def toxvenv_sh(ctx, remote, args, **kwargs):
activate = get_toxvenv_dir(ctx) + '/bin/activate'
return remote.sh(['source', activate, run.Raw('&&')] + args, **kwargs)
@contextlib.contextmanager
def install_packages(ctx, config):
"""
Downloading the two required tar files
1. Keycloak
2. Wildfly (Application Server)
"""
assert isinstance(config, dict)
log.info('Installing packages for Keycloak...')
for (client, _) in config.items():
(remote,) = ctx.cluster.only(client).remotes.keys()
test_dir=teuthology.get_testdir(ctx)
current_version = get_keycloak_version(config)
link1 = 'https://downloads.jboss.org/keycloak/'+current_version+'/keycloak-'+current_version+'.tar.gz'
toxvenv_sh(ctx, remote, ['wget', link1])
file1 = 'keycloak-'+current_version+'.tar.gz'
toxvenv_sh(ctx, remote, ['tar', '-C', test_dir, '-xvzf', file1])
link2 ='https://downloads.jboss.org/keycloak/'+current_version+'/adapters/keycloak-oidc/keycloak-wildfly-adapter-dist-'+current_version+'.tar.gz'
toxvenv_sh(ctx, remote, ['cd', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), 'wget', link2])
file2 = 'keycloak-wildfly-adapter-dist-'+current_version+'.tar.gz'
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)])
try:
yield
finally:
log.info('Removing packaged dependencies of Keycloak...')
for client in config:
current_version = get_keycloak_version(config)
ctx.cluster.only(client).run(
args=['cd', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), 'rm', '-rf', 'keycloak-wildfly-adapter-dist-' + current_version + '.tar.gz'],
)
ctx.cluster.only(client).run(
args=['rm', '-rf', '{tdir}'.format(tdir=get_keycloak_dir(ctx,config))],
)
@contextlib.contextmanager
def download_conf(ctx, config):
"""
Downloads confi.py used in run_admin_cmds
"""
assert isinstance(config, dict)
log.info('Downloading conf...')
testdir = teuthology.get_testdir(ctx)
conf_branch = 'main'
conf_repo = 'https://github.com/TRYTOBE8TME/scripts.git'
for (client, _) in config.items():
ctx.cluster.only(client).run(
args=[
'git', 'clone',
'-b', conf_branch,
conf_repo,
'{tdir}/scripts'.format(tdir=testdir),
],
)
try:
yield
finally:
log.info('Removing conf...')
testdir = teuthology.get_testdir(ctx)
for client in config:
ctx.cluster.only(client).run(
args=[
'rm',
'-rf',
'{tdir}/scripts'.format(tdir=testdir),
],
)
@contextlib.contextmanager
def build(ctx,config):
"""
Build process which needs to be done before starting a server.
"""
assert isinstance(config, dict)
log.info('Building Keycloak...')
for (client,_) in config.items():
run_in_keycloak_dir(ctx, client, config,['cd', 'bin', run.Raw('&&'), './jboss-cli.sh', '--file=adapter-elytron-install-offline.cli'])
try:
yield
finally:
pass
@contextlib.contextmanager
def run_keycloak(ctx,config):
"""
This includes two parts:
1. Adding a user to keycloak which is actually used to log in when we start the server and check in browser.
2. Starting the server.
"""
assert isinstance(config, dict)
log.info('Bringing up Keycloak...')
for (client,_) in config.items():
(remote,) = ctx.cluster.only(client).remotes.keys()
ctx.cluster.only(client).run(
args=[
'{tdir}/bin/add-user-keycloak.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'-r', 'master',
'-u', 'admin',
'-p', 'admin',
],
)
toxvenv_sh(ctx, remote, ['cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), './standalone.sh', run.Raw('&'), 'exit'])
try:
yield
finally:
log.info('Stopping Keycloak Server...')
for (client, _) in config.items():
(remote,) = ctx.cluster.only(client).remotes.keys()
toxvenv_sh(ctx, remote, ['cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'), './jboss-cli.sh', '--connect', 'command=:shutdown'])
@contextlib.contextmanager
def run_admin_cmds(ctx,config):
"""
Running Keycloak Admin commands(kcadm commands) in order to get the token, aud value, thumbprint and realm name.
"""
assert isinstance(config, dict)
log.info('Running admin commands...')
for (client,_) in config.items():
(remote,) = ctx.cluster.only(client).remotes.keys()
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'config', 'credentials',
'--server', 'http://localhost:8080/auth',
'--realm', 'master',
'--user', 'admin',
'--password', 'admin',
'--client', 'admin-cli',
],
)
realm_name='demorealm'
realm='realm={}'.format(realm_name)
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'create', 'realms',
'-s', realm,
'-s', 'enabled=true',
'-s', 'accessTokenLifespan=1800',
'-o',
],
)
client_name='my_client'
client='clientId={}'.format(client_name)
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'create', 'clients',
'-r', realm_name,
'-s', client,
'-s', 'directAccessGrantsEnabled=true',
'-s', 'redirectUris=["http://localhost:8080/myapp/*"]',
],
)
ans1= toxvenv_sh(ctx, remote,
[
'cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'),
'./kcadm.sh', 'get', 'clients',
'-r', realm_name,
'-F', 'id,clientId', run.Raw('|'),
'jq', '-r', '.[] | select (.clientId == "my_client") | .id'
])
pre0=ans1.rstrip()
pre1="clients/{}".format(pre0)
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'update', pre1,
'-r', realm_name,
'-s', 'enabled=true',
'-s', 'serviceAccountsEnabled=true',
'-s', 'redirectUris=["http://localhost:8080/myapp/*"]',
],
)
ans2= pre1+'/client-secret'
out2= toxvenv_sh(ctx, remote,
[
'cd', '{tdir}/bin'.format(tdir=get_keycloak_dir(ctx,config)), run.Raw('&&'),
'./kcadm.sh', 'get', ans2,
'-r', realm_name,
'-F', 'value'
])
ans0= '{client}:{secret}'.format(client=client_name,secret=out2[15:51])
ans3= 'client_secret={}'.format(out2[15:51])
clientid='client_id={}'.format(client_name)
proto_map = pre1+"/protocol-mappers/models"
uname = "username=testuser"
upass = "password=testuser"
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'create', 'users',
'-s', uname,
'-s', 'enabled=true',
'-s', 'attributes.\"https://aws.amazon.com/tags\"=\"{"principal_tags":{"Department":["Engineering", "Marketing"]}}\"',
'-r', realm_name,
],
)
sample = 'testuser'
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'set-password',
'-r', realm_name,
'--username', sample,
'--new-password', sample,
],
)
file_path = '{tdir}/scripts/confi.py'.format(tdir=teuthology.get_testdir(ctx))
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'create', proto_map,
'-r', realm_name,
'-f', file_path,
],
)
remote.run(
args=[
'{tdir}/bin/kcadm.sh'.format(tdir=get_keycloak_dir(ctx,config)),
'config', 'credentials',
'--server', 'http://localhost:8080/auth',
'--realm', realm_name,
'--user', sample,
'--password', sample,
'--client', 'admin-cli',
],
)
out9= toxvenv_sh(ctx, remote,
[
'curl', '-k', '-v',
'-X', 'POST',
'-H', 'Content-Type:application/x-www-form-urlencoded',
'-d', 'scope=openid',
'-d', 'grant_type=password',
'-d', clientid,
'-d', ans3,
'-d', uname,
'-d', upass,
'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token', run.Raw('|'),
'jq', '-r', '.access_token'
])
user_token_pre = out9.rstrip()
user_token = '{}'.format(user_token_pre)
out3= toxvenv_sh(ctx, remote,
[
'curl', '-k', '-v',
'-X', 'POST',
'-H', 'Content-Type:application/x-www-form-urlencoded',
'-d', 'scope=openid',
'-d', 'grant_type=client_credentials',
'-d', clientid,
'-d', ans3,
'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token', run.Raw('|'),
'jq', '-r', '.access_token'
])
pre2=out3.rstrip()
acc_token= 'token={}'.format(pre2)
ans4= '{}'.format(pre2)
out4= toxvenv_sh(ctx, remote,
[
'curl', '-k', '-v',
'-X', 'GET',
'-H', 'Content-Type:application/x-www-form-urlencoded',
'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/certs', run.Raw('|'),
'jq', '-r', '.keys[].x5c[]'
])
pre3=out4.rstrip()
cert_value='{}'.format(pre3)
start_value= "-----BEGIN CERTIFICATE-----\n"
end_value= "\n-----END CERTIFICATE-----"
user_data=""
user_data+=start_value
user_data+=cert_value
user_data+=end_value
remote.write_file(
path='{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)),
data=user_data
)
out5= toxvenv_sh(ctx, remote,
[
'openssl', 'x509',
'-in', '{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)),
'--fingerprint', '--noout', '-sha1'
])
pre_ans= '{}'.format(out5[17:76])
ans5=""
for character in pre_ans:
if(character!=':'):
ans5+=character
str1 = 'curl'
str2 = '-k'
str3 = '-v'
str4 = '-X'
str5 = 'POST'
str6 = '-u'
str7 = '-d'
str8 = 'http://localhost:8080/auth/realms/'+realm_name+'/protocol/openid-connect/token/introspect'
out6= toxvenv_sh(ctx, remote,
[
str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.aud'
])
out7= toxvenv_sh(ctx, remote,
[
str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.sub'
])
out8= toxvenv_sh(ctx, remote,
[
str1, str2, str3, str4, str5, str6, ans0, str7, acc_token, str8, run.Raw('|'), 'jq', '-r', '.azp'
])
ans6=out6.rstrip()
ans7=out7.rstrip()
ans8=out8.rstrip()
os.environ['TOKEN']=ans4
os.environ['THUMBPRINT']=ans5
os.environ['AUD']=ans6
os.environ['SUB']=ans7
os.environ['AZP']=ans8
os.environ['USER_TOKEN']=user_token
os.environ['KC_REALM']=realm_name
try:
yield
finally:
log.info('Removing certificate.crt file...')
for (client,_) in config.items():
(remote,) = ctx.cluster.only(client).remotes.keys()
remote.run(
args=['rm', '-f',
'{tdir}/bin/certificate.crt'.format(tdir=get_keycloak_dir(ctx,config)),
],
)
remote.run(
args=['rm', '-f',
'{tdir}/confi.py'.format(tdir=teuthology.get_testdir(ctx)),
],
)
@contextlib.contextmanager
def task(ctx,config):
"""
To run keycloak the prerequisite is to run the tox task. Following is the way how to run
tox and then keycloak::
tasks:
- tox: [ client.0 ]
- keycloak:
client.0:
keycloak_version: 11.0.0
To pass extra arguments to nose (e.g. to run a certain test)::
tasks:
- tox: [ client.0 ]
- keycloak:
client.0:
keycloak_version: 11.0.0
- s3tests:
client.0:
extra_attrs: ['webidentity_test']
"""
assert config is None or isinstance(config, list) \
or isinstance(config, dict), \
"task keycloak only supports a list or dictionary for configuration"
if not hasattr(ctx, 'tox'):
raise ConfigError('keycloak must run after the tox task')
all_clients = ['client.{id}'.format(id=id_)
for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
if config is None:
config = all_clients
if isinstance(config, list):
config = dict.fromkeys(config)
log.debug('Keycloak config is %s', config)
with contextutil.nested(
lambda: install_packages(ctx=ctx, config=config),
lambda: build(ctx=ctx, config=config),
lambda: run_keycloak(ctx=ctx, config=config),
lambda: download_conf(ctx=ctx, config=config),
lambda: run_admin_cmds(ctx=ctx, config=config),
):
yield