From c6eb27e10cdeb11a3b26f8091f2bf56d420bef1a Mon Sep 17 00:00:00 2001 From: sorrel Date: Tue, 31 Mar 2026 12:56:03 -0400 Subject: [PATCH 1/6] implement kcadm and changes to allow kcadm.sh to authenticate in order to run kcadm.sh commands the script must authenticate to the REST API. this commit includes an init_kc command that replaces the bootstrap user with a permanent admin user (whose password is a docker swarm secret) that can be used to authenticate before running kcadm commands. this reuses the secret 'admin_password' that was previously used as the password for the bootstrap admin user. --- .env.sample | 2 ++ abra.sh | 28 ++++++++++++++++++++++++++++ compose.yml | 5 +++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 abra.sh diff --git a/.env.sample b/.env.sample index 53a17a9..c9bb957 100644 --- a/.env.sample +++ b/.env.sample @@ -5,7 +5,9 @@ DOMAIN=keycloak.example.com #EXTRA_DOMAINS=', `www.keycloak.example.com`' LETS_ENCRYPT_ENV=production +BOOTSTRAP_PASSWORD= ADMIN_USERNAME=admin +ADMIN_EMAIL= WELCOME_THEME=keycloak COMPOSE_FILE="compose.yml" diff --git a/abra.sh b/abra.sh new file mode 100644 index 0000000..3317cb4 --- /dev/null +++ b/abra.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +run_kcadm() { + bin/sh -c "/opt/keycloak/bin/kcadm.sh $@" +} + +login_kcadm() { + KC_PW=$(cat /run/secrets/admin_password) + run_kcadm "config credentials --server http://localhost:8080 --realm master --user ${ADMIN_USERNAME} --password ${KC_PW}" +} + +init_kc() { + run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin_bootstrap --password ${BOOTSTRAP_PASSWORD}" + + # CREATE NEW ADMIN USER + ADMIN_PW=$(cat /run/secrets/admin_password) + run_kcadm "create users -r master -s username=${ADMIN_USERNAME} -s email='${ADMIN_EMAIL}' -s emailVerified=true -s enabled=true" + run_kcadm "set-password -r master --username ${ADMIN_USERNAME} --new-password ${ADMIN_PW}" + run_kcadm "add-roles --uusername ${ADMIN_USERNAME} --rolename admin" + + # AUTHENTICATE WITH NEW ADMIN USER + run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin --password ${ADMIN_PW}" + # DEMOTE BOOTSTRAP ADMIN IN CASE WE CAN'T DELETE + run_kcadm "remove-roles -r master --uusername ${KEYCLOAK_ADMIN} --rolename admin --rolename default-roles-master" + # JSON MUNGING + BOOTSTRAP_ID=$(run_kcadm "get users -q username=admin_bootstrap --limit 1 --fields id | grep id | cut -d : -f2 | tr -d [:space:]") + run_kcadm "delete -r master users/${BOOTSTRAP_ID}" +} diff --git a/compose.yml b/compose.yml index 2136ea5..41b037b 100644 --- a/compose.yml +++ b/compose.yml @@ -2,7 +2,7 @@ services: app: image: "keycloak/keycloak:26.5.4" entrypoint: > - bash -c "KEYCLOAK_ADMIN_PASSWORD=\"$$(cat /run/secrets/admin_password)\" KC_DB_PASSWORD=\"$$(cat /run/secrets/db_password)\" /opt/keycloak/bin/kc.sh start" + bash -c "KEYCLOAK_ADMIN_PASSWORD=\"$$BOOTSTRAP_PASSWORD\" KC_DB_PASSWORD=\"$$(cat /run/secrets/db_password)\" /opt/keycloak/bin/kc.sh start" networks: - proxy - internal @@ -16,7 +16,8 @@ services: - KC_HOSTNAME=https://${DOMAIN} - KC_PROXY=edge - KC_SPI_CONNECTIONS_JPA_LEGACY_MIGRATION_STRATEGY=update - - KEYCLOAK_ADMIN=${ADMIN_USERNAME} + # admin_bootstrap will be superceded by $ADMIN_USERNAME on init_kc + - KEYCLOAK_ADMIN=admin_bootstrap - KEYCLOAK_WELCOME_THEME=${WELCOME_THEME} - KC_PROXY_HEADERS=xforwarded - KC_HTTP_ENABLED=true -- 2.49.0 From 1fe630211a398c17282a15453d95325873e8ee69 Mon Sep 17 00:00:00 2001 From: sorrel Date: Wed, 1 Apr 2026 10:54:18 -0400 Subject: [PATCH 2/6] remove password option from kcadm authentication where possible --- abra.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/abra.sh b/abra.sh index 3317cb4..fc17a8e 100644 --- a/abra.sh +++ b/abra.sh @@ -5,21 +5,21 @@ run_kcadm() { } login_kcadm() { - KC_PW=$(cat /run/secrets/admin_password) - run_kcadm "config credentials --server http://localhost:8080 --realm master --user ${ADMIN_USERNAME} --password ${KC_PW}" + export KC_CLI_PASSWORD=$(cat /run/secrets/admin_password) + run_kcadm "config credentials --server http://localhost:8080 --realm master --user ${ADMIN_USERNAME}" } init_kc() { run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin_bootstrap --password ${BOOTSTRAP_PASSWORD}" - # CREATE NEW ADMIN USER ADMIN_PW=$(cat /run/secrets/admin_password) run_kcadm "create users -r master -s username=${ADMIN_USERNAME} -s email='${ADMIN_EMAIL}' -s emailVerified=true -s enabled=true" run_kcadm "set-password -r master --username ${ADMIN_USERNAME} --new-password ${ADMIN_PW}" run_kcadm "add-roles --uusername ${ADMIN_USERNAME} --rolename admin" + export KC_CLI_PASSWORD="$ADMIN_PW" # AUTHENTICATE WITH NEW ADMIN USER - run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin --password ${ADMIN_PW}" + run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin" # DEMOTE BOOTSTRAP ADMIN IN CASE WE CAN'T DELETE run_kcadm "remove-roles -r master --uusername ${KEYCLOAK_ADMIN} --rolename admin --rolename default-roles-master" # JSON MUNGING -- 2.49.0 From a85d4f3b8f7995990eb313e98bb6f86e36e7caeb Mon Sep 17 00:00:00 2001 From: sorrel Date: Wed, 1 Apr 2026 11:27:56 -0400 Subject: [PATCH 3/6] add documentation for admin cli also updates initial setup documentation to reflect admin cli usage in creating permanent admin user. --- .env.sample | 6 +++--- README.md | 25 +++++++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.env.sample b/.env.sample index c9bb957..6ab6a75 100644 --- a/.env.sample +++ b/.env.sample @@ -5,9 +5,9 @@ DOMAIN=keycloak.example.com #EXTRA_DOMAINS=', `www.keycloak.example.com`' LETS_ENCRYPT_ENV=production -BOOTSTRAP_PASSWORD= -ADMIN_USERNAME=admin -ADMIN_EMAIL= +BOOTSTRAP_PASSWORD= # temporary admin password +ADMIN_USERNAME=admin # permanent admin username +ADMIN_EMAIL= # permanent admin email WELCOME_THEME=keycloak COMPOSE_FILE="compose.yml" diff --git a/README.md b/README.md index d9e5eb2..d41a4e4 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,29 @@ 1. Set up Docker Swarm and [`abra`][abra] 2. Deploy [`coop-cloud/traefik`][cc-traefik] 3. `abra app new keycloak --secrets` (optionally with `--pass` if you'd like - to save secrets in `pass`). Make sure to note the `admin_password` which is needed for initial setup. + to save secrets in `pass`). Make sure to note the `admin_password` 4. `abra app config YOURAPPDOMAIN` - be sure to change `$DOMAIN` to something that resolves to your Docker swarm box 5. `abra app deploy YOURAPPDOMAIN` +6. Proceed with replacing the temporary admin user ## Replacing the temporary admin user -When you first deploy Keycloak, you will login in as a temporary admin user with the username "admin" and a random password generated in step 3 above. You need to create a real admin user and delete the temp admin user, because the temp admin user has no 2FA and its password is stored in plain text on the server, which is insecure. Here's how to create the real admin user: +The inital user created by Keycloak, is a bootstrap user whose password is stored in plain text on the server. This recipe assigns that user the name "admin_bootstrap" and the password $BOOTSTRAP_PASSWORD set by `abra app config YOURAPDOMAIN` -1. Click "Users" then "Add user" -2. For "required user actions", I recommend setting "Configure OTP" and "Update Password" to ensure 2FA is enabled. -3. Set a username, then click "Create" -5. Go to the "Role Mapping" tab and click "Assign role" -6. Change the filter from "Filter by clients" to "Filter by realm roles". Select the box for "role_admin" and click "Assign". This makes the user become an admin. -7. Go to the "Credentials" tab. Click "Set password". Set it to something random and save it for the next step. Leave "Temporary" enabled so the user has to change the password on first login. -8. If this is an admin account for you, then log out and back in as the new admin user and complete the password change and OTP steps. If this is an admin account for someone else, securely send the initial username and password to the user. They must complete the password change and OTP setup when they first log in. +Running `abra app command YOURAPPDOMAIN app init_kc` replaces this bootstrap admin with a permanent admin user whose username is $ADMIN_USERNAME and whose password is the secret generated in step 3 above. This will also delete the temporary admin user. -Once at least one real admin user is set up, you should then delete the temporary "admin" user. +It is recommended to also set up MFA for this account from the web admin panel. Log in to the account, select manage account, select account security/signing in, and enable two factor authentication. + +## Running Commands in Keycloak's Admin CLI + +To authenticate a session to Keycloak's admin API run: +`abra app command YOURAPPDOMAIN app login_kcadm` + +After this you can run any Admin CLI command via the run_kcadm command. An example, which creates a "sandbox" realm: +`abra app command YOURAPPDOMAIN app run_kcadm "'create realms -s realm=sandbox -s displayName=sandbox -s enabled=true'"` + +[Keycloak Admin CLI documentation](https://www.keycloak.org/docs/latest/server_admin/index.html#admin-cli) has more info on running kcadm commands ## How do I setup a custom theme? -- 2.49.0 From a6b84ce9b6e25fe492f989a9c2c07f5388508d57 Mon Sep 17 00:00:00 2001 From: sorrel Date: Fri, 3 Apr 2026 12:07:18 -0400 Subject: [PATCH 4/6] prepare kcadm release --- release/next | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 release/next diff --git a/release/next b/release/next new file mode 100644 index 0000000..a18d6f6 --- /dev/null +++ b/release/next @@ -0,0 +1,22 @@ +This release introduces admin cli commands to "abra app command" + +If you are updating from a previous release, please note that the meaning +of the "admin_password" secret is changed to reflect the permanent admin +user's password. To enable "login_kcadm" and "run_kcadm" commands, you +will need to add your permanent admin password to the secret store. + +To increment the secret version: +"abra app config $APP" +change this line in the config file: +SECRET_ADMIN_PASSWORD_VERSION=NEW_VERSION + +To insert your permanent admin password: +"abra app secret insert $APP SECRET_ADMIN_PASSWORD_VERSION \ + $NEW_VERSION $ADMIN_PASSWORD" + +See here for more on rotating secrets: +https://docs.coopcloud.tech/operators/handbook/#rotating-a-secret + +After redeploying, ensure that you are able to authenticate the admin +CLI by running: +"abra app command $APP app login_kcadm" \ No newline at end of file -- 2.49.0 From 44529dc36a49fbac0dbb114878250a52f7de2466 Mon Sep 17 00:00:00 2001 From: sorrel Date: Fri, 10 Apr 2026 12:11:03 -0400 Subject: [PATCH 5/6] add bootstrap password to secrets also changes environment variables to reflect KC_BOOTSTRAP values for temporary bootstrap admin user --- .env.sample | 7 ++++--- abra.sh | 9 +++++---- compose.yml | 8 ++++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.env.sample b/.env.sample index 6ab6a75..84a4915 100644 --- a/.env.sample +++ b/.env.sample @@ -5,9 +5,9 @@ DOMAIN=keycloak.example.com #EXTRA_DOMAINS=', `www.keycloak.example.com`' LETS_ENCRYPT_ENV=production -BOOTSTRAP_PASSWORD= # temporary admin password -ADMIN_USERNAME=admin # permanent admin username -ADMIN_EMAIL= # permanent admin email +# ADMIN_USERNAME and _EMAIL are for permanent admin user +ADMIN_USERNAME=admin +ADMIN_EMAIL= WELCOME_THEME=keycloak COMPOSE_FILE="compose.yml" @@ -15,6 +15,7 @@ COMPOSE_FILE="compose.yml" SECRET_DB_ROOT_PASSWORD_VERSION=v1 SECRET_DB_PASSWORD_VERSION=v1 SECRET_ADMIN_PASSWORD_VERSION=v1 +SECRET_BOOTSTRAP_PASSWORD_VERSION=v1 # Enable persistent theme volume, if you want to apply a custom theme #COMPOSE_FILE="$COMPOSE_FILE:compose.theme.yml" diff --git a/abra.sh b/abra.sh index fc17a8e..17e95d6 100644 --- a/abra.sh +++ b/abra.sh @@ -10,18 +10,19 @@ login_kcadm() { } init_kc() { - run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin_bootstrap --password ${BOOTSTRAP_PASSWORD}" + BOOTSTRAP_PW=$(cat /run/secrets/bootstrap_password) + run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin_bootstrap --password ${BOOTSTRAP_PW}" # CREATE NEW ADMIN USER ADMIN_PW=$(cat /run/secrets/admin_password) run_kcadm "create users -r master -s username=${ADMIN_USERNAME} -s email='${ADMIN_EMAIL}' -s emailVerified=true -s enabled=true" run_kcadm "set-password -r master --username ${ADMIN_USERNAME} --new-password ${ADMIN_PW}" - run_kcadm "add-roles --uusername ${ADMIN_USERNAME} --rolename admin" + run_kcadm "add-roles --uusername ${ADMIN_USERNAME} --rolename admin --rolename default-roles-master" export KC_CLI_PASSWORD="$ADMIN_PW" # AUTHENTICATE WITH NEW ADMIN USER - run_kcadm "config credentials --server http://localhost:8080 --realm master --user admin" + run_kcadm "config credentials --server http://localhost:8080 --realm master --user ${ADMIN_USERNAME}" # DEMOTE BOOTSTRAP ADMIN IN CASE WE CAN'T DELETE - run_kcadm "remove-roles -r master --uusername ${KEYCLOAK_ADMIN} --rolename admin --rolename default-roles-master" + run_kcadm "remove-roles -r master --uusername admin_bootstrap --rolename admin --rolename default-roles-master" # JSON MUNGING BOOTSTRAP_ID=$(run_kcadm "get users -q username=admin_bootstrap --limit 1 --fields id | grep id | cut -d : -f2 | tr -d [:space:]") run_kcadm "delete -r master users/${BOOTSTRAP_ID}" diff --git a/compose.yml b/compose.yml index 41b037b..c2981ed 100644 --- a/compose.yml +++ b/compose.yml @@ -2,12 +2,13 @@ services: app: image: "keycloak/keycloak:26.5.4" entrypoint: > - bash -c "KEYCLOAK_ADMIN_PASSWORD=\"$$BOOTSTRAP_PASSWORD\" KC_DB_PASSWORD=\"$$(cat /run/secrets/db_password)\" /opt/keycloak/bin/kc.sh start" + bash -c "KC_BOOTSTRAP_ADMIN_PASSWORD=\"$$(cat run/secrets/bootstrap_password)\" KC_DB_PASSWORD=\"$$(cat /run/secrets/db_password)\" /opt/keycloak/bin/kc.sh start" networks: - proxy - internal secrets: - admin_password + - bootstrap_password - db_password environment: - KC_DB=mariadb @@ -17,7 +18,7 @@ services: - KC_PROXY=edge - KC_SPI_CONNECTIONS_JPA_LEGACY_MIGRATION_STRATEGY=update # admin_bootstrap will be superceded by $ADMIN_USERNAME on init_kc - - KEYCLOAK_ADMIN=admin_bootstrap + - KC_BOOTSTRAP_ADMIN_USERNAME=admin_bootstrap - KEYCLOAK_WELCOME_THEME=${WELCOME_THEME} - KC_PROXY_HEADERS=xforwarded - KC_HTTP_ENABLED=true @@ -82,6 +83,9 @@ secrets: admin_password: name: ${STACK_NAME}_admin_password_${SECRET_ADMIN_PASSWORD_VERSION} external: true + bootstrap_password: + name: ${STACK_NAME}_bootstrap_password_${SECRET_BOOTSTRAP_PASSWORD_VERSION} + external: true db_password: name: ${STACK_NAME}_db_password_${SECRET_DB_PASSWORD_VERSION} external: true -- 2.49.0 From 9555bc7980c19715b26189440df6262b778b8404 Mon Sep 17 00:00:00 2001 From: sorrel Date: Fri, 10 Apr 2026 14:52:22 -0400 Subject: [PATCH 6/6] add bootstrap password generation to release note --- release/next | 3 +++ 1 file changed, 3 insertions(+) diff --git a/release/next b/release/next index a18d6f6..5370e37 100644 --- a/release/next +++ b/release/next @@ -14,6 +14,9 @@ To insert your permanent admin password: "abra app secret insert $APP SECRET_ADMIN_PASSWORD_VERSION \ $NEW_VERSION $ADMIN_PASSWORD" +You will also need to generate a bootstrap password (this will not be used) +"abra app secret generate $APP bootstrap_password v1 + See here for more on rotating secrets: https://docs.coopcloud.tech/operators/handbook/#rotating-a-secret -- 2.49.0