From 331eeaf378ed0a5eadea81113a4e74ccb91debbc Mon Sep 17 00:00:00 2001 From: Bernd Worsch Date: Wed, 25 Mar 2026 02:42:15 +0000 Subject: [PATCH] fix(lldap): fix gql() brace bug + use LDAP for password setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes: 1. gql() default vars '${2:-{}}' — bash parsed first '}' as closing the parameter expansion, appending a stray '}' to every caller's vars. Fixed by storing '{}' in a local variable first. 2. make_vars() — add VAR_INT_KEYS support so groupId is emitted as a JSON integer (Int!) rather than a string, matching LLDAP's schema. 3. Password setting — LLDAP has no GraphQL mutation for admin password reset. Replace the broken resetUserPasswordFromAdmin mutation with an RFC 3062 LDAP Password Modify operation via kubectl port-forward to the in-cluster LLDAP service, using ldap3. Co-Authored-By: Claude Sonnet 4.6 --- sso-mfa/k8s/lldap/create-user.sh | 70 ++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/sso-mfa/k8s/lldap/create-user.sh b/sso-mfa/k8s/lldap/create-user.sh index 3cd4578..2fd8153 100755 --- a/sso-mfa/k8s/lldap/create-user.sh +++ b/sso-mfa/k8s/lldap/create-user.sh @@ -65,7 +65,8 @@ LLDAP_TOKEN=$(curl -sf -X POST "$LLDAP_URL/auth/simple/login" \ # with special characters (spaces, quotes) in display names or emails. gql() { local query="$1" - local vars="${2:-{}}" + local _empty="{}" + local vars="${2:-$_empty}" local body body=$(GQL_QUERY="$query" GQL_VARS="$vars" python3 -c " import json, os @@ -80,14 +81,17 @@ print(json.dumps({ -d "$body" } -# Build variables JSON safely via env vars +# Build variables JSON safely via env vars. +# Set VAR_INT_KEYS to a comma-separated list of keys that should be JSON integers. make_vars() { python3 -c " import json, os d = {} +int_keys = set(k for k in os.environ.get('VAR_INT_KEYS','').split(',') if k) for k in os.environ.get('VAR_KEYS','').split(','): if k: - d[k] = os.environ.get('VAR_' + k, '') + v = os.environ.get('VAR_' + k, '') + d[k] = int(v) if k in int_keys else v print(json.dumps(d)) " } @@ -142,7 +146,7 @@ fi # ── Add to net-kingdom-users ────────────────────────────────────────────────── echo "Adding '$USERNAME' to net-kingdom-users (id=$USERS_GID) ..." -VARS=$(VAR_KEYS="uid,gid" VAR_uid="$USERNAME" VAR_gid="$USERS_GID" make_vars) +VARS=$(VAR_KEYS="uid,gid" VAR_INT_KEYS="gid" VAR_uid="$USERNAME" VAR_gid="$USERS_GID" make_vars) ADD_RESP=$(gql \ 'mutation AddToGroup($uid: String!, $gid: Int!) { addUserToGroup(userId: $uid, groupId: $gid) { ok } }' \ "$VARS") @@ -157,7 +161,7 @@ if [[ -n "$ADMIN_FLAG" ]]; then echo " WARNING: group 'net-kingdom-admins' not found — skipping." >&2 else echo "Adding '$USERNAME' to net-kingdom-admins (id=$ADMINS_GID) ..." - VARS=$(VAR_KEYS="uid,gid" VAR_uid="$USERNAME" VAR_gid="$ADMINS_GID" make_vars) + VARS=$(VAR_KEYS="uid,gid" VAR_INT_KEYS="gid" VAR_uid="$USERNAME" VAR_gid="$ADMINS_GID" make_vars) ADD_RESP=$(gql \ 'mutation AddToGroup($uid: String!, $gid: Int!) { addUserToGroup(userId: $uid, groupId: $gid) { ok } }' \ "$VARS") @@ -181,14 +185,54 @@ else fi if [[ -n "$USER_PASS" ]]; then - VARS=$(VAR_KEYS="uid,pw" VAR_uid="$USERNAME" VAR_pw="$USER_PASS" make_vars) - PASS_RESP=$(gql \ - 'mutation SetPass($uid: String!, $pw: String!) { resetUserPasswordFromAdmin(userId: $uid, password: $pw) }' \ - "$VARS") - ERR=$(echo "$PASS_RESP" | python3 -c \ - "import sys,json; d=json.load(sys.stdin); errs=d.get('errors',[]); print(errs[0]['message'] if errs else '')" \ - 2>/dev/null || echo "") - [[ -n "$ERR" ]] && echo " WARNING: password not set — $ERR" || echo " Password set." + # LLDAP has no GraphQL mutation for password setting. + # Use RFC 3062 LDAP Password Modify extended operation via a kubectl + # port-forward to the in-cluster LLDAP LDAP port (ClusterIP only). + LLDAP_BIND_DN="uid=admin,ou=people,dc=netkingdom,dc=local" + TARGET_DN="uid=$USERNAME,ou=people,dc=netkingdom,dc=local" + LOCAL_PORT=13891 # avoid conflict with other forwarders + + echo " Opening kubectl port-forward to lldap:3890 ..." + KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config-railiance01}" \ + kubectl port-forward svc/lldap "$LOCAL_PORT:3890" -n sso &>/dev/null & + PF_PID=$! + # Wait for the forwarder to be ready (up to 10s) + for i in $(seq 1 10); do + nc -z 127.0.0.1 "$LOCAL_PORT" 2>/dev/null && break + sleep 1 + done + + PASS_ERR=$(LLDAP_USER_PASS="$LLDAP_ADMIN_PASS" \ + LLDAP_BIND_DN_VAR="$LLDAP_BIND_DN" \ + TARGET_DN_VAR="$TARGET_DN" \ + NEW_PASS="$USER_PASS" \ + LOCAL_PORT_VAR="$LOCAL_PORT" \ + python3 -c " +import os, sys +try: + from ldap3 import Server, Connection + s = Server('127.0.0.1', port=int(os.environ['LOCAL_PORT_VAR'])) + c = Connection(s, user=os.environ['LLDAP_BIND_DN_VAR'], password=os.environ['LLDAP_USER_PASS']) + c.bind() + c.extend.standard.modify_password( + user=os.environ['TARGET_DN_VAR'], + new_password=os.environ['NEW_PASS']) + if c.result['result'] != 0: + print(c.result['description'], file=sys.stderr) + sys.exit(1) + c.unbind() +except ImportError: + print('ldap3 not installed — install with: pip install ldap3 --break-system-packages', file=sys.stderr) + sys.exit(1) +" 2>&1) + kill "$PF_PID" 2>/dev/null; wait "$PF_PID" 2>/dev/null || true + + if [[ -n "$PASS_ERR" ]]; then + echo " WARNING: password not set — $PASS_ERR" >&2 + echo " Set manually via the LLDAP WebUI at $LLDAP_URL" + else + echo " Password set." + fi else echo " Skipped — set password via $LLDAP_URL as admin." fi