Files
computer-playbook/roles/web-app-keycloak/files/shell/ensure_realm_user.sh
kevinveenbirkenbachandClaude Opus 5 b0ca8784e2 fix(mattermost): take the administrator uid from the inventory, not from LDAP
The OIDC admin wiring is gated on SSO alone, and SSO is 'web-app-keycloak' in group_names. Round 1 takes keycloak variant 1, which pins ldap off, so svc-db-openldap is not in the closure; the block still resolved its swarm service and the waiter spent 600 attempts on a service that does not exist (run 33698844864, Mattermost#1, 25 minutes). In compose the same block would have died on a missing container, so the defect is mode-independent.

The number it fetched is already in the registry: svc-db-openldap/tasks/04_users.yml:118 writes uidNumber from the same lookup('users'), and an earlier run shows inventory uid 1001 against ldapsearch 1001. Reading it directly removes the cross-stack exec entirely.

Keycloak's mattermost-id mapper reads the uidNumber attribute, which until now only the LDAP federation wrote, so the same round would have produced no id claim to match. ensure_realm_user.sh now mirrors the uid into that attribute whenever it is passed one, leaving callers without a uid unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 12:05:09 +02:00

105 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# Ensure a Keycloak realm user exists with the desired profile/password.
#
# Runs on the host and executes kcadm inside the Keycloak container.
#
# REQUIRED environment variables:
# KEYCLOAK_EXEC_CONTAINER
# KEYCLOAK_KCADM
# KEYCLOAK_REALM
# KEYCLOAK_USERNAME
# KEYCLOAK_PASSWORD
#
# OPTIONAL:
# KEYCLOAK_EMAIL
# KEYCLOAK_FIRSTNAME
# KEYCLOAK_LASTNAME
# KEYCLOAK_USER_ENABLED true|false (default: true)
# KEYCLOAK_UIDNUMBER posix uid, mirrored into the uidNumber attribute
###############################################################################
: "${KEYCLOAK_EXEC_CONTAINER:?missing KEYCLOAK_EXEC_CONTAINER}"
: "${KEYCLOAK_KCADM:?missing KEYCLOAK_KCADM}"
: "${KEYCLOAK_REALM:?missing KEYCLOAK_REALM}"
: "${KEYCLOAK_USERNAME:?missing KEYCLOAK_USERNAME}"
: "${KEYCLOAK_PASSWORD:?missing KEYCLOAK_PASSWORD}"
KEYCLOAK_EMAIL="${KEYCLOAK_EMAIL:-}"
KEYCLOAK_FIRSTNAME="${KEYCLOAK_FIRSTNAME:-}"
KEYCLOAK_LASTNAME="${KEYCLOAK_LASTNAME:-}"
KEYCLOAK_USER_ENABLED="${KEYCLOAK_USER_ENABLED:-true}"
KEYCLOAK_UIDNUMBER="${KEYCLOAK_UIDNUMBER:-}"
# shellcheck disable=SC2016 # Inner $vars are intentionally expanded by the container shell, not the host.
${KEYCLOAK_EXEC_CONTAINER} sh -lc '
set -euo pipefail
USERNAME="$1"
PASSWORD="$2"
REALM="$3"
EMAIL="$4"
FIRSTNAME="$5"
LASTNAME="$6"
ENABLED="$7"
KCADM="$8"
UIDNUMBER="$9"
RAW="$($KCADM get users -r "$REALM" -q username="$USERNAME" --fields id --format csv --noquotes 2>&1 || true)"
USER_ID="$(printf "%s\n" "$RAW" \
| grep -Eio "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" \
| head -n1 || true)"
CREATED=false
if [ -z "${USER_ID:-}" ]; then
$KCADM create users -r "$REALM" \
-s "username=$USERNAME" \
-s "enabled=$ENABLED" \
-s "email=$EMAIL" \
-s "firstName=$FIRSTNAME" \
-s "lastName=$LASTNAME"
CREATED=true
RAW="$($KCADM get users -r "$REALM" -q username="$USERNAME" --fields id --format csv --noquotes 2>&1 || true)"
USER_ID="$(printf "%s\n" "$RAW" \
| grep -Eio "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" \
| head -n1 || true)"
fi
if [ -z "${USER_ID:-}" ]; then
echo "[keycloak][user] failed to resolve user id for $USERNAME in realm $REALM" >&2
exit 1
fi
$KCADM update users/$USER_ID -r "$REALM" \
-s "enabled=$ENABLED" \
-s "email=$EMAIL" \
-s "firstName=$FIRSTNAME" \
-s "lastName=$LASTNAME"
if [ -n "$UIDNUMBER" ]; then
$KCADM update users/$USER_ID -r "$REALM" -s "attributes.uidNumber=$UIDNUMBER"
fi
$KCADM set-password -r "$REALM" \
--username "$USERNAME" \
--new-password "$PASSWORD"
if [ "$CREATED" = true ]; then
echo "[keycloak][user] created: $USERNAME ($USER_ID)"
else
echo "[keycloak][user] updated: $USERNAME ($USER_ID)"
fi
' sh \
"${KEYCLOAK_USERNAME}" \
"${KEYCLOAK_PASSWORD}" \
"${KEYCLOAK_REALM}" \
"${KEYCLOAK_EMAIL}" \
"${KEYCLOAK_FIRSTNAME}" \
"${KEYCLOAK_LASTNAME}" \
"${KEYCLOAK_USER_ENABLED}" \
"${KEYCLOAK_KCADM}" \
"${KEYCLOAK_UIDNUMBER}"