Skip to content

Commit

Permalink
feat(jans-linux-setup): resource provisioning on both jans-auth and k…
Browse files Browse the repository at this point in the history
…eycloak (#7447)

* feat(jans-linux-setup): resource provisioning on both jans-auh and keycloak

Signed-off-by: Mustafa Baser <mbaser@mail.com>

* fix(jans-linux-setup): grant types for saml clients

Signed-off-by: Mustafa Baser <mbaser@mail.com>

---------

Signed-off-by: Mustafa Baser <mbaser@mail.com>
  • Loading branch information
devrimyatar committed Jan 22, 2024
1 parent f84e99a commit e8fa4cf
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 36 deletions.
89 changes: 59 additions & 30 deletions jans-linux-setup/jans_setup/setup_app/installers/jans_saml.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(self):
self.ldif_config_fn = os.path.join(self.output_folder, 'configuration.ldif')
self.config_json_fn = os.path.join(self.templates_folder, 'jans-saml-config.json')
self.idp_config_fn = os.path.join(self.templates_folder, 'keycloak.conf')
self.clients_ldif_fn = os.path.join(self.output_folder, 'clients.ldif')
self.clients_json_fn = os.path.join(self.templates_folder, 'clients.json')

Config.jans_idp_idp_metadata_root_dir = os.path.join(self.idp_config_root_dir, 'idp/metadata')
Config.jans_idp_idp_metadata_temp_dir = os.path.join(self.idp_config_root_dir, 'idp/temp_metadata')
Expand All @@ -85,7 +85,7 @@ def __init__(self):

def install(self):
"""installation steps"""
self.create_scim_client()
self.create_clients()
self.install_keycloack()


Expand All @@ -111,31 +111,38 @@ def create_folders(self):
self.chown(self.idp_root_dir, Config.jetty_user, Config.jetty_group, recursive=True)
self.run([paths.cmd_chmod, '0760', saml_dir])

def create_scim_client(self):
result = self.check_clients([('saml_scim_client_id', '2100.')])
if result.get('2100.') == -1:

scopes = ['inum=F0C4,ou=scopes,o=jans']
users_write_search_result = self.dbUtils.search('ou=scopes,o=jans', search_filter='(jansId=https://jans.io/scim/users.write)')
if users_write_search_result:
scopes.append(users_write_search_result['dn'])
users_read_search_result = self.dbUtils.search('ou=scopes,o=jans', search_filter='(jansId=https://jans.io/scim/users.read)')
if users_read_search_result:
scopes.append(users_read_search_result['dn'])

create_client_ldif(
ldif_fn=self.clients_ldif_fn,
client_id=Config.saml_scim_client_id,
encoded_pw=Config.saml_scim_client_encoded_pw,
scopes=scopes,
redirect_uri=['https://{}/admin-ui'.format(Config.hostname), 'http://localhost:4100'],
display_name="Jans SCIM Client for SAML",
grant_types=['authorization_code', 'client_credentials', 'password', 'refresh_token'],
authorization_methods=['client_secret_basic', 'client_secret_post']
)

self.dbUtils.import_ldif([self.clients_ldif_fn])

def create_clients(self):
clients_data = base.readJsonFile(self.clients_json_fn)
client_ldif_fns = []
for client_info in clients_data:
check_client = self.check_clients([(client_info['client_var'], client_info['client_prefix'])])
if check_client.get(client_info['client_prefix']) == -1:
scopes = client_info['scopes_dns']
for scope_id in client_info['scopes_ids']:
scope_info = self.dbUtils.search('ou=scopes,o=jans', search_filter=f'(jansId=scope_id)')
if scope_info:
scopes.append(scope_info['dn'])
client_id = getattr(Config, client_info['client_var'])
client_ldif_fn = os.path.join(self.output_folder, f'clients-{client_id}.ldif')
client_ldif_fns.append(client_ldif_fn)
encoded_pw_var = '_'.join(client_info["client_var"].split('_')[:-1])+'_encoded_pw'
if client_info['redirect_uri']:
for i, redirect_uri in enumerate(client_info['redirect_uri']):
client_info['redirect_uri'][i] = self.fomatWithDict(redirect_uri, Config.__dict__)
create_client_ldif(
ldif_fn=client_ldif_fn,
client_id=client_id,
description=client_info['description'],
display_name=client_info['display_name'],
encoded_pw=getattr(Config, encoded_pw_var),
scopes=scopes,
redirect_uri=client_info['redirect_uri'] ,
grant_types=client_info['grant_types'],
authorization_methods=client_info['authorization_methods'],
application_type=client_info['application_type'],
response_types=client_info['response_types']
)
self.dbUtils.import_ldif(client_ldif_fns)

def install_keycloack(self):
self.logIt("Installing KC", pbar=self.service_name)
Expand Down Expand Up @@ -172,10 +179,12 @@ def config_api_idp_plugin_config(self):
jans_api_openid_client_fn = 'jans.api-openid-client.json'
jans_api_realm_fn = 'jans.api-realm.json'
jans_api_user_fn = 'jans.api-user.json'
jans_browser_auth_flow_fn = 'jans.browser-auth-flow.json'
jans_execution_config_jans_fn = 'jans.execution-config-jans.json'

self.idp_config_fn = os.path.join(self.templates_folder, 'keycloak.conf')

for tmp_fn in (jans_api_openid_client_fn, jans_api_realm_fn, jans_api_user_fn):
for tmp_fn in (jans_api_openid_client_fn, jans_api_realm_fn, jans_api_user_fn, jans_browser_auth_flow_fn):
self.renderTemplateInOut(os.path.join(jans_api_tmp_dir, tmp_fn), jans_api_tmp_dir, jans_api_output_dir, pystring=True)

self.logIt("Starting KC for config api idp plugin configurations")
Expand Down Expand Up @@ -211,13 +220,33 @@ def config_api_idp_plugin_config(self):
# create realm
self.run([kcadm_cmd, 'create', 'realms', '-f', os.path.join(jans_api_output_dir, jans_api_realm_fn),'--config', kc_tmp_config], env=env)


# create client
self.run([kcadm_cmd, 'create', 'clients', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_api_openid_client_fn),'--config', kc_tmp_config], env=env)
self.run([kcadm_cmd, 'create', 'clients', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_api_openid_client_fn), '--config', kc_tmp_config], env=env)

# create user and change password
self.run([kcadm_cmd, 'create', 'users', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_api_user_fn),'--config', kc_tmp_config], env=env)
self.run([kcadm_cmd, 'set-password', '-r', Config.jans_idp_realm, '--username', Config.jans_idp_user_name, '--new-password', Config.jans_idp_user_password, '--config', kc_tmp_config], env=env)

# assign roles to jans-api-user
self.run([kcadm_cmd, 'add-roles', '-r', Config.jans_idp_realm, '--uusername', Config.jans_idp_user_name, '--cclientid', 'realm-management', '--rolename', 'manage-identity-providers', '--rolename', 'view-identity-providers', '--rolename', 'view-identity-providers', '--config', kc_tmp_config], env=env)

# Create authentication flow in the jans-api realm used for saml clients
_, result = self.run([kcadm_cmd, 'create', 'authentication/flows', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_browser_auth_flow_fn), '--config', kc_tmp_config], env=env, get_stderr=True)
Config.templateRenderingDict['jans_browser_auth_flow_id'] = result.strip().split()[-1].strip("'").strip('"')

jans_execution_auth_cookie_fn = 'jans.execution-auth-cookie.json'
jans_execution_auth_jans_fn = 'jans.execution-auth-jans.json'

for tmp_fn in (jans_execution_auth_cookie_fn, jans_execution_auth_jans_fn):
self.renderTemplateInOut(os.path.join(jans_api_tmp_dir, tmp_fn), jans_api_tmp_dir, jans_api_output_dir, pystring=True)

# Add execution steps to the flow created in the jansapi realm
self.run([kcadm_cmd, 'create', 'authentication/executions', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_execution_auth_cookie_fn), '--config', kc_tmp_config], env=env)
_, result = self.run([kcadm_cmd, 'create', 'authentication/executions', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_execution_auth_jans_fn), '--config', kc_tmp_config], env=env, get_stderr=True)
jans_execution_auth_jans_id = result.strip().split()[-1].strip("'").strip('"')
self.renderTemplateInOut(os.path.join(jans_api_tmp_dir, jans_execution_config_jans_fn), jans_api_tmp_dir, jans_api_output_dir, pystring=True)

# Configure the jans auth execution step in realm jans-api
self.run([kcadm_cmd, 'create', f'authentication/executions/{jans_execution_auth_jans_id}/config', '-r', Config.jans_idp_realm, '-f', os.path.join(jans_api_output_dir, jans_execution_config_jans_fn), '--config', kc_tmp_config], env=env)


31 changes: 25 additions & 6 deletions jans-linux-setup/jans_setup/setup_app/utils/ldif_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,20 @@ def schema2json(schema_file, out_dir=None):
with open(out_file, 'w') as w:
w.write(schema_str)

def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, display_name, trusted_client='false', grant_types=None, authorization_methods=None):
def create_client_ldif(
ldif_fn,
client_id,
encoded_pw,
scopes,
redirect_uri,
display_name,
trusted_client='false',
grant_types=None,
authorization_methods=None,
response_types=['code'],
application_type='web',
description=None
):
# create directory if not exists
dirname = os.path.dirname(ldif_fn)
if not os.path.exists(dirname):
Expand All @@ -170,8 +183,7 @@ def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, dis
if not authorization_methods:
authorization_methods = ['client_secret_basic']

ldif_clients_writer.unparse(
client_dn, {
client_dict = {
'objectClass': ['top', 'jansClnt'],
'del': ['false'],
'displayName': [display_name],
Expand All @@ -182,18 +194,25 @@ def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, dis
'jansAttrs': ['{"tlsClientAuthSubjectDn":"","runIntrospectionScriptBeforeJwtCreation":false,"keepClientAuthorizationAfterExpiration":false,"allowSpontaneousScopes":false,"spontaneousScopes":[],"spontaneousScopeScriptDns":[],"backchannelLogoutUri":[],"backchannelLogoutSessionRequired":false,"additionalAudience":[],"postAuthnScripts":[],"consentGatheringScripts":[],"introspectionScripts":[],"rptClaimsScripts":[]}'],
'jansClntSecret': [encoded_pw],
'jansDisabled': ['false'],
'jansGrantTyp': grant_types,
'jansIdTknSignedRespAlg': ['RS256'],
'jansInclClaimsInIdTkn': ['false'],
'jansLogoutSessRequired': ['false'],
'jansPersistClntAuthzs': ['true'],
'jansRespTyp': ['code'],
'jansRptAsJwt': ['false'],
'jansScope': scopes,
'jansSubjectTyp': ['pairwise'],
'jansTknEndpointAuthMethod': authorization_methods,
'jansTrustedClnt': [trusted_client],
'jansRedirectURI': redirect_uri
})
}

if description:
client_dict['description'] = [description]
if response_types:
client_dict['jansRespTyp'] = response_types
if grant_types:
client_dict['jansGrantTyp'] = grant_types

ldif_clients_writer.unparse(client_dn, client_dict)

clients_ldif_fd.close()
58 changes: 58 additions & 0 deletions jans-linux-setup/jans_setup/templates/jans-saml/clients.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[
{
"client_prefix": "2100.",
"client_var": "saml_scim_client_id",
"client_id": "saml_scim_client",
"display_name": "Jans SCIM Client for SAML",
"description": "Jans SCIM Client for SAML",
"scopes_dns": ["inum=F0C4,ou=scopes,o=jans"],
"scopes_ids": ["https://jans.io/scim/users.write", "https://jans.io/scim/users.read"],
"redirect_uri": ["https://%(hostname)s/admin-ui", "http://localhost:4100"],
"grant_types": ["authorization_code", "client_credentials", "password", "refresh_token"],
"authorization_methods": ["client_secret_basic", "client_secret_post"],
"response_types": null,
"application_type": "web"
},
{
"client_prefix": "2101.",
"client_var": "kc_saml_openid_client_id",
"client_id": "kc_saml_openid",
"display_name": "kc_saml_openid",
"description": "Keycloak OpenID client used for SAML authentication",
"scopes_dns": [],
"scopes_ids": ["email" , "user_name" , "openid"],
"redirect_uri": ["https://%(idp_config_hostname)s:%(idp_config_http_port)s/realms/master/kc-jans-authn-rest-bridge/auth-complete"],
"grant_types": ["authorization_code"],
"authorization_methods": ["client_secret_basic"],
"response_types": ["code", "token"],
"application_type": "native"
},
{
"client_prefix": "2102.",
"client_var": "kc_scheduler_api_client_id",
"client_id": "kc_scheduler_api",
"display_name": "kc_scheduler_api",
"description": "keycloak scheduler openid client used to obtain API keys to invoke the config-api",
"scopes_dns": [],
"scopes_ids": ["http://jans.io/idp/saml.write", "http://jans.io/idp/saml.readonly", "http://jans.io/oauth/config/saml.readonly", "http://jans.io/oauth/config/saml.write", "http://jans.io/oauth/config/attributes.readonly"],
"redirect_uri": ["https://%(idp_config_hostname)s:%(idp_config_http_port)s/realms/jans/dev/null"],
"grant_types": ["client_credentials"],
"authorization_methods": ["client_secret_basic"],
"response_types": ["token"],
"application_type": "native"
},
{
"client_prefix": "2103.",
"client_var": "kc_master_auth_client_id",
"client_id": "kc_master_auth",
"display_name": "kc_master_auth",
"description": "keycloak master realm client used to authenticate administrators",
"scopes_dns": [],
"scopes_ids": ["email" , "user_name" , "openid"],
"redirect_uri": ["https://%(idp_config_hostname)s:%(idp_config_http_port)s/realms/master/kc-jans-authn-rest-bridge/auth-complete"],
"grant_types": ["authorization_code"],
"authorization_methods": ["client_secret_basic"],
"response_types": ["code", "token"],
"application_type": "web"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"alias":"janssen login",
"description": "browser based authentication tailored for the janssen-auth server",
"providerId": "basic-flow",
"topLevel": true,
"builtIn" : false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"parentFlow": "${jans_browser_auth_flow_id}",
"authenticator": "auth-cookie",
"authenticatorConfig": null,
"requirement": "ALTERNATIVE",
"priority": 10
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"parentFlow": "${jans_browser_auth_flow_id}",
"authenticator": "kc-jans-authn",
"authenticatorConfig": null,
"requirement": "REQUIRED",
"priority": 20
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"alias": "jans-auth-openid-config",
"config": {
"jans.auth.server.url": "https://${hostname}",
"jans.auth.client.id": "${kc_saml_openid_client_id}",
"jans.auth.client.secret": "${kc_saml_openid_client_pw}",
"jans.auth.issuer": "https://${hostname}",
"jans.auth.extra_scopes": null
}
}

0 comments on commit e8fa4cf

Please sign in to comment.