mirror of https://github.com/ceph/ceph
469 lines
16 KiB
Python
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
|
|
|