From 8324485b75a152eab5084bb93c3226bb91091c60 Mon Sep 17 00:00:00 2001 From: syed salman <72004356+syedsalman3753@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:25:11 +0530 Subject: [PATCH] [MOSIP-30473] Updated keycloak-init & keycloak-artemis (#70) * [MOSIP-30473] Updated keycloak-init & keycloak-artemis [MOSIP-30473] Enabled captcha for keycloak login [DSD-4243] Added feature to set up SMTP configuration [MOSIP-31032] Fixed keycloak artemis build issue Signed-off-by: syed salman * [MOSIP-30473] Fixed permission issue Signed-off-by: syed salman * [MOSIP-30473] Fixed permission issue Signed-off-by: syed salman * [MOSIP-30473] Fixed permission issue Signed-off-by: syed salman * [MOSIP-30473] updated keycloak_init.py Signed-off-by: syed salman --------- Signed-off-by: syed salman --- keycloak-artemis/Dockerfile | 46 +--- keycloak-init/Dockerfile | 10 +- keycloak-init/input.yaml | 471 ++++++++------------------------- keycloak-init/keycloak_init.py | 283 +++++++++++++++++--- 4 files changed, 377 insertions(+), 433 deletions(-) diff --git a/keycloak-artemis/Dockerfile b/keycloak-artemis/Dockerfile index 959e12e6..d547c20a 100644 --- a/keycloak-artemis/Dockerfile +++ b/keycloak-artemis/Dockerfile @@ -1,4 +1,6 @@ -FROM docker.io/bitnami/minideb:buster +FROM docker.io/bitnami/keycloak:16.1.1 + +USER root ARG SOURCE ARG COMMIT_HASH @@ -9,26 +11,9 @@ LABEL commit_hash=${COMMIT_HASH} LABEL commit_id=${COMMIT_ID} LABEL build_time=${BUILD_TIME} -LABEL maintainer "Bitnami " - -ENV HOME="/" \ - OS_ARCH="amd64" \ - OS_FLAVOUR="debian-10" \ - OS_NAME="linux" - -ARG JAVA_EXTRA_SECURITY_DIR="/bitnami/java/extra-security" +LABEL maintainer="Bitnami " COPY prebuildfs / -RUN chmod +x /usr/sbin/install_packages -# Install required system packages and dependencies -RUN install_packages acl ca-certificates curl gzip libaio1 libc6 procps rsync tar zlib1g -RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "wait-for-port" "1.0.1-10" --checksum 35c818ba3f4b5aae905959bc7d3a5e81fc63786e3c662b604612c0aa7fcda8fd -RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "java" "11.0.14-7" --checksum 900545c4f346a0ece8abf2caf64fd9d4ab7514967d4614d716bf7362b24f828b -RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "keycloak" "16.1.1-8" --checksum c432a2b3551a40e48b77f63257def52e9556c2b6ffa989e7eb6847ad87fdd9db -RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "gosu" "1.14.0-7" --checksum d6280b6f647a62bf6edc74dc8e526bfff63ddd8067dcb8540843f47203d9ccf1 -RUN apt-get update && apt-get upgrade -y && \ - rm -r /var/lib/apt/lists /var/cache/apt/archives -RUN chmod g+rwX /opt/bitnami COPY ./theme/base /opt/bitnami/keycloak/themes/base @@ -37,19 +22,16 @@ COPY ./theme/mosip /opt/bitnami/keycloak/themes/mosip COPY ./standalone/deployments/* /opt/bitnami/keycloak/standalone/deployments COPY rootfs / -RUN chmod +x /opt/bitnami/scripts/keycloak/postunpack.sh -RUN chmod +x /opt/bitnami/scripts/java/postunpack.sh - -RUN /opt/bitnami/scripts/java/postunpack.sh -RUN /opt/bitnami/scripts/keycloak/postunpack.sh -ENV APP_VERSION="16.1.1" \ - BITNAMI_APP_NAME="keycloak" \ - JAVA_HOME="/opt/bitnami/java" \ - PATH="/opt/bitnami/common/bin:/opt/bitnami/java/bin:/opt/bitnami/keycloak/bin:$PATH" - -RUN chmod +x /opt/bitnami/scripts/keycloak/entrypoint.sh -RUN chmod +x /opt/bitnami/scripts/keycloak/setup.sh -RUN chmod +x /opt/bitnami/scripts/keycloak/run.sh + +RUN chmod +x /usr/sbin/install_packages && chmod g+rwX /opt/bitnami && \ + chmod +x /opt/bitnami/scripts/keycloak/entrypoint.sh /opt/bitnami/scripts/keycloak/setup.sh /opt/bitnami/scripts/keycloak/run.sh && \ + chown -R 1001:1001 /opt/* + +# Install required system packages and dependencies +RUN . /usr/sbin/install_packages acl ca-certificates curl gzip libaio1 libc6 procps rsync tar zlib1g + USER 1001 + ENTRYPOINT [ "/opt/bitnami/scripts/keycloak/entrypoint.sh" ] + CMD [ "/opt/bitnami/scripts/keycloak/run.sh" ] diff --git a/keycloak-init/Dockerfile b/keycloak-init/Dockerfile index bf0040ce..7020c637 100644 --- a/keycloak-init/Dockerfile +++ b/keycloak-init/Dockerfile @@ -38,11 +38,11 @@ USER ${container_user_uid}:${container_user_gid} WORKDIR /home/${container_user} COPY keycloak_init.py . -ENV KEYCLOAK_SERVER_URL= -ENV KEYCLOAK_ADMIN_USER=user -ENV KEYCLOAK_ADMIN_PASSWORD= +ENV KEYCLOAK_SERVER_URL='' +ENV KEYCLOAK_ADMIN_USER='' +ENV KEYCLOAK_ADMIN_PASSWORD='' ENV INPUT_DIR=/opt/mosip/input ENV INPUT_FILE=input.yaml -ENV FRONTEND_URL= +ENV FRONTEND_URL='' -CMD python3 keycloak_init.py $KEYCLOAK_SERVER_URL $KEYCLOAK_ADMIN_USER $KEYCLOAK_ADMIN_PASSWORD $INPUT_DIR/$INPUT_FILE --frontend_url $FRONTEND_URL +ENTRYPOINT ["/bin/bash", "-c", "python3 keycloak_init.py $KEYCLOAK_SERVER_URL $KEYCLOAK_ADMIN_USER $KEYCLOAK_ADMIN_PASSWORD $INPUT_DIR/$INPUT_FILE"] diff --git a/keycloak-init/input.yaml b/keycloak-init/input.yaml index 32e4d674..39d750ce 100644 --- a/keycloak-init/input.yaml +++ b/keycloak-init/input.yaml @@ -1,364 +1,107 @@ -mosip: # realm - roles: - - Default - - ABIS_PARTNER - - SDK_PARTNER - - AUTH - - AUTH_PARTNER - - BIOMETRIC_READ - - CENTRAL_ADMIN - - CENTRAL_APPROVER - - CREATE_SHARE - - CREDENTIAL_ISSUANCE - - CREDENTIAL_PARTNER - - CREDENTIAL_REQUEST - - DATA_READ - - DEVICE_PROVIDER - - DOCUMENT_READ - - FTM_PROVIDER - - GLOBAL_ADMIN - - ID_AUTHENTICATION - - ID_REPOSITORY - - INDIVIDUAL - - KEY_MAKER - - MASTERDATA_ADMIN - - METADATA_READ - - MISP - - MISP_PARTNER - - offline_access - - ONLINE_VERIFICATION_PARTNER - - PARTNER - - PARTNER_ADMIN - - PARTNERMANAGER - - PMS_ADMIN - - PMS_USER - - POLICYMANAGER - - PREREG - - PRE_REGISTRATION - - PRE_REGISTRATION_ADMIN - - PRINT_PARTNER - - PUBLISH_ACTIVATE_ID_ALL_INDIVIDUAL - - PUBLISH_ANONYMOUS_PROFILE_GENERAL - - PUBLISH_APIKEY_APPROVED_GENERAL - - PUBLISH_APIKEY_UPDATED_GENERAL - - PUBLISH_AUTHENTICATION_TRANSACTION_STATUS_GENERAL - - PUBLISH_AUTH_TYPE_STATUS_UPDATE_ACK_GENERAL - - PUBLISH_AUTH_TYPE_STATUS_UPDATE_ALL_INDIVIDUAL - - PUBLISH_CA_CERTIFICATE_UPLOADED_GENERAL - - PUBLISH_CREDENTIAL_ISSUED_ALL_INDIVIDUAL - - PUBLISH_CREDENTIAL_STATUS_UPDATE_GENERAL - - PUBLISH_DEACTIVATE_ID_ALL_INDIVIDUAL - - PUBLISH_IDA_FRAUD_ANALYTICS_GENERAL - - PUBLISH_MASTERDATA_IDAUTHENTICATION_TEMPLATES_GENERAL - - PUBLISH_MASTERDATA_TITLES_GENERAL - - PUBLISH_MISP_LICENSE_GENERATED_GENERAL - - PUBLISH_MISP_LICENSE_UPDATED_GENERAL - - PUBLISH_MOSIP_HOTLIST_GENERAL - - PUBLISH_PARTNER_UPDATED_GENERAL - - PUBLISH_POLICY_UPDATED_GENERAL - - PUBLISH_REGISTRATION_PROCESSOR_WORKFLOW_COMPLETED_EVENT_GENERAL - - PUBLISH_REGISTRATION_PROCESSOR_WORKFLOW_PAUSED_FOR_ADDITIONAL_INFO_EVENT_GENERAL - - PUBLISH_REMOVE_ID_ALL_INDIVIDUAL - - PUBLISH_VID_CRED_STATUS_UPDATE_GENERAL - - REGISTRATION_ADMIN - - REGISTRATION_OFFICER - - REGISTRATION_OPERATOR - - REGISTRATION_PROCESSOR - - REGISTRATION_SUPERVISOR - - RESIDENT - - SUBSCRIBE_ACTIVATE_ID_INDIVIDUAL - - SUBSCRIBE_APIKEY_APPROVED_GENERAL - - SUBSCRIBE_APIKEY_UPDATED_GENERAL - - SUBSCRIBE_AUTH_TYPE_STATUS_UPDATE_ACK_GENERAL - - SUBSCRIBE_AUTH_TYPE_STATUS_UPDATE_INDIVIDUAL - - SUBSCRIBE_CA_CERTIFICATE_UPLOADED_GENERAL - - SUBSCRIBE_CREDENTIAL_ISSUED_INDIVIDUAL - - SUBSCRIBE_CREDENTIAL_STATUS_UPDATE_GENERAL - - SUBSCRIBE_DEACTIVATE_ID_INDIVIDUAL - - SUBSCRIBE_MASTERDATA_IDAUTHENTICATION_TEMPLATES_GENERAL - - SUBSCRIBE_MASTERDATA_TITLES_GENERAL - - SUBSCRIBE_MISP_LICENSE_GENERATED_GENERAL - - SUBSCRIBE_MISP_LICENSE_UPDATED_GENERAL - - SUBSCRIBE_MOSIP_HOTLIST_GENERAL - - SUBSCRIBE_PARTNER_UPDATED_GENERAL - - SUBSCRIBE_POLICY_UPDATED_GENERAL - - SUBSCRIBE_REMOVE_ID_INDIVIDUAL - - SUBSCRIBE_VID_CRED_STATUS_UPDATE_GENERAL - - uma_authorization - - ZONAL_ADMIN - - ZONAL_APPROVER - - HOTLIST_ADMIN - - SUBSCRIBE_REGISTRATION_PROCESSOR_WORKFLOW_COMPLETED_EVENT_GENERAL - - SUBSCRIBE_REGISTRATION_PROCESSOR_WORKFLOW_PAUSED_FOR_ADDITIONAL_INFO_EVENT_GENERAL - clients: - - name: mosip-abis-client - mappers: [] - saroles: [] - - - name: mosip-admin-client - mappers: [] - saroles: - - MASTERDATA_ADMIN - - GLOBAL_ADMIN - - PUBLISH_MASTERDATA_IDAUTHENTICATION_TEMPLATES_GENERAL - - offline_access - - PUBLISH_MOSIP_HOTLIST_GENERAL - - uma_authorization - - PUBLISH_MASTERDATA_TITLES_GENERAL - - - name: mosip-admin-services-client - mappers: [] - saroles: [] - - - name: mosip-auth-client - mappers: [] - saroles: - - AUTH - - - name: mosip-crereq-client - mappers: [] - saroles: - - CREDENTIAL_ISSUANCE - - CREDENTIAL_REQUEST - - SUBSCRIBE_CREDENTIAL_STATUS_UPDATE_GENERAL - - offline_access - - uma_authorization - - - name: mosip-creser-client - mappers: [] - saroles: - - CREDENTIAL_ISSUANCE - - REGISTRATION_PROCESSOR - - POLICYMANAGER - - CREATE_SHARE - - offline_access - - PUBLISH_CREDENTIAL_ISSUED_ALL_INDIVIDUAL - - uma_authorization - - name: mosip-creser-idpass-client - mappers: [] - saroles: - - REGISTRATION_PROCESSOR - - DATA_READ - - DOCUMENT_READ - - BIOMETRIC_READ - - METADATA_READ - - CREATE_SHARE - - CREDENTIAL_REQUEST - - - name: mosip-datsha-client - mappers: [] - saroles: - - CREATE_SHARE - - REGISTRATION_PROCESSOR - - POLICYMANAGER - - - name: mosip-ida-client - mappers: [] - saroles: - - CREDENTIAL_REQUEST - - GLOBAL_ADMIN - - ID_AUTHENTICATION - - PARTNERMANAGER # Added only for cert upload using postman during install. Not required otherwise. - - - name: mosip-misp-client - mappers: [] - saroles: [] - - - name: mosip-partner-client - mappers: - - mapper_name: phoneNumber - mapper_user_attribute: phoneNumber - token_claim_name: phoneNumber - - mapper_name: organizationName - mapper_user_attribute: organizationName - token_claim_name: organizationName - - mapper_name: partnerType - mapper_user_attribute: partnerType - token_claim_name: partnerType - - mapper_name: addressTest - mapper_user_attribute: address - token_claim_name: addressTest - saroles: - - REGISTRATION_PROCESSOR - - CREATE_SHARE - - PMS_USER - - PMS_ADMIN - - PARTNER_ADMIN - - SUBSCRIBE_CA_CERTIFICATE_UPLOADED_GENERAL - - PUBLISH_MISP_LICENSE_UPDATED_GENERAL - - PUBLISH_PARTNER_UPDATED_GENERAL - - PUBLISH_MISP_LICENSE_GENERATED_GENERAL - - PUBLISH_APIKEY_APPROVED_GENERAL - - PUBLISH_APIKEY_UPDATED_GENERAL - - PUBLISH_CA_CERTIFICATE_UPLOADED_GENERAL - - PUBLISH_POLICY_UPDATED_GENERAL - - - name: mosip-partnermanager-client - mappers: [] - saroles: - - PARTNERMANAGER - - KEY_MAKER - - - name: mosip-pms-client - mappers: [] - saroles: - - PARTNER_ADMIN - - - name: mosip-policymanager-client - mappers: [] - saroles: [] - - - name: mosip-reg-client - mappers: [] - saroles: - - GLOBAL_ADMIN - - REGISTRATION_ADMIN - - REGISTRATION_OFFICER - - REGISTRATION_OPERATOR - - REGISTRATION_SUPERVISOR - - - name: mosip-regproc-client - mappers: [] - saroles: - - REGISTRATION_PROCESSOR - - DATA_READ - - DOCUMENT_READ - - BIOMETRIC_READ - - METADATA_READ - - CREATE_SHARE - - CREDENTIAL_REQUEST - - - name: mpartner-default-mobile - mappers: [] - saroles: - - CREDENTIAL_PARTNER - - SUBSCRIBE_REGISTRATION_PROCESSOR_WORKFLOW_COMPLETED_EVENT_GENERAL - - SUBSCRIBE_REGISTRATION_PROCESSOR_WORKFLOW_PAUSED_FOR_ADDITIONAL_INFO_EVENT_GENERAL - - PUBLISH_REGISTRATION_PROCESSOR_WORKFLOW_COMPLETED_EVENT_GENERAL - - PUBLISH_CREDENTIAL_STATUS_UPDATE_GENERAL - - PUBLISH_REGISTRATION_PROCESSOR_WORKFLOW_PAUSED_FOR_ADDITIONAL_INFO_EVENT_GENERAL - - SUBSCRIBE_CREDENTIAL_ISSUED_INDIVIDUAL - - - name: mosip-resident-client - mappers: [] - saroles: - - RESIDENT - - PARTNER_ADMIN - - CREDENTIAL_REQUEST - - offline_access - - uma_authorization - - - name: mosip-prereg-client - mappers: [] - saroles: - - PREREG - - REGISTRATION_PROCESSOR - - PRE_REGISTRATION_ADMIN - - - name: mosip-creser-idpass-client - mappers: [] - saroles: - - REGISTRATION_PROCESSOR - - DATA_READ - - DOCUMENT_READ - - BIOMETRIC_READ - - METADATA_READ - - CREATE_SHARE - - CREDENTIAL_REQUEST - - - name: mosip-syncdata-client - mappers: [] - saroles: - - REGISTRATION_ADMIN - - GLOBAL_ADMIN - - SUBSCRIBE_CA_CERTIFICATE_UPLOADED_GENERAL - - REGISTRATION_SUPERVISOR - - REGISTRATION_OFFICER - - - name: mpartner-default-auth - mappers: - - mapper_name: langCode - mapper_user_attribute: langCode - token_claim_name: langCode - saroles: - - SUBSCRIBE_AUTH_TYPE_STATUS_UPDATE_INDIVIDUAL - - SUBSCRIBE_POLICY_UPDATED_GENERAL - - SUBSCRIBE_MISP_LICENSE_GENERATED_GENERAL - - CREDENTIAL_REQUEST - - SUBSCRIBE_MOSIP_HOTLIST_GENERAL - - PUBLISH_ANONYMOUS_PROFILE_GENERAL - - SUBSCRIBE_ACTIVATE_ID_INDIVIDUAL - - SUBSCRIBE_REMOVE_ID_INDIVIDUAL - - SUBSCRIBE_MASTERDATA_TITLES_GENERAL - - SUBSCRIBE_CREDENTIAL_ISSUED_INDIVIDUAL - - SUBSCRIBE_MISP_LICENSE_UPDATED_GENERAL - - ID_AUTHENTICATION - - PUBLISH_CREDENTIAL_STATUS_UPDATE_GENERAL - - SUBSCRIBE_AUTH_TYPE_STATUS_UPDATE_ACK_GENERAL - - SUBSCRIBE_PARTNER_UPDATED_GENERAL - - offline_access - - SUBSCRIBE_APIKEY_APPROVED_GENERAL - - PUBLISH_AUTH_TYPE_STATUS_UPDATE_ACK_GENERAL - - SUBSCRIBE_MASTERDATA_IDAUTHENTICATION_TEMPLATES_GENERAL - - uma_authorization - - SUBSCRIBE_APIKEY_UPDATED_GENERAL - - SUBSCRIBE_DEACTIVATE_ID_INDIVIDUAL - - SUBSCRIBE_CA_CERTIFICATE_UPLOADED_GENERAL - - PUBLISH_AUTHENTICATION_TRANSACTION_STATUS_GENERAL - - PUBLISH_IDA_FRAUD_ANALYTICS_GENERAL - - - name: mosip-idrepo-client - mappers: [] - saroles: - - PUBLISH_DEACTIVATE_ID_ALL_INDIVIDUAL - - SUBSCRIBE_VID_CRED_STATUS_UPDATE_GENERAL - - ID_REPOSITORY - - PUBLISH_ACTIVATE_ID_ALL_INDIVIDUAL - - offline_access - - PUBLISH_REMOVE_ID_ALL_INDIVIDUAL - - PUBLISH_AUTHENTICATION_TRANSACTION_STATUS_GENERAL - - uma_authorization - - PUBLISH_VID_CRED_STATUS_UPDATE_GENERAL - - PUBLISH_AUTH_TYPE_STATUS_UPDATE_ALL_INDIVIDUAL - - - name: mpartner-default-print - mappers: [] - saroles: - - SUBSCRIBE_CREDENTIAL_ISSUED_INDIVIDUAL - - PUBLISH_CREDENTIAL_STATUS_UPDATE_GENERAL - - CREATE_SHARE - - PRINT_PARTNER - - - name: mosip-hotlist-client - saroles: - - HOTLIST_ADMIN - - uma_authorization - - offline_access - - PUBLISH_MOSIP_HOTLIST_GENERAL - - # Used only for initial deployment purposes. Maybe deleted from installation later. - - name: mosip-deployment-client - saroles: - - ID_AUTHENTICATION - - GLOBAL_ADMIN # TODO: do we need this? - - PARTNER_ADMIN - - uma_authorization - - offline_access - - - name: mosip-testrig-client - saroles: - - ID_AUTHENTICATION - - GLOBAL_ADMIN # TODO: do we need this? - - PARTNER_ADMIN - - REGISTRATION_PROCESSOR - - CREATE_SHARE - - PMS_ADMIN - - PMS_USER - - uma_authorization - - offline_access - sa_client_roles: - - realm-management: ## realm-management client id - - view-users # realm-management client roles - - view-clients - - view-realm - - manage-users +mosip: + realm_config: + "realm": 'mosip' + "enabled": True + "accessCodeLifespan": 7200 + "accessCodeLifespanLogin": 1800 + "accessCodeLifespanUserAction": 300 + "accessTokenLifespan": 86400 + "accessTokenLifespanForImplicitFlow": 900 + "actionTokenGeneratedByAdminLifespan": 43200 + "actionTokenGeneratedByUserLifespan": 300 + "passwordPolicy": "length(8)" + "resetPasswordAllowed": True + "bruteForceProtected": True + "permanentLockout": False + "maxFailureWaitSeconds": 900 + "minimumQuickLoginWaitSeconds": 60 + "waitIncrementSeconds": 300 + "quickLoginCheckMilliSeconds": 1000 + "maxDeltaTimeSeconds": 600 + "failureFactor": 5 + "attributes": + "frontendUrl": '' + "loginTheme": "mosip" + "accountTheme": "mosip" + "adminTheme": "mosip" + "emailTheme": "mosip" + "browserSecurityHeaders": + "contentSecurityPolicy": "frame-src 'self' https://www.google.com; frame-ancestors 'self'; object-src 'none';" + "smtpServer": + "password": "" + "starttls": "false" + "auth": "true" + "port": "465" + "host": "smtp.gmail.com" + "from": "mosipqa@gmail.com" + "ssl": "true" + "user": "mosipqa@gmail.com" + roles: + - CTK_ADMIN + clients: + - name: mosip-toolkit-client + saroles: [] + authentication_flow_overrides: + browser: 'Browser With Recaptcha' + - name: mosip-toolkit-android-client + public_client: True + redirect_urls: ["android://mosip-compliance-toolkit-ui","http://localhost"] + web_origins: ["android://mosip-compliance-toolkit-ui","http://localhost"] + direct_grant_flow_alias: 'direct grant' + browser_flow_alias: 'Browser With Recaptcha' + authentication_flow_overrides: + browser: 'Browser With Recaptcha' + authentication: + auth_flows: + - alias: "Browser With Recaptcha" + builtIn: false + description: "" + providerId: "basic-flow" + topLevel: true + authentication_executions: + - provider: "auth-cookie" + displayName: "Cookie" + update_execution: + requirement: "ALTERNATIVE" + - provider: "auth-spnego" + displayName: "Kerberos" + update_execution: + requirement: "DISABLED" + - provider: "identity-provider-redirector" + displayName: "Identity Provider Redirector" + update_execution: + requirement: "ALTERNATIVE" + authentication_flows: + - alias: "browser with recaptcha forms" + description: "" + provider: "registration-page-form" + type: "basic-flow" + displayName: "browser with recaptcha forms" + update_flow_requirement: + requirement: "ALTERNATIVE" + authentication_executions: + - provider: "recaptcha-u-p-form" + displayName: "Recaptcha Username Password Form" + update_config: + alias: "ctk-captcha" + config: + secret: "" + site.key: "" + useRecaptchaNet: "" + authentication_flows: + - alias: "Browser With Recaptcha Browser - Conditional OTP" + description: "" + provider: "registration-page-form" + type: "basic-flow" + displayName: 'Browser With Recaptcha Browser - Conditional OTP' + update_execution: + requirement: "CONDITIONAL" + authentication_executions: + - provider: "conditional-user-configured" + displayName: "Condition - user configured" + update_execution: + requirement: "REQUIRED" + - provider: "auth-otp-form" + displayName: "OTP Form" + update_execution: + requirement: "REQUIRED" diff --git a/keycloak-init/keycloak_init.py b/keycloak-init/keycloak_init.py index 27c005a1..b8746e74 100755 --- a/keycloak-init/keycloak_init.py +++ b/keycloak-init/keycloak_init.py @@ -3,7 +3,6 @@ import os import sys -import ast import argparse import secrets import json @@ -11,7 +10,6 @@ import traceback from keycloak import KeycloakAdmin from keycloak.exceptions import raise_error_from_response, KeycloakError, KeycloakGetError -from keycloak.connection import ConnectionManager from keycloak.urls_patterns import URL_ADMIN_USER_REALM_ROLES class KeycloakSession: @@ -21,32 +19,8 @@ def __init__(self, realm, server_url, user, pwd, ssl_verify): password=pwd, realm_name=realm, verify=ssl_verify) - def create_realm(self, realm, frontend_url=''): - payload = { - "realm" : realm, - "enabled": True, - "accessCodeLifespan": 7200, - "accessCodeLifespanLogin": 1800, - "accessCodeLifespanUserAction": 300, - "accessTokenLifespan": 86400, - "accessTokenLifespanForImplicitFlow": 900, - "actionTokenGeneratedByAdminLifespan": 43200, - "actionTokenGeneratedByUserLifespan": 300, - "passwordPolicy":"length(8)", - "bruteForceProtected":True, - "permanentLockout":False, - "maxFailureWaitSeconds":900, - "minimumQuickLoginWaitSeconds":60, - "waitIncrementSeconds":300, - "quickLoginCheckMilliSeconds":1000, - "maxDeltaTimeSeconds":600, - "failureFactor":5, - "attributes": {"frontendUrl": frontend_url}, - "loginTheme":"mosip", - "accountTheme":"mosip", - "adminTheme":"mosip", - "emailTheme":"mosip" - } + def create_realm(self, realm, realm_config): + payload = realm_config try: self.keycloak_admin.create_realm(payload, skip_exists=False) except KeycloakError as e: @@ -56,6 +30,221 @@ def create_realm(self, realm, frontend_url=''): except: raise + def create_flow_to_auth_flow(self, realm, flow_alias, add_flow_payload, space): + self.keycloak_admin.realm_name = realm + payload = add_flow_payload + + URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions/flow' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + try: + response = self.keycloak_admin.connection.raw_post( + URL.format(**params_path), + data=json.dumps(payload) + ) + raise_error_from_response(response, KeycloakGetError) + + except KeycloakError as e: + if e.response_code == 409: + print(space,'Flow "%s" already exists for flow "%s"; SKIPPING;' % (payload['alias'], flow_alias)) + else: + raise + + def update_config_to_execution(self, realm, flow_alias,execution_displayName, update_config_payload, space): + self.keycloak_admin.realm_name = realm + payload = update_config_payload + try: + site_key=os.environ.get(payload['alias']+"-site-key") if os.environ.get(payload['alias']+"-site-key") else payload['config']['secret'] + secret_key=os.environ.get(payload['alias']+"-secret-key") if os.environ.get(payload['alias']+"-secret-key") else payload['config']['site.key'] + use_recaptcha_net=os.environ.get(payload['alias']+"-use-recaptcha-net") if os.environ.get(payload['alias']+"-use-recaptcha-net") else payload['config']['useRecaptchaNet'] + if site_key is None or site_key == '': + print(space,'\033[91m'+"Environmental variable ",payload['alias']+"-site-key"," Not Found; EXITING") + exit(1) + if secret_key is None or secret_key == '': + print(space,'\033[91m'+"Environmental variable ",payload['alias']+"-secret-key"," Not Found; EXITING") + exit(1) + if use_recaptcha_net is None or use_recaptcha_net == '': + print(space,"Environmental variable ",payload['alias']+"-use-recaptcha-net"," Not Found; Setting empty value :") + use_recaptcha_net='' + payload['config']['secret']=secret_key + payload['config']['site.key']=site_key + payload['config']['useRecaptchaNet']=use_recaptcha_net + + # print(space, "Update config Payload : ", payload) + print(space, "Update config Payload : ") + GET_URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + GET_RESPONSE = self.keycloak_admin.connection.raw_get( + GET_URL.format(**params_path) + ) + AUTH_EXECUTION_LIST=GET_RESPONSE.json() + for auth_execution in AUTH_EXECUTION_LIST: + if payload is not None and auth_execution['displayName'] == execution_displayName: + URL = 'admin/realms/mosip/authentication/executions/{execution_alias_id}/config' + params_path = {"realm-name": self.keycloak_admin.realm_name, "execution_alias_id": auth_execution['id']} + response = self.keycloak_admin.connection.raw_post( + URL.format(**params_path), + data=json.dumps(payload) + ) + raise_error_from_response(response, KeycloakGetError) + return + except KeycloakError as e: + if e.response_code == 204 or e.response_code == 201: + print(space,'Execution for flow "%s" updated;' % execution_displayName) + else: + raise + + def update_execution_to_auth_flow(self, realm, flow_alias, execution, update_execution_payload=None, space=None): + self.keycloak_admin.realm_name = realm + payload = update_execution_payload + try: + GET_URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + GET_RESPONSE = self.keycloak_admin.connection.raw_get( + GET_URL.format(**params_path) + ) + AUTH_EXECUTION_LIST=GET_RESPONSE.json() + for auth_execution in AUTH_EXECUTION_LIST: + if payload is not None and auth_execution['displayName'] == execution['displayName']: + # print(space,'Execution for flow "%s" already exists; SKIPPING;' % auth_execution['displayName']) + URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + payload=({**auth_execution, **payload}) + response = self.keycloak_admin.connection.raw_put( + URL.format(**params_path), + data=json.dumps(payload) + ) + raise_error_from_response(response, KeycloakGetError) + return + except KeycloakError as e: + if e.response_code == 202: + print(space,'Execution for flow "%s" updated;' % execution['displayName']) + else: + raise + + def create_execution_to_auth_flow(self, realm, flow_alias, add_execution_payload, space): + self.keycloak_admin.realm_name = realm + payload = add_execution_payload + try: + GET_URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + GET_RESPONSE = self.keycloak_admin.connection.raw_get( + GET_URL.format(**params_path) + ) + AUTH_EXECUTION_LIST=GET_RESPONSE.json() + for auth_execution in AUTH_EXECUTION_LIST: + if auth_execution['displayName'] == payload['displayName']: + print(space,'Execution for flow "%s" already exists; SKIPPING;' % add_execution_payload['displayName']) + raise_error_from_response(GET_RESPONSE, KeycloakError,None, True) + return + + URL = 'admin/realms/{realm-name}/authentication/flows/{flow_alias}/executions/execution' + params_path = {"realm-name": self.keycloak_admin.realm_name, "flow_alias": flow_alias} + + response = self.keycloak_admin.connection.raw_post( + URL.format(**params_path), + data=json.dumps(payload) + ) + raise_error_from_response(response, KeycloakGetError) + + except KeycloakError as e: + if e.response_code == 409: + print(space,'Execution for flow "%s" already exists; SKIPPING;' % add_execution_payload['displayName']) + else: + raise + + + def create_authentication_flow(self, realm, auth_flow_payload): + self.keycloak_admin.realm_name = realm + executions=[] + if 'authentication_executions' in auth_flow_payload: + executions = auth_flow_payload.pop('authentication_executions') + + flows=[] + if 'authentication_flows' in auth_flow_payload: + flows = auth_flow_payload.pop('authentication_flows') + + payload = auth_flow_payload + URL = 'admin/realms/{realm-name}/authentication/flows' + params_path = {"realm-name": self.keycloak_admin.realm_name} + try: + response = self.keycloak_admin.connection.raw_post(URL.format(**params_path), data=json.dumps(payload)) + raise_error_from_response(response, KeycloakError) + except KeycloakError as e: + if e.response_code == 409: + print('\t\t\tFlow "%s" already exists; SKIPPING;' % payload['alias']) + else: + raise + for execution in executions: + space="\t\t\t" + print(space,'Adding execution : "%s" for "%s"' % (execution['displayName'], payload['alias'])) + update_config=None + if 'update_config' in execution: + update_config=execution.pop('update_config') + update_execution={} + if 'update_execution' in execution: + update_execution=execution.pop('update_execution') + self.create_execution_to_auth_flow(realm, payload['alias'], execution, space+"\t") + self.update_execution_to_auth_flow(realm, payload['alias'], execution, update_execution, space+"\t") + if update_config is not None: + self.update_config_to_execution(realm, payload['alias'], execution['displayName'], update_config, space+"\t") + + for flow in flows: + space="\t\t\t" + print(space,'Adding flow : "%s" for "%s" ' % (flow['alias'], payload['alias'])) + flow_authentication_executions={} + if 'authentication_executions' in flow: + flow_authentication_executions=flow.pop('authentication_executions') + flow_authentication_flows={} + if 'authentication_flows' in flow: + flow_authentication_flows=flow.pop('authentication_flows') + update_flow_requirement={} + if 'update_flow_requirement' in flow: + update_flow_requirement=flow.pop('update_flow_requirement') + + ## create the flow + self.create_flow_to_auth_flow(realm, payload['alias'], flow, space) + + ## update the requirement in flow + print(space,'Updating requirement for flow : "%s"' % flow['displayName']) + self.update_execution_to_auth_flow(realm, payload['alias'], flow, update_flow_requirement, space+"\t") + + for flow_execution in flow_authentication_executions: + exec_space=space+'\t' + print(exec_space,'Adding execution : "%s" to flow "%s" ' % (flow_execution['displayName'], flow['alias'])) + update_config=None + if 'update_config' in flow_execution: + update_config=flow_execution.pop('update_config') + self.create_execution_to_auth_flow(realm, flow['alias'], flow_execution, exec_space) + if update_config is not None: + print(exec_space,'Updating execution config, "Google Captcha Site key & Secret Key" to execution : "%s" of flow "%s" ' % (flow_execution['displayName'], flow['alias'])) + self.update_config_to_execution(realm, payload['alias'], flow_execution['displayName'], update_config, exec_space) + + for add_flow in flow_authentication_flows: + flow_space=space+'\t' + print(flow_space,'Adding flow : "%s" to flow "%s" ' % (add_flow['alias'], flow['alias'])) + update_execution={} + if 'update_execution' in add_flow: + update_execution=add_flow.pop('update_execution') + auth_flow_execs={} + if 'authentication_executions' in add_flow: + auth_flow_execs=add_flow.pop('authentication_executions') + + self.create_flow_to_auth_flow(realm, flow['alias'], add_flow, flow_space+"\t") + + print(flow_space,'Updating requirement for flow : "%s"' % flow['displayName'] ) + self.update_execution_to_auth_flow(realm, flow['alias'], add_flow, update_execution, flow_space) + + + for auth_flow_exec in auth_flow_execs: + update_execution={} + if 'update_execution' in auth_flow_exec: + update_execution=auth_flow_exec.pop('update_execution') + print(flow_space,'Adding execution : "%s" to flow "%s" ' % (auth_flow_exec['displayName'], flow['alias'])) + self.create_execution_to_auth_flow(realm, add_flow['alias'], auth_flow_exec, flow_space+"\t") + self.update_execution_to_auth_flow(realm, add_flow['alias'], auth_flow_exec, update_execution, flow_space+'\t') + + + def delete_realm(self, realm, skip_exists=False): self.keycloak_admin.realm_name = realm # work around because otherwise client was getting created in master url = 'admin/realms/'+realm @@ -400,7 +589,6 @@ def args_parse(): parser.add_argument('password', type=str, help='Admin password') parser.add_argument('input_yaml', type=str, help='File containing input for roles and clients in YAML format') parser.add_argument('--disable_ssl_verify', help='Disable ssl cert verification while connecting to server', action='store_true') - parser.add_argument('--frontend_url', help='Frontend URL', dest='frontend_url', action='store', default='') args = parser.parse_args() return args @@ -420,7 +608,7 @@ def main(): fp = open(input_yaml, 'rt') values = yaml.load(fp, Loader=yaml.FullLoader) - server_url = server_url + '/auth/' # Full url to access api + server_url = server_url # Full url to access api try: print(server_url) @@ -436,8 +624,9 @@ def main(): for realm in values: if realm == "del_realms": continue - print('\tCreate realms : %s' % realm) - ks.create_realm(realm, args.frontend_url) # {realm : [role]} + print('\tCreate realms : %s' %realm) + if 'realm_config' in values[realm]: + ks.create_realm(realm, values[realm]['realm_config']) # {realm : [role]} for realm in values: roles = [] @@ -467,6 +656,17 @@ def main(): for client_scope in client_scopes: ks.create_client_scope(realm, client_scope) + authentication = [] + if 'authentication' in values[realm]: + authentication = values[realm]['authentication'] + auth_flows = [] + if 'auth_flows' in authentication: + auth_flows = authentication['auth_flows'] + print('\tCreating authentication flows') + for auth_flow in auth_flows: + print('\t\tCreating authentication flow for "%s" ' % auth_flow['alias']) + ks.create_authentication_flow(realm, auth_flow) + # Expect secrets passed via env variables. clients = [] if 'clients' in values[realm]: @@ -512,6 +712,25 @@ def main(): for client_scope in assign_client_scopes: ks.assign_client_scope(realm, client['name'], client_scope) + if 'authentication_flow_overrides' in client: + cid=None + client_data=None + print('\tUpdating "authentication flow overrides" for client : "%s"' % client['name']) + + for auth_flow_overrides in client['authentication_flow_overrides']: + print('\t\tAuth flow overrides "%s" : "%s"' %(auth_flow_overrides, client['authentication_flow_overrides'][auth_flow_overrides])) + authentication_flow_overrides_id = ks.get_auth_flow_id(realm, client['authentication_flow_overrides'][auth_flow_overrides]) + cid=ks.keycloak_admin.get_client_id(client['name']) + client_data=ks.keycloak_admin.get_client(cid) + client_data['authenticationFlowBindingOverrides'][auth_flow_overrides]=authentication_flow_overrides_id + print('\t\t\tauthentication_flow_overrides_id : ', authentication_flow_overrides_id) + print("\t\t\tClient ID : ",cid) + print("\t\t\tClient Data authentication_flow_overrides_id : ",client_data['authenticationFlowBindingOverrides']) + if cid is not None and client_data is not None: + ks.keycloak_admin.update_client(cid,client_data) + print("\t\t\t'authenticationFlowBindingOverrides' successfully updated for client '%s' "% client['name']) + + users = [] if 'users' in values[realm]: users = values[realm]['users']