diff --git a/docs/install-candig.md b/docs/install-candig.md index b411a9f2c..fd3bdaf69 100644 --- a/docs/install-candig.md +++ b/docs/install-candig.md @@ -354,8 +354,6 @@ cp -i etc/env/example.env .env LOCAL_IP_ADDR=xxx.xx.x.x # change OS VENV_OS=arm64mac -# change keycloak -KEYCLOAK_BASE_IMAGE=quay.io/c3genomics/keycloak:${KEYCLOAK_VERSION}.arm64 ``` Edit /etc/hosts on the machine (`sudo nano /etc/hosts`): diff --git a/etc/env/example.env b/etc/env/example.env index da1b74be9..9c758412d 100644 --- a/etc/env/example.env +++ b/etc/env/example.env @@ -175,23 +175,18 @@ CACHE_TIME=0 # keycloak service -KEYCLOAK_VERSION=16.1.1 -#KEYCLOAK_BASE_IMAGE=quay.io/c3genomics/keycloak:${KEYCLOAK_VERSION}.arm64 -KEYCLOAK_BASE_IMAGE=quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} +KEYCLOAK_VERSION=24.0.0 KEYCLOAK_REALM=candig KEYCLOAK_CLIENT_ID=local_candig KEYCLOAK_LOGIN_REDIRECT_PATH=/auth/login KEYCLOAK_PORT=8080 -KEYCLOAK_CONTAINER_PORT=8080 -KEYCLOAK_HOST=0.0.0.0 KEYCLOAK_PUBLIC_PROTO=http KEYCLOAK_PRIVATE_PROTO=http KEYCLOAK_ENABLE_PROXY=false KEYCLOAK_PUBLIC_URL=${KEYCLOAK_PUBLIC_PROTO}://${CANDIG_AUTH_DOMAIN}:${KEYCLOAK_PORT} KEYCLOAK_PUBLIC_URL_PROD=${KEYCLOAK_PUBLIC_PROTO}://${CANDIG_AUTH_DOMAIN} -KEYCLOAK_PRIVATE_URL=${KEYCLOAK_PRIVATE_PROTO}://${CANDIG_AUTH_DOMAIN}:${KEYCLOAK_CONTAINER_PORT} +KEYCLOAK_PRIVATE_URL=${KEYCLOAK_PRIVATE_PROTO}://${CANDIG_AUTH_DOMAIN}:${KEYCLOAK_PORT} KEYCLOAK_REALM_URL=${KEYCLOAK_PUBLIC_URL}/auth/realms/${KEYCLOAK_REALM} - KEYCLOAK_GENERATE_TEST_USER=1 # query service diff --git a/lib/keycloak/Dockerfile b/lib/keycloak/Dockerfile deleted file mode 100644 index 34290e575..000000000 --- a/lib/keycloak/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -ARG BASE_IMAGE -FROM ${BASE_IMAGE} - -COPY ./configuration_templates/* /opt/jboss/keycloak/standalone/ \ No newline at end of file diff --git a/lib/keycloak/client_setup.sh b/lib/keycloak/client_setup.sh new file mode 100644 index 000000000..734d37a11 --- /dev/null +++ b/lib/keycloak/client_setup.sh @@ -0,0 +1,38 @@ +# This script creates and configures a client within a Keycloak realm + +echo +echo -e "${BLUE}Creating client: $KEYCLOAK_CLIENT_ID${DEFAULT}" + +CREATE_OUTPUT=$(KCADM create clients -r "$KEYCLOAK_REALM" \ + -s clientId="$KEYCLOAK_CLIENT_ID" \ + -s enabled=true \ + -s protocol=openid-connect \ + -s publicClient=false \ + -s clientAuthenticatorType=client-secret \ + -s standardFlowEnabled=true \ + -s directAccessGrantsEnabled=true \ + -s 'redirectUris=["'"$TYK_LOGIN_TARGET_URL$KEYCLOAK_LOGIN_REDIRECT_PATH"'"]' \ + -s 'webOrigins=["'"$TYK_LOGIN_TARGET_URL"'"]' 2>&1) +# uncomment the line beblow to see the output +# echo $CREATE_OUTPUT + +# Extract the client ID from the output +CLIENT_ID=$(echo $CREATE_OUTPUT | grep -oE '[0-9a-fA-F-]{36}') + +# Create client scopes +SCOPE_NAME="${KEYCLOAK_CLIENT_ID}-audience" +CREATE_OUTPUT=$(KCADM create clients/$CLIENT_ID/protocol-mappers/models -r $KEYCLOAK_REALM \ + -s name=$SCOPE_NAME \ + -s protocol=openid-connect \ + -s protocolMapper=oidc-audience-mapper \ + -s config="{\"included.client.audience\" : \"$KEYCLOAK_CLIENT_ID\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\"}" 2>&1) +# uncomment the line beblow to see the output +# echo $CREATE_OUTPUT + +# EXPORT: Get the client secret and save it to secrets +CLIENT_SECRET=$(KCADM get clients/"$CLIENT_ID"/client-secret -r "$KEYCLOAK_REALM" | jq -r '.value') +echo "$CLIENT_SECRET" > tmp/secrets/keycloak-client-$KEYCLOAK_CLIENT_ID-secret + +# EXPORT: Encode the Keycloak client ID in base64 and save it to secrets +KEYCLOAK_CLIENT_ID_64=$(echo -n "${KEYCLOAK_CLIENT_ID}" | base64) +echo "$KEYCLOAK_CLIENT_ID_64" > "tmp/secrets/keycloak-client-${KEYCLOAK_CLIENT_ID}-id-64" diff --git a/lib/keycloak/configuration_templates/application-users.properties b/lib/keycloak/configuration_templates/application-users.properties deleted file mode 100644 index 24889c3f3..000000000 --- a/lib/keycloak/configuration_templates/application-users.properties +++ /dev/null @@ -1,25 +0,0 @@ -# -# Properties declaration of users for the realm 'ApplicationRealm' which is the default realm -# for application services on a new installation. -# -# This includes the following protocols: remote ejb, remote jndi, web, remote jms -# -# Users can be added to this properties file at any time, updates after the server has started -# will be automatically detected. -# -# The format of this realm is as follows: - -# username=HEX( MD5( username ':' realm ':' password)) -# -# A utility script is provided which can be executed from the bin folder to add the users: - -# - Linux -# bin/add-user.sh -# -# - Windows -# bin\add-user.bat -# -#$REALM_NAME=ApplicationRealm$ This line is used by the add-user utility to identify the realm name already used in this file. -# -# The following illustrates how an admin user could be defined, this -# is for illustration only and does not correspond to a usable password. -# -#admin=2a0923285184943425d1f53ddd58ec7a diff --git a/lib/keycloak/configuration_templates/logging.properties b/lib/keycloak/configuration_templates/logging.properties deleted file mode 100644 index 3e7aeb1bf..000000000 --- a/lib/keycloak/configuration_templates/logging.properties +++ /dev/null @@ -1,70 +0,0 @@ -# -# JBoss, Home of Professional Open Source. -# Copyright 2013, Red Hat, Inc., and individual contributors -# as indicated by the @author tags. See the copyright.txt file in the -# distribution for a full listing of individual contributors. -# -# This is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation; either version 2.1 of -# the License, or (at your option) any later version. -# -# This software is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this software; if not, write to the Free -# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA, or see the FSF site: http://www.fsf.org. -# - -# Additional loggers to configure (the root logger is always configured) -loggers=com.arjuna,jacorb,org.jboss.as.config,org.apache.tomcat.util.modeler,sun.rmi,jacorb.config - -logger.level=INFO -logger.handlers=CONSOLE,FILE - -logger.com.arjuna.level=WARN -logger.com.arjuna.useParentHandlers=true - -logger.jacorb.level=WARN -logger.jacorb.useParentHandlers=true - -logger.org.jboss.as.config.level=DEBUG -logger.org.jboss.as.config.useParentHandlers=true - -logger.org.apache.tomcat.util.modeler.level=WARN -logger.org.apache.tomcat.util.modeler.useParentHandlers=true - -logger.sun.rmi.level=WARN -logger.sun.rmi.useParentHandlers=true - -logger.jacorb.config.level=ERROR -logger.jacorb.config.useParentHandlers=true - -handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler -handler.CONSOLE.level=INFO -handler.CONSOLE.formatter=COLOR-PATTERN -handler.CONSOLE.properties=autoFlush,target -handler.CONSOLE.autoFlush=true -handler.CONSOLE.target=SYSTEM_OUT - -handler.FILE=org.jboss.logmanager.handlers.PeriodicRotatingFileHandler -handler.FILE.level=ALL -handler.FILE.formatter=PATTERN -handler.FILE.properties=autoFlush,append,fileName,suffix -handler.FILE.constructorProperties=fileName,append -handler.FILE.autoFlush=true -handler.FILE.append=true -handler.FILE.fileName=${org.jboss.boot.log.file:boot.log} -handler.FILE.suffix=.yyyy-MM-dd - -formatter.COLOR-PATTERN=org.jboss.logmanager.formatters.PatternFormatter -formatter.COLOR-PATTERN.properties=pattern -formatter.COLOR-PATTERN.pattern=%K{level}%d{HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%E%n - -formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter -formatter.PATTERN.properties=pattern -formatter.PATTERN.pattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%E%n diff --git a/lib/keycloak/configuration_templates/mgmt-users.properties b/lib/keycloak/configuration_templates/mgmt-users.properties deleted file mode 100644 index 1eb477657..000000000 --- a/lib/keycloak/configuration_templates/mgmt-users.properties +++ /dev/null @@ -1,27 +0,0 @@ -# -# Properties declaration of users for the realm 'ManagementRealm' which is the default realm -# for new installations. Further authentication mechanism can be configured -# as part of the in standalone.xml. -# -# Users can be added to this properties file at any time, updates after the server has started -# will be automatically detected. -# -# By default the properties realm expects the entries to be in the format: - -# username=HEX( MD5( username ':' realm ':' password)) -# -# A utility script is provided which can be executed from the bin folder to add the users: - -# - Linux -# bin/add-user.sh -# -# - Windows -# bin\add-user.bat -# -#$REALM_NAME=ManagementRealm$ This line is used by the add-user utility to identify the realm name already used in this file. -# -# On start-up the server will also automatically add a user $local - this user is specifically -# for local tools running against this AS installation. -# -# The following illustrates how an admin user could be defined, this -# is for illustration only and does not correspond to a usable password. -# -#admin=2a0923285184943425d1f53ddd58ec7a diff --git a/lib/keycloak/configuration_templates/standalone-ha.xml b/lib/keycloak/configuration_templates/standalone-ha.xml deleted file mode 100644 index 35da18455..000000000 --- a/lib/keycloak/configuration_templates/standalone-ha.xml +++ /dev/null @@ -1,636 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - h2 - - sa - sa - - - - jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE - h2 - - sa - sa - - - - - org.h2.jdbcx.JdbcDataSource - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - auth - - - classpath:${jboss.home.dir}/providers/* - - - master - 900 - - 2592000 - true - true - ${env.KEYCLOAK_WELCOME_THEME:keycloak} - ${jboss.home.dir}/themes - - - - - - - - - - - - - jpa - - - basic - - - - - - - - - - - - - - - - - - - default - - - - - - - - ${keycloak.jta.lookup.provider:jboss} - - - - - - - - - - - ${keycloak.x509cert.lookup.provider:default} - - - - ${keycloak.hostname.provider:request} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/keycloak/configuration_templates/standalone.xml b/lib/keycloak/configuration_templates/standalone.xml deleted file mode 100644 index 461ac9497..000000000 --- a/lib/keycloak/configuration_templates/standalone.xml +++ /dev/null @@ -1,573 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - h2 - - sa - sa - - - - jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE - h2 - - sa - sa - - - - - org.h2.jdbcx.JdbcDataSource - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - auth - - classpath:${jboss.home.dir}/providers/* - - master - 900 - - 2592000 - true - true - ${jboss.home.dir}/themes - - - - - - - - - - - - - jpa - - - basic - - - - - - - - - - - - - - - - - - - default - - - - - - - - ${keycloak.jta.lookup.provider:jboss} - - - - - - - - - - - ${keycloak.x509cert.lookup.provider:default} - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/keycloak/credential_loader.sh b/lib/keycloak/credential_loader.sh new file mode 100755 index 000000000..747081ddf --- /dev/null +++ b/lib/keycloak/credential_loader.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Load credentials from secrets +export KEYCLOAK_ADMIN=$(< /run/secrets/keycloak-admin-user) +export KEYCLOAK_ADMIN_PASSWORD=$(< /run/secrets/keycloak-admin-password) + +exec /opt/keycloak/bin/kc.sh "$@" \ No newline at end of file diff --git a/lib/keycloak/docker-compose.yml b/lib/keycloak/docker-compose.yml index 76cf005f2..b85f7ed86 100644 --- a/lib/keycloak/docker-compose.yml +++ b/lib/keycloak/docker-compose.yml @@ -1,31 +1,30 @@ services: keycloak: - build: - context: ${PWD}/lib/keycloak - args: - #- BASE_IMAGE=candig/keycloak:${KEYCLOAK_VERSION} - - BASE_IMAGE=${KEYCLOAK_BASE_IMAGE} + image: keycloak/keycloak:${KEYCLOAK_VERSION} labels: - "candigv2=keycloak" - command: [ "-b", "${KEYCLOAK_HOST}", "-Dkeycloak.migration.strategy=IGNORE_EXISTING" ] - ports: - - "${KEYCLOAK_CONTAINER_PORT}:8080" - extra_hosts: - - "${CANDIG_DOMAIN}:${LOCAL_IP_ADDR}" volumes: - - keycloak-data:/opt/jboss/keycloak/standalone + - ${PWD}/lib/keycloak/credential_loader.sh:/opt/keycloak/credential_loader.sh + - keycloak-data:/opt/keycloak/data environment: - - KEYCLOAK_USER_FILE=/run/secrets/keycloak-admin-user - - KEYCLOAK_PASSWORD_FILE=/run/secrets/keycloak-admin-password - - PROXY_ADDRESS_FORWARDING=${KEYCLOAK_ENABLE_PROXY} - #- KEYCLOAK_FRONTEND_URL=${KEYCLOAK_PUBLIC_URL_PROD}/auth + KC_HOSTNAME: ${CANDIG_DOMAIN} + PROXY_ADDRESS_FORWARDING: ${KEYCLOAK_ENABLE_PROXY} + KC_HTTP_RELATIVE_PATH: /auth + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY: true + # KEYCLOAK_FRONTEND_URL: ${KEYCLOAK_PUBLIC_URL_PROD}/auth secrets: - - source: keycloak-admin-user - target: /run/secrets/keycloak-admin-user - - source: keycloak-admin-password - target: /run/secrets/keycloak-admin-password + - keycloak-admin-user + - keycloak-admin-password + ports: + - "${KEYCLOAK_PORT}:8080" + extra_hosts: + - "${CANDIG_DOMAIN}:${LOCAL_IP_ADDR}" + entrypoint: ["/bin/sh", "-c"] + command: ["/opt/keycloak/credential_loader.sh start-dev"] healthcheck: - test: [ "CMD", "curl", "-f", "http://0.0.0.0:8080" ] + test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080;echo -e \"GET /auth/health HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"] interval: 30s - timeout: 20s - retries: 3 + timeout: 10s + retries: 3 \ No newline at end of file diff --git a/lib/keycloak/keycloak_setup.sh b/lib/keycloak/keycloak_setup.sh index 0cbd0bc3a..7dbec20d4 100644 --- a/lib/keycloak/keycloak_setup.sh +++ b/lib/keycloak/keycloak_setup.sh @@ -1,271 +1,61 @@ #!/usr/bin/env bash -set -Euo pipefail +# Entrypoint into Keycloak. It uses Keycloak Admin CLI (KCADM) +# to setup realms, clients, and users for candig services. -LOGFILE=$PWD/tmp/progress.txt +set -euo pipefail +# Terminal colors +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +GREEN='\033[0;32m' +DEFAULT='\033[0m' -# Verify if keycloak container is running -KEYCLOAK_CONTAINERS=$(echo "$(docker ps | grep keycloak | wc -l)") -echo "Number of keycloak containers running: ${KEYCLOAK_CONTAINERS}" | tee -a $LOGFILE -if [[ $KEYCLOAK_CONTAINERS -eq 1 ]]; then - echo "Waiting for keycloak to start" | tee -a $LOGFILE - while ! docker logs --tail 1000 "$(docker ps | grep keycloak | awk '{print $1}')" | grep "Undertow HTTPS listener https listening on 0.0.0.0"; do sleep 1; done - echo "Keycloak container started." | tee -a $LOGFILE -else - echo "Too many (or too few) keycloak containers! Shut down all keycloak containers and then re-run make compose-keycloak." - exit 1 -fi - -KEYCLOAK_CONTAINER=$(docker ps | grep keycloak | awk '{print $1}') -add_user() { - # CANDIG_AUTH_DOMAIN is the name of the keycloak server inside the compose network - local username=$1 - local password=$2 - - local JSON=' { - "username": "'${username}'", - "email": "'${username}'@test.ca", - "enabled": true, - "access": { - "manageGroupMembership": true, - "view": true, - "mapRoles": true, - "impersonate": true, - "manage": true - } - }' - - user_id=`curl --stderr - \ - -i -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X POST -H "Content-Type: application/json" -d "${JSON}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${KEYCLOAK_REALM}/users" -k | grep -i "Location:" \ - | sed -E s/.*users.\([a-z0-9-]+\).*/\\\1/` - - echo "Created user ${user_id}" | tee -a $LOGFILE - - local password_json=' { - "type": "rawPassword", - "value": "'${password}'" - }' - - curl \ - -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X PUT -H "Content-Type: application/json" -d "${password_json}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${KEYCLOAK_REALM}/users/${user_id}/reset-password" -k -} - -get_token() { - local keycloak_admin_user=$(cat tmp/secrets/keycloak-admin-user) - local keycloak_admin_password=$(cat tmp/secrets/keycloak-admin-password) - - local BID=$(curl \ - -d "client_id=admin-cli" \ - -d "username=${keycloak_admin_user}" \ - -d "password=${keycloak_admin_password}" \ - -d "grant_type=password" \ - "${KEYCLOAK_PUBLIC_URL}/auth/realms/master/protocol/openid-connect/token" -k 2>/dev/null) - # TODO: security issue fix this, -k flag above ignores cert, even if the url is https - - echo ${BID} | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["access_token"])' -} - -set_realm() { - local realm=$1 - - local JSON='{ - "realm": "'${realm}'", - "enabled": true - }' - - local RESULT=$(curl \ - -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X POST -H "Content-Type: application/json" -d "${JSON}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms" -k) - - echo ${RESULT} | grep errorMessage - if [[ $? == 0 ]]; then - echo "Realm cannot be set: ${RESULT}" +handle_error() { + echo -e "🚨🚨🚨 ${RED}AN ERROR OCCURRED DURING KEYCLOAK SETUP PROCESS${DEFAULT} 🚨🚨🚨" exit 1 - fi -} - -get_realm() { - local realm=$1 - - curl \ - -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${realm}" -k | jq . -} - -get_realm_clients() { - local realm=$1 - - curl \ - -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${realm}/clients" -k | jq -S . -} - -set_client() { - local realm=$1 - local client=$2 - local redirect=$3 - - # add client scope with protocol mappers - scope_json='{ - "name": "'${realm}'", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "name": "'${client}'-audience", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-mapper", - "consentRequired": false, - "config": { - "included.client.audience": "'${client}'", - "id.token.claim": "true", - "access.token.claim": "true" - } - } - ] - }' - - new_scope=`curl --stderr - \ - -i -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X POST -H "Content-Type: application/json" -d "${scope_json}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${realm}/client-scopes" -k | grep -i "Location:" \ - | sed -E s/.*client-scopes.\([a-z0-9-]+\).*/\\\\1/` - - echo "Created client scope ${new_scope}" | tee -a $LOGFILE - - # Will add / to listen only if it is present - - local client_json='{ - "clientId": "'"${client}"'", - "enabled": true, - "protocol": "openid-connect", - "implicitFlowEnabled": true, - "standardFlowEnabled": true, - "publicClient": false, - "redirectUris": [ - "'${TYK_LOGIN_TARGET_URL}${redirect}'" - ], - "attributes": { - "saml.assertion.signature": "false", - "saml.authnstatement": "false", - "saml.client.signature": "false", - "saml.encrypt": "false", - "saml.force.post.binding": "false", - "saml.multivalued.roles": "false", - "saml.onetimeuse.condition": "false", - "saml.server.signature": "false", - "saml.server.signature.keyinfo.ext": "false", - "saml_force_name_id_format": "false" - }, - "defaultClientScopes": [ - "web-origins", - "roles", - "profile", - "'${realm}'", - "email" - ] - }' - - new_client=`curl --stderr - \ - -i -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X POST -H "Content-Type: application/json" -d "${client_json}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${realm}/clients" -k | grep -i "Location:" \ - | sed -E s/.*clients.\([a-z0-9-]+\).*/\\\\1/` - # TODO: security issue fix this, -k flag above ignores cert, even if the url is https - - echo "Created client ${new_client}" | tee -a $LOGFILE } -set_role() { - local realm=$1 - local client=$2 - local role=$3 - - local JSON='{ - "name": "'${role}'" - }' - - role_id=`curl \ - -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - -X POST -H "Content-Type: application/json" -d "${JSON}" \ - "${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${realm}/roles" -k` - - echo "Created role ${role}" | tee -a $LOGFILE -} - -get_secret() { - id=$(curl -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - ${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${KEYCLOAK_REALM}/clients -k 2>/dev/null | - python3 -c 'import json,sys;obj=json.load(sys.stdin); print([l["id"] for l in obj if l["clientId"] == - "'"$KEYCLOAK_CLIENT_ID"'" ][0])') - - curl -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ - ${KEYCLOAK_PUBLIC_URL}/auth/admin/realms/${KEYCLOAK_REALM}/clients/$id/client-secret -k 2>/dev/null | - python3 -c 'import json,sys;obj=json.load(sys.stdin); print(obj["value"])' -} - -get_public_key() { - curl \ - ${KEYCLOAK_PUBLIC_URL}/auth/realms/${KEYCLOAK_REALM} -k 2>/dev/null | - python3 -c 'import json,sys;obj=json.load(sys.stdin); print(obj["public_key"])' -} - -# SCRIPT START - -echo " Starting setup calls to keycloak" | tee -a $LOGFILE - -echo "Getting keycloak token" | tee -a $LOGFILE -KEYCLOAK_TOKEN=$(get_token) - -echo "Creating Realm ${KEYCLOAK_REALM}" | tee -a $LOGFILE -set_realm ${KEYCLOAK_REALM} - -echo "Setting client ${KEYCLOAK_CLIENT_ID} in base64" | tee -a $LOGFILE -export KEYCLOAK_CLIENT_ID_64=$(echo -n ${KEYCLOAK_CLIENT_ID} | base64) -echo $KEYCLOAK_CLIENT_ID_64 > tmp/secrets/keycloak-client-$KEYCLOAK_CLIENT_ID-id-64 - -echo "Remove ports on prod" | tee -a $LOGFILE -if [[ ${KEYCLOAK_PUBLIC_URL} == *":443"* ]]; then - echo "option 1"; - export KEYCLOAK_PUBLIC_URL_PROD=$(echo ${KEYCLOAK_PUBLIC_URL} | sed -e 's/\(:443\)\$//g') -elif [[ ${KEYCLOAK_PUBLIC_URL} == *":80"* ]]; then - echo "option 2"; - export KEYCLOAK_PUBLIC_URL_PROD=$(echo ${KEYCLOAK_PUBLIC_URL} | sed -e 's/\(:80\)\$//g') -else - echo "option 3"; - export KEYCLOAK_PUBLIC_URL_PROD=$KEYCLOAK_PUBLIC_URL -fi ; - -echo "Setting client ${KEYCLOAK_CLIENT_ID}" | tee -a $LOGFILE -set_client "${KEYCLOAK_REALM}" "${KEYCLOAK_CLIENT_ID}" "${KEYCLOAK_LOGIN_REDIRECT_PATH}" - -echo "Getting keycloak secret" | tee -a $LOGFILE -KEYCLOAK_SECRET_RESPONSE=$(get_secret ${KEYCLOAK_REALM}) -export KEYCLOAK_SECRET=$KEYCLOAK_SECRET_RESPONSE -echo $KEYCLOAK_SECRET > tmp/secrets/keycloak-client-$KEYCLOAK_CLIENT_ID-secret | tee -a $LOGFILE - -echo "Getting keycloak public key" | tee -a $LOGFILE -KEYCLOAK_PUBLIC_KEY_RESPONSE=$(get_public_key ${KEYCLOAK_REALM}) -export KEYCLOAK_PUBLIC_KEY=$KEYCLOAK_PUBLIC_KEY_RESPONSE -echo "Retrieved keycloak public key as ${KEYCLOAK_PUBLIC_KEY}" | tee -a $LOGFILE -echo $KEYCLOAK_PUBLIC_KEY > tmp/secrets/keycloak-public-key | tee -a $LOGFILE - -if [[ ${KEYCLOAK_GENERATE_TEST_USER} == 1 ]]; then - echo "Adding test user" | tee -a $LOGFILE - add_user "$(sed s/@test.ca// tmp/secrets/keycloak-test-user)" "$(cat tmp/secrets/keycloak-test-user-password)" - add_user "$(sed s/@test.ca// tmp/secrets/keycloak-test-user2)" "$(cat tmp/secrets/keycloak-test-user2-password)" - add_user "$(sed s/@test.ca// tmp/secrets/keycloak-test-site-admin)" "$(cat tmp/secrets/keycloak-test-site-admin-password)" +# Trap ERR signal to call handle_error function +trap handle_error ERR + +#################################################### +# VARIABLES CONFIGURATION # +#################################################### +KEYCLOAK_ADMIN=$(cat tmp/secrets/keycloak-admin-user) +KEYCLOAK_ADMIN_PASSWORD=$(cat tmp/secrets/keycloak-admin-password) +READY_CHECK_URL="http://${CANDIG_DOMAIN}:${KEYCLOAK_PORT}/auth/health/ready" +KC_ADMIN_URL="http://${CANDIG_DOMAIN}:${KEYCLOAK_PORT}/auth" +##################################################### + +echo -e "🚧🚧🚧 ${YELLOW}KEYCLOAK SETUP BEGIN${DEFAULT} 🚧🚧🚧" +echo -n ">> waiting for keycloak to start" +# keycloak is booting up before it can accept any requests +until $(curl --output /dev/null --silent --fail --head "${READY_CHECK_URL}"); do + printf '.' + sleep 1 +done +echo -e "\n${GREEN}Keycloak is ready ✅${DEFAULT}" + +# Get the Keycloak container ID +KEYCLOAK_CONTAINER_ID=$(docker ps | grep keycloak/keycloak | awk '{print $1}') + +# Define the KCADM function to run commands inside the Keycloak container +function KCADM() { + docker exec "$KEYCLOAK_CONTAINER_ID" /opt/keycloak/bin/kcadm.sh "$@" +} + +# authenticate as admin +KCADM config credentials --server $KC_ADMIN_URL --user $KEYCLOAK_ADMIN --password $KEYCLOAK_ADMIN_PASSWORD --realm master + +# create realm +source ./lib/keycloak/realm_setup.sh +# create client +source ./lib/keycloak/client_setup.sh +# create test users +if [ "${KEYCLOAK_GENERATE_TEST_USER}" == "1" ]; then + source ./lib/keycloak/user_setup.sh fi -echo "Waiting for keycloak to restart" | tee -a $LOGFILE -while ! docker logs --tail 5 ${KEYCLOAK_CONTAINER} | grep "Admin console listening on http://127.0.0.1:9990"; do sleep 1; done -echo "Keycloak setup done!" | tee -a $LOGFILE +echo -e "🎉🎉🎉 ${GREEN}KEYCLOAK SETUP DONE!${DEFAULT} 🎉🎉🎉" diff --git a/lib/keycloak/realm_setup.sh b/lib/keycloak/realm_setup.sh new file mode 100644 index 000000000..300d41150 --- /dev/null +++ b/lib/keycloak/realm_setup.sh @@ -0,0 +1,19 @@ +# This script creates and configures a realm within Keycloak + +echo +echo -e "${BLUE}Creating realm: $KEYCLOAK_REALM${DEFAULT}" + +CREATE_OUTPUT=$(KCADM create realms -s realm="$KEYCLOAK_REALM" -s enabled=true 2>&1) +# uncomment the line beblow to see the output +# echo $CREATE_OUTPUT + +KCADM update events/config -r ${KEYCLOAK_REALM} \ +-s adminEventsEnabled=true \ +-s adminEventsDetailsEnabled=true \ +-s eventsEnabled=true \ +-s eventsExpiration=259200 \ +-s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]' + +# EXPORT: Get the realm public key and save it to secrets +KEYCLOAK_PUBLIC_KEY=$(curl ${KEYCLOAK_PUBLIC_URL}/auth/realms/${KEYCLOAK_REALM} -k 2>/dev/null | jq -r '.public_key') +echo "$KEYCLOAK_PUBLIC_KEY" > tmp/secrets/keycloak-public-key \ No newline at end of file