#!/usr/bin/env bash # Sync Keycloak users into Jenkins local accounts (for CLI/API use). # Jenkins is OIDC-enabled, but local users can still be provisioned for tokens. # Requires: curl, jq, kubectl. Needs Jenkins admin user+API token. set -euo pipefail require() { command -v "$1" >/dev/null 2>&1 || { echo "missing required binary: $1" >&2; exit 1; }; } require curl; require jq; require kubectl : "${KEYCLOAK_URL:=https://sso.bstein.dev}" : "${KEYCLOAK_REALM:=atlas}" : "${KEYCLOAK_CLIENT_ID:?set KEYCLOAK_CLIENT_ID or export via secret}" : "${KEYCLOAK_CLIENT_SECRET:?set KEYCLOAK_CLIENT_SECRET or export via secret}" : "${JENKINS_URL:=https://ci.bstein.dev}" : "${JENKINS_NAMESPACE:=jenkins}" : "${JENKINS_ADMIN_SECRET_NAME:=jenkins-admin-token}" : "${JENKINS_ADMIN_USER_KEY:=username}" : "${JENKINS_ADMIN_TOKEN_KEY:=token}" : "${DEFAULT_PASSWORD:=TempSsoPass!2025}" fetch_token() { curl -fsS -X POST \ -d "grant_type=client_credentials" \ -d "client_id=${KEYCLOAK_CLIENT_ID}" \ -d "client_secret=${KEYCLOAK_CLIENT_SECRET}" \ "${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \ | jq -r '.access_token' } pull_users() { local token="$1" curl -fsS -H "Authorization: Bearer ${token}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?max=500" \ | jq -r '.[] | select(.enabled == true) | select(.username | startswith("service-account-") | not) | [.id, .username, (.email // "")] | @tsv' } get_admin_auth() { local user token if [[ -n "${JENKINS_ADMIN_USER:-}" && -n "${JENKINS_ADMIN_TOKEN:-}" ]]; then echo "${JENKINS_ADMIN_USER}:${JENKINS_ADMIN_TOKEN}" return fi user=$(kubectl -n "${JENKINS_NAMESPACE}" get secret "${JENKINS_ADMIN_SECRET_NAME}" -o "jsonpath={.data.${JENKINS_ADMIN_USER_KEY}}" | base64 -d) token=$(kubectl -n "${JENKINS_NAMESPACE}" get secret "${JENKINS_ADMIN_SECRET_NAME}" -o "jsonpath={.data.${JENKINS_ADMIN_TOKEN_KEY}}" | base64 -d) echo "${user}:${token}" } get_crumb() { local auth="$1" curl -fsS -u "${auth}" "${JENKINS_URL}/crumbIssuer/api/json" | jq -r .crumb } user_exists() { local auth="$1" user="$2" local code code=$(curl -s -o /dev/null -w '%{http_code}' -u "${auth}" "${JENKINS_URL}/user/${user}/api/json") [[ "${code}" == "200" ]] } create_user() { local auth="$1" crumb="$2" username="$3" email="$4" local status status=$(curl -s -o /dev/null -w '%{http_code}' \ -u "${auth}" \ -H "Jenkins-Crumb: ${crumb}" \ -X POST \ --data "username=${username}&password1=${DEFAULT_PASSWORD}&password2=${DEFAULT_PASSWORD}&fullname=${username}&email=${email}" \ "${JENKINS_URL}/securityRealm/createAccountByAdmin") if [[ "${status}" == "200" || "${status}" == "302" ]]; then echo "created jenkins user ${username}" elif [[ "${status}" == "400" ]]; then echo "jenkins user ${username} already exists (400)" >&2 else echo "failed to create jenkins user ${username} (status ${status})" >&2 fi } main() { local kc_token auth crumb kc_token="$(fetch_token)" auth="$(get_admin_auth)" crumb="$(get_crumb "${auth}")" while IFS=$'\t' read -r _ uid email; do if user_exists "${auth}" "${uid}"; then continue fi create_user "${auth}" "${crumb}" "${uid}" "${email}" done < <(pull_users "${kc_token}") } main "$@"