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>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-09-03 12:05:09 +02:00
1 parent 52e755ef3c
commit b0ca8784e2
4 files changed
+11 -26

No files matched your search

@@ -18,6 +18,7 @@ set -euo pipefail
# 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}"
@@ -30,6 +31,7 @@ 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 '
@@ -43,6 +45,7 @@ ${KEYCLOAK_EXEC_CONTAINER} sh -lc '
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" \
@@ -76,6 +79,10 @@ ${KEYCLOAK_EXEC_CONTAINER} sh -lc '
-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"
@@ -93,4 +100,5 @@ ${KEYCLOAK_EXEC_CONTAINER} sh -lc '
"${KEYCLOAK_FIRSTNAME}" \
"${KEYCLOAK_LASTNAME}" \
"${KEYCLOAK_USER_ENABLED}" \
"${KEYCLOAK_KCADM}"
"${KEYCLOAK_KCADM}" \
"${KEYCLOAK_UIDNUMBER}"
@@ -17,6 +17,7 @@
KEYCLOAK_FIRSTNAME: "{{ item.value.firstname | default('') }}"
KEYCLOAK_LASTNAME: "{{ item.value.lastname | default('') }}"
KEYCLOAK_USER_ENABLED: "true"
KEYCLOAK_UIDNUMBER: "{{ item.value.uid }}"
loop: >-
{{
lookup('users_non_reserved')
@@ -13,33 +13,11 @@
- when: (mm_admin_authservice.rows[0][0] | default('') | trim) != 'gitlab'
block:
- name: "🔍 Resolve openldap hosting node + container id"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: openldap
resolve_service_name: "{{ lookup('container_service', 'svc-db-openldap', 'openldap') }}"
resolve_output_prefix: MATTERMOST_LDAP
resolve_compose_cid: "{{ MATTERMOST_LDAP_CONTAINER_ADDRESS }}"
- name: "🗂️ Mattermost | Get administrator uidNumber from LDAP"
ansible.builtin.shell: | # nocheck: container-exec-resolver MATTERMOST_LDAP_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell
container exec -i {{ MATTERMOST_LDAP_LOCAL_CID | quote }} \
ldapsearch -x -H ldap://localhost:389 \
-D {{ LDAP.DN.ADMINISTRATOR.DATA | quote }} \
-w {{ LDAP.BIND_CREDENTIAL | quote }} \
-b {{ LDAP.DN.OU.USERS | quote }} \
"(uid={{ lookup('users', 'administrator').username }})" uidNumber \
| grep '^uidNumber:' | awk '{print $2}'
register: mm_admin_uidnumber
changed_when: false
failed_when: mm_admin_uidnumber.rc != 0 or mm_admin_uidnumber.stdout | trim == ''
delegate_to: "{{ MATTERMOST_LDAP_HOST_NODE | default(omit) }}"
- name: "🔄 Mattermost | Convert administrator to gitlab SSO auth"
database_query:
config: "{{ lookup('database', application_id) }}"
query_file: "{{ role_path }}/files/sql/update/user-to-gitlab-auth.sql"
named_args:
authdata: "{{ mm_admin_uidnumber.stdout | trim }}"
authdata: "{{ lookup('users', 'administrator').uid }}"
email: "{{ lookup('users', 'administrator').email }}"
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
-2
View File
@@ -53,8 +53,6 @@ MATTERMOST_KEYCLOAK_CONTAINER_ADDRESS: "{{ lookup('container_address', 'web-app-
MATTERMOST_KEYCLOAK_ADMIN_USER: "{{ lookup('config', 'web-app-keycloak', 'services.keycloak.accounts.system.username') }}"
MATTERMOST_KEYCLOAK_ADMIN_PASS: "{{ lookup('users', 'administrator').password }}"
MATTERMOST_LDAP_CONTAINER_ADDRESS: "{{ lookup('container_address', 'svc-db-openldap', 'openldap') }}"
MATTERMOST_MCP_CONTAINER: "{{ lookup('config', application_id, 'services.mattermostmcp.name') }}"
MATTERMOST_MCP_PORT: "{{ lookup('config', application_id, 'services.mattermostmcp.ports.internal.http') }}"
MATTERMOST_MCP_INSTANCE_DIR: "{{ [lookup('container', application_id, 'directories.instance'), 'mcp'] | path_join }}"