#!/usr/bin/env bash # Author: Marcel Herrguth, Claude Code set -euo pipefail usage() { cat < [options] Actions: create-ro Create a read-only user (and a matching role) create-rw Create a read-write user (and a matching role) remove-custom List and remove all non-default (non-reserved) users Options: -u Elasticsearch URL (default: https://127.0.0.1:9200) -U Admin username to authenticate with (default: elastic) -p Admin password (required, or set ES_PASSWORD env var) -k Allow insecure TLS (skip cert verification) -n New username (required for create-ro / create-rw) -w New user's password (required for create-ro / create-rw, or set ES_NEW_PASSWORD) -i Restrict access to indices matching "*" (default: "*", all indices) Examples: $0 create-ro -U elastic -p "\$ES_PASSWORD" -n zammad_reader -w "\$NEW_PW" -i zammad_production -k $0 create-rw -U elastic -p "\$ES_PASSWORD" -n zammad_writer -w "\$NEW_PW" -i zammad_production -k $0 remove-custom -U elastic -p "\$ES_PASSWORD" -k EOF exit 1 } [[ $# -ge 1 ]] || usage action="$1"; shift url="https://127.0.0.1:9200" admin_user="elastic" admin_password="${ES_PASSWORD:-}" insecure=() new_user="" new_password="${ES_NEW_PASSWORD:-}" index_pattern="*" while getopts "u:U:p:kn:w:i:h" opt; do case "$opt" in u) url="$OPTARG" ;; U) admin_user="$OPTARG" ;; p) admin_password="$OPTARG" ;; k) insecure=(--insecure) ;; n) new_user="$OPTARG" ;; w) new_password="$OPTARG" ;; i) index_pattern="${OPTARG}*" ;; h|*) usage ;; esac done [[ -n "$admin_password" ]] || { echo "Admin password required (-p or ES_PASSWORD)"; usage; } CURL=(curl "${insecure[@]}" -s -u "${admin_user}:${admin_password}") create_user() { local role_name="$1" privileges_json="$2" [[ -n "$new_user" ]] || { echo "New username required (-n)"; usage; } [[ -n "$new_password" ]] || { echo "New user's password required (-w or ES_NEW_PASSWORD)"; usage; } role_body=$(jq -n --argjson privileges "$privileges_json" --arg pattern "$index_pattern" \ '{indices: [{names: [$pattern], privileges: $privileges}]}') role_resp=$("${CURL[@]}" -X PUT "${url}/_security/role/${role_name}" -H 'Content-Type: application/json' -d "$role_body") echo "role ${role_name}: $(echo "$role_resp" | jq -c .)" user_body=$(jq -n --arg password "$new_password" --arg role "$role_name" \ '{password: $password, roles: [$role]}') user_resp=$("${CURL[@]}" -X PUT "${url}/_security/user/${new_user}" -H 'Content-Type: application/json' -d "$user_body") echo "user ${new_user}: $(echo "$user_resp" | jq -c .)" } case "$action" in create-ro) create_user "${new_user}_ro" '["read","view_index_metadata"]' echo "Created read-only user '${new_user}', scoped to indices matching '${index_pattern}'." ;; create-rw) create_user "${new_user}_rw" '["read","write","create_index","view_index_metadata"]' echo "Created read-write user '${new_user}', scoped to indices matching '${index_pattern}'." ;; remove-custom) users=$("${CURL[@]}" "${url}/_security/user" | jq -r 'to_entries[] | select(.value.metadata._reserved != true) | .key') if [[ -z "$users" ]]; then echo "No non-default users found." exit 0 fi echo "Non-default users found:" echo "$users" | sed 's/^/ - /' read -rp "Delete all of the above? [y/N] " ok [[ "$ok" == "y" ]] || exit 1 for u in $users; do "${CURL[@]}" -X DELETE "${url}/_security/user/${u}" | jq -c --arg u "$u" '{user: $u, result: .}' done ;; *) usage ;; esac