generated from coulomb/repo-seed
feat(lldap): add create-user.sh for user provisioning
Creates a user in LLDAP via GraphQL, adds them to net-kingdom-users, optionally net-kingdom-admins (--admin flag), and sets a password interactively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
159
sso-mfa/k8s/lldap/create-user.sh
Executable file
159
sso-mfa/k8s/lldap/create-user.sh
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
# create-user.sh — create a user in LLDAP and add them to net-kingdom-users
|
||||
#
|
||||
# Usage:
|
||||
# ./create-user.sh <username> <email> [display-name] [lldap-url] [secrets-dir]
|
||||
#
|
||||
# <username> LDAP uid — e.g. "bernd" or "testuser"
|
||||
# <email> e.g. "bernd@coulomb.social"
|
||||
# <display-name> defaults to <username>
|
||||
# <lldap-url> default: https://lldap.coulomb.social
|
||||
# <secrets-dir> default: ../../bootstrap/secrets
|
||||
#
|
||||
# The user is created with no password. They must set one via:
|
||||
# https://lldap.coulomb.social (admin sets password in the WebUI), or
|
||||
# the user resets it themselves if self-service password reset is configured.
|
||||
#
|
||||
# Add --admin as the last argument to also add to net-kingdom-admins.
|
||||
#
|
||||
# Examples:
|
||||
# ./create-user.sh testuser testuser@coulomb.social
|
||||
# ./create-user.sh bernd bernd@coulomb.social "Bernd W" --admin
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
USERNAME="${1:-}"
|
||||
EMAIL="${2:-}"
|
||||
DISPLAY_NAME="${3:-$USERNAME}"
|
||||
LLDAP_URL="${4:-https://lldap.coulomb.social}"
|
||||
SECRETS_DIR="${5:-../../bootstrap/secrets}"
|
||||
ADMIN_FLAG="${6:-}"
|
||||
|
||||
# Allow --admin anywhere after the first two args
|
||||
for arg in "$@"; do
|
||||
[[ "$arg" == "--admin" ]] && ADMIN_FLAG="yes"
|
||||
done
|
||||
|
||||
if [[ -z "$USERNAME" || -z "$EMAIL" ]]; then
|
||||
echo "Usage: $0 <username> <email> [display-name] [lldap-url] [secrets-dir] [--admin]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LLDAP_ENV="$SECRETS_DIR/lldap/secrets.env"
|
||||
if [[ ! -f "$LLDAP_ENV" ]]; then
|
||||
echo "ERROR: $LLDAP_ENV not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_env() { bash -c "source '$1' 2>/dev/null; echo \${$2}"; }
|
||||
LLDAP_ADMIN_PASS=$(read_env "$LLDAP_ENV" LLDAP_LDAP_USER_PASS)
|
||||
|
||||
# ── Authenticate ──────────────────────────────────────────────────────────────
|
||||
echo "Authenticating to LLDAP at $LLDAP_URL ..."
|
||||
LLDAP_TOKEN=$(curl -sf -X POST "$LLDAP_URL/auth/simple/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"admin\",\"password\":\"$LLDAP_ADMIN_PASS\"}" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
|
||||
|
||||
gql() {
|
||||
local query="$1"; local vars="${2:-{}}"
|
||||
local body
|
||||
body=$(python3 -c "
|
||||
import json, sys
|
||||
print(json.dumps({'query': sys.argv[1], 'variables': json.loads(sys.argv[2])}))
|
||||
" "$query" "$vars")
|
||||
curl -sf -X POST "$LLDAP_URL/api/graphql" \
|
||||
-H "Authorization: Bearer $LLDAP_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body"
|
||||
}
|
||||
|
||||
# ── Create user ───────────────────────────────────────────────────────────────
|
||||
echo "Creating user '$USERNAME' ($EMAIL) ..."
|
||||
CREATE_RESP=$(gql \
|
||||
'mutation CreateUser($id: String!, $email: String!, $display: String!) {
|
||||
createUser(user: {id: $id, email: $email, displayName: $display}) {
|
||||
id email displayName
|
||||
}
|
||||
}' \
|
||||
"$(python3 -c "import json; print(json.dumps({'id':'$USERNAME','email':'$EMAIL','display':'$DISPLAY_NAME'}))")")
|
||||
|
||||
ERR=$(echo "$CREATE_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 "parse error")
|
||||
|
||||
if [[ -n "$ERR" ]]; then
|
||||
echo " ERROR: $ERR" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " User '$USERNAME' created."
|
||||
|
||||
# ── Look up group IDs ─────────────────────────────────────────────────────────
|
||||
GROUPS_RESP=$(gql 'query { groups { id displayName } }')
|
||||
get_group_id() {
|
||||
echo "$GROUPS_RESP" | python3 -c \
|
||||
"import sys,json; d=json.load(sys.stdin); grps=d.get('data',{}).get('groups',[]); matches=[g['id'] for g in grps if g['displayName']=='$1']; print(matches[0] if matches else '')"
|
||||
}
|
||||
|
||||
USERS_GID=$(get_group_id "net-kingdom-users")
|
||||
ADMINS_GID=$(get_group_id "net-kingdom-admins")
|
||||
|
||||
if [[ -z "$USERS_GID" ]]; then
|
||||
echo " ERROR: group 'net-kingdom-users' not found — run bootstrap-users.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Add to net-kingdom-users ──────────────────────────────────────────────────
|
||||
echo "Adding '$USERNAME' to net-kingdom-users (id=$USERS_GID) ..."
|
||||
ADD_RESP=$(gql \
|
||||
'mutation AddToGroup($uid: String!, $gid: Int!) { addUserToGroup(userId: $uid, groupId: $gid) { ok } }' \
|
||||
"{\"uid\":\"$USERNAME\",\"gid\":$USERS_GID}")
|
||||
ERR=$(echo "$ADD_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: $ERR" || echo " Added to net-kingdom-users."
|
||||
|
||||
# ── Add to net-kingdom-admins (optional) ─────────────────────────────────────
|
||||
if [[ -n "$ADMIN_FLAG" ]]; then
|
||||
if [[ -z "$ADMINS_GID" ]]; then
|
||||
echo " WARNING: group 'net-kingdom-admins' not found — skipping admin group." >&2
|
||||
else
|
||||
echo "Adding '$USERNAME' to net-kingdom-admins (id=$ADMINS_GID) ..."
|
||||
ADD_RESP=$(gql \
|
||||
'mutation AddToGroup($uid: String!, $gid: Int!) { addUserToGroup(userId: $uid, groupId: $gid) { ok } }' \
|
||||
"{\"uid\":\"$USERNAME\",\"gid\":$ADMINS_GID}")
|
||||
ERR=$(echo "$ADD_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: $ERR" || echo " Added to net-kingdom-admins."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Set password ──────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "Setting password for '$USERNAME' ..."
|
||||
read -r -s -p " Enter password (leave blank to skip): " USER_PASS
|
||||
echo ""
|
||||
|
||||
if [[ -n "$USER_PASS" ]]; then
|
||||
PASS_RESP=$(gql \
|
||||
'mutation SetPass($uid: String!, $pw: String!) { resetUserPasswordFromAdmin(userId: $uid, password: $pw) }' \
|
||||
"$(python3 -c "import json; print(json.dumps({'uid':'$USERNAME','pw':'$USER_PASS'}))")")
|
||||
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."
|
||||
else
|
||||
echo " Skipped — set password via $LLDAP_URL as admin."
|
||||
fi
|
||||
|
||||
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " User '$USERNAME' created and added to net-kingdom-users."
|
||||
[[ -n "$ADMIN_FLAG" ]] && echo " Also added to net-kingdom-admins."
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. User self-enrolls TOTP at https://pink-account.coulomb.social"
|
||||
echo " 2. Verify user appears in privacyIDEA: GET /user/?realm=coulomb&username=$USERNAME"
|
||||
Reference in New Issue
Block a user