diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9de6f55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +data +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bb6e101 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM quay.io/ecosystem-appeng/keycloak-source:latest as keycloak + +FROM registry.access.redhat.com/ubi9/openjdk-11-runtime +WORKDIR /opt/keycloak/bin +ARG KEYCLOAK_VERSION=21.1.1 + +USER root +# COPY ca-bundle.crt /etc/pki/ca-trust/source/anchors +# RUN update-ca-trust extract +# RUN trust list --filter=ca-anchors | grep ingress -A3 + +RUN microdnf install -y jq +RUN mkdir -p /opt/keycloak/bin/client/lib/ + +COPY --from=keycloak /opt/keycloak/bin/client/keycloak-admin-cli-${KEYCLOAK_VERSION}.jar client +COPY --from=keycloak /opt/keycloak/bin/client/lib/ client/lib/ +COPY --from=keycloak /opt/keycloak/bin/kcadm.sh . + +COPY scripts/*.sh /opt/keycloak/bin/ + +RUN chmod +x /opt/keycloak/bin/Entrypoint.sh && \ + chmod +x /opt/keycloak/bin/kcadm.sh && \ + chmod +x /opt/keycloak/bin/user-import.sh && \ + chmod +x /opt/keycloak/bin/user-export.sh + +USER 185 +ENTRYPOINT ["/opt/keycloak/bin/Entrypoint.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..669bd94 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +WORK_DIRECTORY := $(shell pwd)/data +CONTAINER_ENGINE ?= $(shell which podman >/dev/null 2>&1 && echo podman || echo docker) + +help: ## Prints help for targets with comments + @grep -E '^[a-zA-Z0-9.\ _-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +build-docker: ## Build the application image using Docker + docker build -t quay.io/ecosystem-appeng/kc-exim . + +build-podman: ## Build the application image using Podman + podman build -t quay.io/ecosystem-appeng/kc-exim . + +build: ## build with available Container Enging + @$(CONTAINER_ENGINE) build -t quay.io/ecosystem-appeng/kc-exim . + +export: ## run an export job, exports remote server users into local filesystem + + @$(CONTAINER_ENGINE) run -it \ + -e EXPORT_KEYCLOAK_SERVER=$(EXPORT_KEYCLOAK_SERVER) \ + -e EXPORT_REALM=$(EXPORT_REALM) \ + -e EXPORT_TOKEN=$(EXPORT_TOKEN) \ + -v $(WORK_DIRECTORY):/home/default/kc-exim \ + quay.io/ecosystem-appeng/kc-exim export + +import: ## run an import job, imports local users/groups into a remote server + @$(CONTAINER_ENGINE) run -it \ + -e IMPORT_KEYCLOAK_SERVER=$(IMPORT_KEYCLOAK_SERVER) \ + -e IMPORT_REALM=$(IMPORT_REALM) \ + -e IMPORT_TOKEN=$(IMPORT_TOKEN) \ + -v $(WORK_DIRECTORY):/home/default/kc-exim/ \ + quay.io/ecosystem-appeng/kc-exim import + +generate-token: ## TO-DO maybe support generating token based on username/password \ No newline at end of file diff --git a/README.md b/README.md index c07827e..303f3de 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,89 @@ -# kcum -key cloak user migrationr shell scripts +# kc-exim +KeyCloak user Export/Import tool -## about -this app contains the kcadm.sh and its dependincies script provided with the latest Keycloak distibution. -in addition a customized scripts that uses the kcadm.sh to export/import keycloak users from a server/realm into a target server/realm -it does export relevant user infor including attributes -it does export group paths -during the import, the same groups expected to pre-exist in the target server, and the newly migrated users will join the target groups according to the target server group ids, (automatically) +## Building the Image -## how to run +### Build +``` +make build +``` -1. clone - - ``` - git clone git@github.com:nemerna/kcum.git +### build with Docker +``` +make build-docker +``` + + +### build with Podman +``` +make build-podman +``` + +--- +--- +## Variables Used By the Application + +| Environment Variables | Description | +|-------------------------- |:---------------------------------------------------------------------------------------------------------------------: | +| `EXPORT_KEYCLOAK_SERVER` | The Keycloak Server that you would like to export Users from | +| `EXPORT_REALM` | The Realm Name under the EXPORT Server you would like to export users from | +| `EXPORT_TOKEN` | A Temporary Token you need to obtain (using curl or any other method)
used for authenticating to the EXPORT server | +| `IMPORT_KEYCLOAK_SERVER` | The Keycloak Server that you would like to Import Users into | +| `IMPORT_REALM` | The Realm Name under the IMPORT Server you would like to IMPORT users into | +| `IMPORT_TOKEN` | A Temporary Token you need to obtain (using curl or any other method)
used for authenticating to the IMPORT server | + +--- +--- + +## Running The Application + + +### EXPORT + +1. prepare variables + ``` + export EXPORT_KEYCLOAK_SERVER=http://localhost:2020 + export EXPORT_REALM=kcm + export EXPORT_TOKEN=xxxxx ``` -2. move to the project dir directory +2. start the EXPORT job ``` - cd kcum + make export ``` -3. add the bin to your PATH +### IMPORT + +1. prepare variables ``` - export PATH=$PATH:$(pwd)/bin + # export the following env variables + IMPORT_KEYCLOAK_SERVER=http://localhost:1010 + IMPORT_REALM=kcm + IMPORT_TOKEN=xxxxx ``` -4. export relevant env vars - +2. start the IMPORT job + ``` + make import ``` - #the work directory to proccess directory (always should be set) - export WORK_DIRECTORY=./USERMANAGER_EXPORT - - # the source server url (copy from) - export IMPORT_KEYCLOAK_SERVER=https://src-server.com - - # the realm of the source server (copy from) - export IMPORT_REALM=src-realm - - # the client-id of the source server (copy from) - export IMPORT_CLIENT=src-client +--- +--- - # the client secret of the source server (copy from) - export IMPORT_SECRET=src-secret +## Help Commands +### obtaining an EXPORT_TOKEN token example - # the target server url (create in) - export EXPORT_KEYCLOAK_SERVER=https://target-server.com +``` + export EXPORT_TOKEN=$(curl -X POST --location "https://$EXPORT_KEYCLOAK_SERVER/realms/$EXPORT_REALM/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&username=$USER_NAME_PLACE_HOLDER&password=$PASSWORD_PLACE_HOLDER&client_id=$CLIENT_ID_PLACE_HOLDER" | jq -r .access_token) +``` - # the realm of the target server (create in) - export EXPORT_REALM=target-realm +### obtaining an IMPORT_TOKEN token example - # the client-id of the target server (create in) - export EXPORT_CLIENT=target-client +``` + export IMPORT_TOKEN=$(curl -X POST --location "https://$IMPORT_KEYCLOAK_SERVER/realms/$IMPORT_REALM/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&username=$USER_NAME_PLACE_HOLDER&password=$PASSWORD_PLACE_HOLDER&client_id=$CLIENT_ID_PLACE_HOLDER" | jq -r .access_token) +``` - # the client secret of the target server (create in) - export EXPORT_SECRET=target-secret - ``` -4. run the user manager - - ``` - user-manager.sh [export | import | migrate] - ``` -**NOTE: when you export, only export parameters needed, when import then only import parameters are needed, when igrate you need to specify both export and import related variables** diff --git a/bin/client/keycloak-admin-cli-21.1.2.jar b/bin/client/keycloak-admin-cli-21.1.2.jar deleted file mode 100644 index c8600c9..0000000 Binary files a/bin/client/keycloak-admin-cli-21.1.2.jar and /dev/null differ diff --git a/bin/client/keycloak-client-registration-cli-21.1.2.jar b/bin/client/keycloak-client-registration-cli-21.1.2.jar deleted file mode 100644 index a26776d..0000000 Binary files a/bin/client/keycloak-client-registration-cli-21.1.2.jar and /dev/null differ diff --git a/bin/client/lib/bcprov-jdk15on-1.70.jar b/bin/client/lib/bcprov-jdk15on-1.70.jar deleted file mode 100644 index 0e4198e..0000000 Binary files a/bin/client/lib/bcprov-jdk15on-1.70.jar and /dev/null differ diff --git a/bin/client/lib/keycloak-crypto-default-21.1.2.jar b/bin/client/lib/keycloak-crypto-default-21.1.2.jar deleted file mode 100644 index ba67ed9..0000000 Binary files a/bin/client/lib/keycloak-crypto-default-21.1.2.jar and /dev/null differ diff --git a/bin/client/lib/keycloak-crypto-fips1402-21.1.2.jar b/bin/client/lib/keycloak-crypto-fips1402-21.1.2.jar deleted file mode 100644 index 6e4bc99..0000000 Binary files a/bin/client/lib/keycloak-crypto-fips1402-21.1.2.jar and /dev/null differ diff --git a/bin/groups-ids-wrapper.sh b/bin/groups-ids-wrapper.sh deleted file mode 100755 index 5bfa94b..0000000 --- a/bin/groups-ids-wrapper.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# Function to extract the ID of a group by its path -get_group_id() { - local group_path=$1 - local id - - # Remove double quotes from the path - group_path="${group_path%\"}" - group_path="${group_path#\"}" - - # Use jq's recursive ".." to traverse the JSON structure - id=$(jq -r --arg path "$group_path" '.. | select(type=="object" and .path?==$path) | .id' $ALL_GROUPS_FILE) - - if [[ $id != "null" ]]; then - echo $id - return 0 - fi - - return 1 -} - -# Iterate over each directory -for user_dir in "$WORK_DIRECTORY"/*; do - # Ensure it's a directory - if [ -d "$user_dir" ]; then - # Define the group_paths file for this directory - group_paths_file="$user_dir/group_paths.csv" - group_ids_file="$user_dir/GROUP_IDS.csv" - - # Read each line from the group_paths file - while IFS= read -r line || [ -n "$line" ]; do - # Split the line into paths - IFS=' ' read -ra paths <<< "$line" - group_ids_line="" - for path in "${paths[@]}"; do - # Get the group ID - group_id=$(get_group_id "$path") - - if [[ -n $group_id ]]; then - # Append group id to group_ids_line - group_ids_line+="$group_id " - else - echo "Group not found for path: $path" - fi - done - # Output the group IDs to the file - echo "${group_ids_line% }" >> "$group_ids_file" - done < "$group_paths_file" - fi -done diff --git a/bin/kc.bat b/bin/kc.bat deleted file mode 100755 index ddf4559..0000000 --- a/bin/kc.bat +++ /dev/null @@ -1,175 +0,0 @@ -@echo off -rem ------------------------------------------------------------------------- -rem Keycloak Startup Script -rem ------------------------------------------------------------------------- - -@if not "%ECHO%" == "" echo %ECHO% -setlocal - -rem Get the program name before using shift as the command modify the variable ~nx0 -if "%OS%" == "Windows_NT" ( - set PROGNAME=%~nx0% -) else ( - set PROGNAME=kc.bat -) - -if "%OS%" == "Windows_NT" ( - set DIRNAME=%~dp0% -) else ( - set DIRNAME=.\ -) - -set SERVER_OPTS=-Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dquarkus-log-max-startup-records=10000 - -set DEBUG_MODE=false -set DEBUG_PORT_VAR=8787 -set DEBUG_SUSPEND_VAR=n -set CONFIG_ARGS= - -rem Read command-line args, the ~ removes the quotes from the parameter -:READ-ARGS -set KEY=%~1 -if "%KEY%" == "" ( - goto MAIN -) -if "%KEY%" == "--debug" ( - set DEBUG_MODE=true - set DEBUG_PORT_VAR=%2 - if "%DEBUG_PORT_VAR%" == "" ( - set DEBUG_PORT_VAR=8787 - ) - if "%DEBUG_SUSPEND_VAR%" == "" ( - set DEBUG_SUSPEND_VAR=n - ) - shift - shift - goto READ-ARGS -) -if "%KEY%" == "start-dev" ( - set CONFIG_ARGS=%CONFIG_ARGS% --profile=dev %KEY% - shift - goto READ-ARGS -) -if not "%KEY:~0,2%"=="--" if "%KEY:~0,2%"=="-D" ( - set SERVER_OPTS=%SERVER_OPTS% %KEY%=%2 - shift -) -if not "%KEY:~0,2%"=="--" if not "%KEY:~0,1%"=="-" ( - set CONFIG_ARGS=%CONFIG_ARGS% %KEY% -) -if not "%KEY:~0,2%"=="-D" ( - if "%KEY:~0,1%"=="-" ( - if "%~2"=="" ( - set CONFIG_ARGS=%CONFIG_ARGS% %KEY% - ) else ( - set CONFIG_ARGS=%CONFIG_ARGS% %KEY% %2% - ) - shift - ) -) -shift -goto READ-ARGS - -:MAIN -if not "x%JAVA_OPTS%" == "x" ( - echo "JAVA_OPTS already set in environment; overriding default settings with values: %JAVA_OPTS%" -) else ( - set "JAVA_OPTS=-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.err.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8" -) - -@REM See also https://github.com/wildfly/wildfly-core/blob/7e5624cf92ebe4b64a4793a8c0b2a340c0d6d363/core-feature-pack/common/src/main/resources/content/bin/common.sh#L57-L60 -if not "x%JAVA_ADD_OPENS%" == "x" ( - echo "JAVA_ADD_OPENS already set in environment; overriding default settings with values: %JAVA_ADD_OPENS%" -) else ( - set "JAVA_ADD_OPENS=--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED" -) -set "JAVA_OPTS=%JAVA_OPTS% %JAVA_ADD_OPENS%" - -if not "x%JAVA_OPTS_APPEND%" == "x" ( - echo "Appending additional Java properties to JAVA_OPTS: %JAVA_OPTS_APPEND%" - set JAVA_OPTS=%JAVA_OPTS% %JAVA_OPTS_APPEND% -) - -if NOT "x%DEBUG%" == "x" ( - set DEBUG_MODE=%DEBUG% -) - -if NOT "x%DEBUG_PORT%" == "x" ( - set DEBUG_PORT_VAR=%DEBUG_PORT% -) - -if NOT "x%DEBUG_SUSPEND%" == "x" ( - set DEBUG_SUSPEND_VAR=%DEBUG_SUSPEND% -) - -rem Set debug settings if not already set -if "%DEBUG_MODE%" == "true" ( - echo "%JAVA_OPTS%" | findstr /I "\-agentlib:jdwp" > nul - if errorlevel == 1 ( - set JAVA_OPTS=%JAVA_OPTS% -agentlib:jdwp=transport=dt_socket,address=%DEBUG_PORT_VAR%,server=y,suspend=%DEBUG_SUSPEND_VAR% - ) else ( - echo Debug already enabled in JAVA_OPTS, ignoring --debug argument - ) -) - -rem Setup Keycloak specific properties -set JAVA_OPTS=-Dprogram.name=%PROGNAME% %JAVA_OPTS% - -if "x%JAVA_HOME%" == "x" ( - set JAVA=java - echo JAVA_HOME is not set. Unexpected results may occur. - echo Set JAVA_HOME to the directory of your local JDK to avoid this message. -) else ( - if not exist "%JAVA_HOME%" ( - echo JAVA_HOME "%JAVA_HOME%" path doesn't exist - goto END - ) else ( - if not exist "%JAVA_HOME%\bin\java.exe" ( - echo "%JAVA_HOME%\bin\java.exe" does not exist - goto END - ) - set "JAVA=%JAVA_HOME%\bin\java" - ) -) - -set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar" - -set JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir=%DIRNAME%.. -Djboss.server.config.dir=%DIRNAME%..\conf -Dkeycloak.theme.dir=%DIRNAME%..\themes %SERVER_OPTS% -cp %CLASSPATH_OPTS% io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS% - -SetLocal EnableDelayedExpansion - -set OPTIMIZED_OPTION=--optimized -set HELP_LONG_OPTION=--help -set BUILD_OPTION=build -set IS_HELP_SHORT=false - -echo %CONFIG_ARGS% | findstr /r "\<-h\>" > nul - -if not errorlevel == 1 ( - set IS_HELP_SHORT=true -) - -if "%PRINT_ENV%" == "true" ( - echo "Using JAVA_OPTS: %JAVA_OPTS%" - echo "Using JAVA_RUN_OPTS: %JAVA_RUN_OPTS%" -) - -set START_SERVER=true - -if "!CONFIG_ARGS:%OPTIMIZED_OPTION%=!"=="!CONFIG_ARGS!" if "!CONFIG_ARGS:%BUILD_OPTION%=!"=="!CONFIG_ARGS!" if "!CONFIG_ARGS:%HELP_LONG_OPTION%=!"=="!CONFIG_ARGS!" if "%IS_HELP_SHORT%" == "false" ( - setlocal enabledelayedexpansion - - "%JAVA%" -Dkc.config.build-and-exit=true %JAVA_RUN_OPTS% - - if not !errorlevel! == 0 ( - set START_SERVER=false - ) - - set JAVA_RUN_OPTS=-Dkc.config.built=true %JAVA_RUN_OPTS% -) - -if "%START_SERVER%" == "true" ( - "%JAVA%" %JAVA_RUN_OPTS% -) - -:END diff --git a/bin/kc.sh b/bin/kc.sh deleted file mode 100755 index 6b66014..0000000 --- a/bin/kc.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/bin/bash - -case "$(uname)" in - CYGWIN*) - IS_CYGWIN="true" - CFILE="$(cygpath "$0")" - RESOLVED_NAME="$(readlink -f "$CFILE")" - ;; - Darwin*) - RESOLVED_NAME="$(readlink "$0")" - ;; - FreeBSD) - RESOLVED_NAME="$(readlink -f "$0")" - ;; - Linux) - RESOLVED_NAME="$(readlink -f "$0")" - ;; -esac - -if [ "x$RESOLVED_NAME" = "x" ]; then - RESOLVED_NAME="$0" -fi - -GREP="grep" -DIRNAME="$(dirname "$RESOLVED_NAME")" - -abs_path () { - if [ -z $IS_CYGWIN ] ; then - echo "$DIRNAME/$1" - else - cygpath -w "$DIRNAME/$1" - fi -} - -SERVER_OPTS="-Dkc.home.dir='$(abs_path '..')'" -SERVER_OPTS="$SERVER_OPTS -Djboss.server.config.dir='$(abs_path '../conf')'" -SERVER_OPTS="$SERVER_OPTS -Djava.util.logging.manager=org.jboss.logmanager.LogManager" -SERVER_OPTS="$SERVER_OPTS -Dquarkus-log-max-startup-records=10000" -CLASSPATH_OPTS="'$(abs_path "../lib/quarkus-run.jar")'" - -DEBUG_MODE="${DEBUG:-false}" -DEBUG_PORT="${DEBUG_PORT:-8787}" -DEBUG_SUSPEND="${DEBUG_SUSPEND:-n}" - -CONFIG_ARGS=${CONFIG_ARGS:-""} - -while [ "$#" -gt 0 ] -do - case "$1" in - --debug) - DEBUG_MODE=true - if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then - DEBUG_PORT=$2 - shift - fi - ;; - --) - shift - break - ;; - *) - if [[ $1 = --* || ! $1 =~ ^-D.* ]]; then - if [[ "$1" = "start-dev" ]]; then - CONFIG_ARGS="$CONFIG_ARGS --profile=dev $1" - else - CONFIG_ARGS="$CONFIG_ARGS $1" - fi - else - SERVER_OPTS="$SERVER_OPTS $1" - fi - ;; - esac - shift -done - -if [ "x$JAVA" = "x" ]; then - if [ "x$JAVA_HOME" != "x" ]; then - JAVA="$JAVA_HOME/bin/java" - else - JAVA="java" - fi -fi - -# -# Specify options to pass to the Java VM. -# -if [ "x$JAVA_OPTS" = "x" ]; then - JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.err.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8" -else - echo "JAVA_OPTS already set in environment; overriding default settings with values: $JAVA_OPTS" -fi - -# See also https://github.com/wildfly/wildfly-core/blob/7e5624cf92ebe4b64a4793a8c0b2a340c0d6d363/core-feature-pack/common/src/main/resources/content/bin/common.sh#L57-L60 -if [ "x$JAVA_ADD_OPENS" = "x" ]; then - JAVA_ADD_OPENS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED" -else - echo "JAVA_ADD_OPENS already set in environment; overriding default settings with values: $JAVA_ADD_OPENS" -fi -JAVA_OPTS="$JAVA_OPTS $JAVA_ADD_OPENS" - -if [ "x$JAVA_OPTS_APPEND" != "x" ]; then - echo "Appending additional Java properties to JAVA_OPTS: $JAVA_OPTS_APPEND" - JAVA_OPTS="$JAVA_OPTS $JAVA_OPTS_APPEND" -fi - -# Set debug settings if not already set -if [ "$DEBUG_MODE" = "true" ]; then - DEBUG_OPT="$(echo "$JAVA_OPTS" | $GREP "\-agentlib:jdwp")" - if [ "x$DEBUG_OPT" = "x" ]; then - JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND" - else - echo "Debug already enabled in JAVA_OPTS, ignoring --debug argument" - fi -fi - -JAVA_RUN_OPTS="$JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint ${CONFIG_ARGS#?}" - -if [ "$PRINT_ENV" = "true" ]; then - echo "Using JAVA_OPTS: $JAVA_OPTS" - echo "Using JAVA_RUN_OPTS: $JAVA_RUN_OPTS" -fi - -if [[ (! $CONFIG_ARGS = *"--optimized"*) ]] && [[ ! "$CONFIG_ARGS" == " build"* ]] && [[ ! "$CONFIG_ARGS" == *"-h" ]] && [[ ! "$CONFIG_ARGS" == *"--help"* ]]; then - eval "'$JAVA'" -Dkc.config.build-and-exit=true $JAVA_RUN_OPTS - EXIT_CODE=$? - JAVA_RUN_OPTS="-Dkc.config.built=true $JAVA_RUN_OPTS" - if [ $EXIT_CODE != 0 ]; then - exit $EXIT_CODE - fi -fi - -eval exec "'$JAVA'" $JAVA_RUN_OPTS diff --git a/bin/kcadm.bat b/bin/kcadm.bat deleted file mode 100644 index 9f69f85..0000000 --- a/bin/kcadm.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off - -if "%OS%" == "Windows_NT" ( - set "DIRNAME=%~dp0%" -) else ( - set DIRNAME=.\ -) -java %KC_OPTS% -cp "%DIRNAME%\client\keycloak-admin-cli-21.1.2.jar" --add-opens=java.base/java.security=ALL-UNNAMED -Dkc.lib.dir="%DIRNAME%\client\lib" org.keycloak.client.admin.cli.KcAdmMain %* diff --git a/bin/kcadm.sh b/bin/kcadm.sh deleted file mode 100755 index 490c841..0000000 --- a/bin/kcadm.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -case "`uname`" in - CYGWIN*) - CFILE = `cygpath "$0"` - RESOLVED_NAME=`readlink -f "$CFILE"` - ;; - Darwin*) - RESOLVED_NAME=`readlink "$0"` - ;; - FreeBSD) - RESOLVED_NAME=`readlink -f "$0"` - ;; - Linux) - RESOLVED_NAME=`readlink -f "$0"` - ;; -esac - -if [ "x$RESOLVED_NAME" = "x" ]; then - RESOLVED_NAME="$0" -fi - -DIRNAME=`dirname "$RESOLVED_NAME"` - -if [ "x$JAVA" = "x" ]; then - if [ "x$JAVA_HOME" != "x" ]; then - JAVA="$JAVA_HOME/bin/java" - else - JAVA="java" - fi -fi - -"$JAVA" $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-21.1.2.jar --add-opens=java.base/java.security=ALL-UNNAMED -Dkc.lib.dir=$DIRNAME/client/lib org.keycloak.client.admin.cli.KcAdmMain "$@" diff --git a/bin/kcreg.bat b/bin/kcreg.bat deleted file mode 100644 index 46fc4e6..0000000 --- a/bin/kcreg.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off - -if "%OS%" == "Windows_NT" ( - set "DIRNAME=%~dp0%" -) else ( - set DIRNAME=.\ -) -java %KC_OPTS% -cp "%DIRNAME%\client\keycloak-client-registration-cli-21.1.2.jar" --add-opens=java.base/java.security=ALL-UNNAMED -Dkc.lib.dir="%DIRNAME%\client\lib" org.keycloak.client.registration.cli.KcRegMain %* diff --git a/bin/kcreg.sh b/bin/kcreg.sh deleted file mode 100755 index c513eff..0000000 --- a/bin/kcreg.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh -case "`uname`" in - CYGWIN*) - CFILE = `cygpath "$0"` - RESOLVED_NAME=`readlink -f "$CFILE"` - ;; - Darwin*) - RESOLVED_NAME=`readlink "$0"` - ;; - FreeBSD) - RESOLVED_NAME=`readlink -f "$0"` - ;; - Linux) - RESOLVED_NAME=`readlink -f "$0"` - ;; -esac - -if [ "x$RESOLVED_NAME" = "x" ]; then - RESOLVED_NAME="$0" -fi - -if [ "x$JAVA" = "x" ]; then - if [ "x$JAVA_HOME" != "x" ]; then - JAVA="$JAVA_HOME/bin/java" - else - JAVA="java" - fi -fi - -DIRNAME=`dirname "$RESOLVED_NAME"` -"$JAVA" $KC_OPTS -cp $DIRNAME/client/keycloak-client-registration-cli-21.1.2.jar --add-opens=java.base/java.security=ALL-UNNAMED -Dkc.lib.dir=$DIRNAME/client/lib org.keycloak.client.registration.cli.KcRegMain "$@" diff --git a/bin/user-exporter.sh b/bin/user-exporter.sh deleted file mode 100755 index dc1a0da..0000000 --- a/bin/user-exporter.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -echo "starting exporter" - -ERROR_COUNT=0 -# Get users as single json -kcadm.sh get users -r $EXPORT_REALM -F id,username > $ALL_USERS_FILE - -# For each user, get role mappings, group memberships and user attributes and append it to the respective user entry -users=$(cat $ALL_USERS_FILE | jq -c '.[]') - -# For each user id&name get groups paths and full user definition relevant attributes -for user in $users -do - # Get user id - user_id=$(echo $user | jq -r '.id') - - # Get username - username=$(echo $user | jq -r '.username') - - # Display user information for understanding - echo "Getting user groups for userID=$user_id username=$username" - - # Create a directory for each user - mkdir -p $WORK_DIRECTORY/$username - - # Get full user json excluding the id and timestamp - kcadm.sh get users/$user_id --fields '*(*(*(*(*(*))))),-id,-createdTimestamp' > $WORK_DIRECTORY/$username/user.json - - # If failed to get user, skip the current iteration - if [ $? -ne 0 ]; then - echo "Failed to get the user with ID=$user_id" - ((ERROR_COUNT++)) - continue - fi - - # Get the paths of all groups the current user is a member of and save into separate csv file (space separated) - kcadm.sh get users/$user_id/groups -F path --format CSV > $WORK_DIRECTORY/$username/group_paths.csv - - # If failed, skip the rest, (for now there is no rest) - if [ $? -ne 0 ]; then - echo "Failed to get the groups of the user with ID=$user_id" - ((ERROR_COUNT++)) - continue - fi - - echo "Successfully exported user $username" -done - -echo cleaning up temp files -echo removing $ALL_USERS_FILE -rm -rf $ALL_USERS_FILE - -echo finished the export proccess successfully with $ERROR_COUNT errors \ No newline at end of file diff --git a/bin/user-manager.sh b/bin/user-manager.sh deleted file mode 100755 index 825b450..0000000 --- a/bin/user-manager.sh +++ /dev/null @@ -1,124 +0,0 @@ -# #!/bin/bash - -action="$1" - -case "$action" in - "export") - - export ALL_USERS_FILE=${WORK_DIRECTORY}/EXPORT_SERVER_USERS.json - - # check for missing variables - variables=("EXPORT_KEYCLOAK_SERVER" "EXPORT_REALM" - "EXPORT_CLIENT" "EXPORT_SECRET" - "WORK_DIRECTORY" "ALL_USERS_FILE") - - for var in "${variables[@]}"; do - if [ -z "${!var}" ]; then - echo "Error: $var is not set" - exit 1 - fi - done - - echo "All variables are set" - - - - rm -rf $WORK_DIRECTORY - mkdir $WORK_DIRECTORY - - - # Login to Keycloak EXPORT SERVER - - echo $EXPORT_SECRET|kcadm.sh config credentials --server $EXPORT_KEYCLOAK_SERVER --realm $EXPORT_REALM --client $EXPORT_CLIENT - - - # run the exporter - user-exporter.sh - ;; - "import") - - export ALL_GROUPS_FILE=${WORK_DIRECTORY}/IMPORT_SERVER_GROUPS.json - - # check for missing variables - variables=("IMPORT_KEYCLOAK_SERVER" "IMPORT_REALM" - "IMPORT_CLIENT" "IMPORT_SECRET" - "WORK_DIRECTORY" "ALL_GROUPS_FILE") - - for var in "${variables[@]}"; do - if [ -z "${!var}" ]; then - echo "Error: $var is not set" - exit 1 - fi - done - - echo "All variables are set" - - # Login to Keycloak IMPORT SERVER - echo $IMPORT_SECRET|kcadm.sh config credentials --server $IMPORT_KEYCLOAK_SERVER --realm $IMPORT_REALM --client $IMPORT_CLIENT - - # get all groups of the target import server - kcadm.sh get groups > ${ALL_GROUPS_FILE} - - # run the groups wrapper - groups-ids-wrapper.sh - - # run the importer - user_importer.sh - - ;; - "migrate") - - export ALL_USERS_FILE=${WORK_DIRECTORY}/EXPORT_SERVER_USERS.json - export ALL_GROUPS_FILE=${WORK_DIRECTORY}/IMPORT_SERVER_GROUPS.json - - # check for missing variables - variables=("IMPORT_KEYCLOAK_SERVER" "EXPORT_KEYCLOAK_SERVER" "IMPORT_REALM" "EXPORT_REALM" - "IMPORT_CLIENT" "EXPORT_CLIENT" "IMPORT_SECRET" "EXPORT_SECRET" - "WORK_DIRECTORY" "WORK_DIRECTORY" "ALL_USERS_FILE" "ALL_GROUPS_FILE") - - for var in "${variables[@]}"; do - if [ -z "${!var}" ]; then - echo "Error: $var is not set" - exit 1 - fi - done - - echo "All variables are set" - - - - rm -rf $WORK_DIRECTORY - mkdir $WORK_DIRECTORY - rm -rf $WORK_DIRECTORY - mkdir $WORK_DIRECTORY - - - - - # Login to Keycloak EXPORT SERVER - - echo $EXPORT_SECRET|kcadm.sh config credentials --server $EXPORT_KEYCLOAK_SERVER --realm $EXPORT_REALM --client $EXPORT_CLIENT - - - # run the exporter - user-exporter.sh - - - # Login to Keycloak IMPORT SERVER - echo $IMPORT_SECRET|kcadm.sh config credentials --server $IMPORT_KEYCLOAK_SERVER --realm $IMPORT_REALM --client $IMPORT_CLIENT - - # get all groups of the target import server - kcadm.sh get groups > ${ALL_GROUPS_FILE} - - # run the groups wrapper - groups-ids-wrapper.sh - - # run the importer - user_importer.sh - ;; - *) - # Invalid action - echo "Invalid argument. Please provide 'export', 'import', or 'migrate' as the argument." - exit 1 - ;; -esac diff --git a/bin/user_importer.sh b/bin/user_importer.sh deleted file mode 100755 index 894da23..0000000 --- a/bin/user_importer.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -for USER_DIR in $(find $WORK_DIRECTORY/* -type d) -do - USER_JSON_FILE="${USER_DIR}/USER.json" - GROUP_ID_FILE="${USER_DIR}/GROUP_IDS.csv" - - if [[ -f $USER_JSON_FILE ]] - then - # Create user from user.json file - USER_ID=$(kcadm.sh create users -r $IMPORT_REALM -f $USER_JSON_FILE -i) - if [ $? -ne 0 ]; then - #TO-DO handle when user exists, only join him to groups? - echo "skipping the currently user as it exists , we talk about the dir $USER_JSON_FILE" - continue - fi - if [[ -f $GROUP_ID_FILE ]] - then - # Add user to each group from the group_ids.csv file - while read -r GROUP_ID - do - if [[ ! -z "$GROUP_ID" ]] - then - kcadm.sh update users/$USER_ID/groups/$GROUP_ID -r kcm -s realm=$IMPORT_REALM -s userId=$USER_ID -s groupId=$GROUP_ID -n - fi - done < $GROUP_ID_FILE - else - echo "user $USER_DIR with new ID $USER_ID has no groups to join to" - echo the GROUP FIle is $GROUP_ID_FILE - fi - else - echo "cannot find user file under the path $USER_DIR" - fi -done diff --git a/scripts/Entrypoint.sh b/scripts/Entrypoint.sh new file mode 100644 index 0000000..2372169 --- /dev/null +++ b/scripts/Entrypoint.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# define common functions +log_finish_date() { + # Capture the finish time + local finish_time=$(date +%s) + local finish_date=$(date) + + # Calculate the duration + local duration=$((finish_time - start_time)) + local hours=$((duration / 3600)) + local minutes=$(( (duration % 3600) / 60 )) + local seconds=$((duration % 60)) + + # Echo the results + echo -e "Script duration: $hours hrs $minutes mins $seconds secs.\n\nStart date: $start_date \n\nFinish date: $finish_date" +} + +# Define comman variables +export timestamp=$(date "+%Y.%m.%d-%H.%M.%S") +export PATH=$PATH:/opt/keycloak/bin/ +start_time=$(date +%s) +start_date=$(date) + +if [ -z "$WORK_DIRECTORY" ]; then + export WORK_DIRECTORY="/home/default/kc-exim" +fi + + + + +# Start the proccess + +if [ "$1" = "import" ]; then + user-import.sh 2>&1 | tee "$WORK_DIRECTORY/Logs-import-$IMPORT_REALM-$timestamp" + log_finish_date | tee -a "$WORK_DIRECTORY/Logs-import-$IMPORT_REALM-$timestamp" +elif [ "$1" = "export" ]; then + mkdir -p $WORK_DIRECTORY + user-export.sh 2>&1 | tee "$WORK_DIRECTORY/Logs-export-$EXPORT_REALM-$timestamp" + log_finish_date | tee -a "$WORK_DIRECTORY/Logs-export-$EXPORT_REALM-$timestamp" +else + echo "Invalid argument provided." +fi + diff --git a/scripts/user-export.sh b/scripts/user-export.sh new file mode 100755 index 0000000..269ad1a --- /dev/null +++ b/scripts/user-export.sh @@ -0,0 +1,63 @@ +#!/bin/bash +export USERS_FILE=${WORK_DIRECTORY}/EXPORT_SERVER_USERS.json + +# Define the temporary file for storing updated users +temp_file=$(mktemp) + +# Check for missing variables +variables=("EXPORT_KEYCLOAK_SERVER" "EXPORT_REALM" "EXPORT_TOKEN" "WORK_DIRECTORY" "USERS_FILE") + +for var in "${variables[@]}"; do + if [ -z "${!var}" ]; then + echo "Error: $var is not set" + exit 1 + fi +done + +echo "All variables are set" + +echo "Starting exporter" +ERROR_COUNT=0 +EXPORTED_USERS_COUNT=0 + +# Get users as single JSON +kcadm.sh get users -r "$EXPORT_REALM" -F '*(*(*(*(*(*))))),-createdTimestamp' --realm "$EXPORT_REALM" --server "$EXPORT_KEYCLOAK_SERVER" --token "$EXPORT_TOKEN" --no-config > "$USERS_FILE" + +if [ $? -ne 0 ]; then + echo -e "\e[31mError: Failed to Export Users\e[0m" + exit 1 +fi + +# For each user, get role mappings, group memberships, and user attributes, and append them to the respective user entry +while IFS= read -r user; do + # Get user id and username + user_id=$(echo "$user" | jq -r '.id') + username=$(echo "$user" | jq -r '.username') + + # Get user group paths + REMOTE_GROUPS=$(kcadm.sh get "users/$user_id/groups" -F path --realm "$EXPORT_REALM" --server "$EXPORT_KEYCLOAK_SERVER" --token "$EXPORT_TOKEN" --no-config) + + # If failed to get user groups, skip the current iteration + if [ $? -ne 0 ]; then + echo "Failed to get groups for user with ID=$user_id, leaving groups empty" + ((ERROR_COUNT++)) + continue + fi + + # Turn the user groups into a comma-separated array + GROUPS_ARRAY=$(echo "$REMOTE_GROUPS" | jq -r '.[].path' | jq -sR 'split("\n")[:-1]') + + # Update Single User JSON to include the "groups" key and its value + UPDATED_USER=$(echo "$user" | jq --argjson groups "$GROUPS_ARRAY" 'if has("groups") then .groups = $groups else . + { "groups": $groups } end') + + # Add the updated user to the temporary file + echo "$UPDATED_USER" >> "$temp_file" + + echo "Successfully exported user $username" + ((EXPORTED_USERS_COUNT++)) +done < <(jq -c '.[]' "$USERS_FILE") + +# Move the temporary file to the final output file +jq -s '.' "$temp_file" > "$USERS_FILE" +rm -f $temp_file +echo -e "Export Process Finished \nSuccessfully made Full Export for: $EXPORTED_USERS_COUNT Users.\nPartially exported: $ERROR_COUNT Users.\n" diff --git a/scripts/user-import.sh b/scripts/user-import.sh new file mode 100755 index 0000000..a44c91b --- /dev/null +++ b/scripts/user-import.sh @@ -0,0 +1,34 @@ +#!/bin/bash +export USERS_FILE=${WORK_DIRECTORY}/EXPORT_SERVER_USERS.json + +# check for missing variables +variables=("IMPORT_KEYCLOAK_SERVER" "IMPORT_TOKEN" "WORK_DIRECTORY") + +for var in "${variables[@]}"; do + if [ -z "${!var}" ]; then + echo "Error: $var is not set" + exit 1 + fi +done + +users=$(cat $USERS_FILE | jq -c '.[]') +ERROR_COUNT=0 +IMPORTED_USERS_COUNT=0 +# For each user id&name get groups paths and full user definition relevant attributes +while IFS= read -r user; do + echo + echo "---------------importing user----------------" + echo "${user}" | jq . + echo "${user}" | jq . | kcadm.sh create users -r $IMPORT_REALM --realm $IMPORT_REALM --server $IMPORT_KEYCLOAK_SERVER --token $IMPORT_TOKEN --no-config -f - + if [ $? -ne 0 ]; then + echo "------------Failed To Import------------" + ((ERROR_COUNT++)) + continue + fi + ((IMPORTED_USERS_COUNT++)) + echo "------------successfully imported------------" + echo + echo +done < <(jq -c '.[]' "$USERS_FILE") + +echo -e "\n\nFinished the Import Proccess, Imported:$IMPORTED_USERS_COUNT Users, with $ERROR_COUNT Failed Imports" \ No newline at end of file