356 lines
18 KiB
Bash
356 lines
18 KiB
Bash
#!/usr/bin/env bash
|
|||
|
|
# =============================================================================
|
||
|
|
# eBdesk SiCABE — Verifikasi Keycloak (realm, client, user) — LEBIH LENGKAP dari
|
||
|
|
# verify-phase1. Tujuan: menjamin konfigurasi Keycloak siap dipakai fase berikutnya
|
||
|
|
# & alur uji E2E (OIDC Trino/StarRocks/Airflow/OM/Grafana; login user engineer/…).
|
||
|
|
#
|
||
|
|
# Yang diperiksa:
|
||
|
|
# A. Realm & roles : discovery/issuer; role admin/engineer/analyst/viewer.
|
||
|
|
# B. Clients (statis) : per client — ada, enabled, publicClient=false,
|
||
|
|
# standardFlow, serviceAccounts, client-secret,
|
||
|
|
# redirectUris/webOrigins (localhost + PUBLIC_URL), audience mapper.
|
||
|
|
# C. Users (statis) : per user — ada, enabled, realm role benar, dan
|
||
|
|
# apakah password masih DEFAULT (hardcoded: engineer123/…).
|
||
|
|
# D. Fungsional : client_credentials tiap service client → token OK (uji SECRET).
|
||
|
|
# E. Fungsional : login user (password grant) → preferred_username + roles.
|
||
|
|
# F. Redirect URI : validasi NYATA via /auth — Keycloak MENERIMA redirect alamat
|
||
|
|
# host (dan MENOLAK yang ngawur). Bukan sekadar banding daftar.
|
||
|
|
#
|
||
|
|
# Mode READ-ONLY (tanpa admin Keycloak): bagian statis (B/C) DILEWATI; D/E tetap jalan.
|
||
|
|
# Exit: 0 = semua gate lolos (WARN boleh), 1 = ada FAIL.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# ./scripts/verify-keycloak.sh
|
||
|
|
# ./scripts/verify-keycloak.sh --env-file generated/host-control/.env
|
||
|
|
# KEYCLOAK_URL=http://kc.corp:8080 ./scripts/verify-keycloak.sh # IdP existing
|
||
|
|
# =============================================================================
|
||
|
|
set -uo pipefail
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
|
|
# shellcheck source=../lib/common.sh
|
||
|
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||
|
|
# shellcheck source=../config/environment.defaults.sh
|
||
|
|
source "${SCRIPT_DIR}/config/environment.defaults.sh"
|
||
|
|
sicabe_load_env "${SCRIPT_DIR}" "$@" || exit 1
|
||
|
|
|
||
|
|
KC="$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)"
|
||
|
|
REALM="${SICABE_REALM:-sicabe}"
|
||
|
|
|
||
|
|
REQUIRED_CLIENTS=("trino" "starrocks" "airflow" "openmetadata" "grafana")
|
||
|
|
REQUIRED_ROLES=("admin" "engineer" "analyst" "viewer")
|
||
|
|
REQUIRED_USERS=("admin" "engineer" "analyst" "viewer")
|
||
|
|
AUDIENCE_MAPPER="audience-account"
|
||
|
|
|
||
|
|
# Peran yang Diharapkan per user.
|
||
|
|
declare -A USER_ROLE=([admin]=admin [engineer]=engineer [analyst]=analyst [viewer]=viewer)
|
||
|
|
# Password DEFAULT (hardcoded oleh bootstrap.sh) — untuk mendeteksi password lemah.
|
||
|
|
declare -A USER_DEFAULT_PW=(
|
||
|
|
[admin]="${KEYCLOAK_ADMIN_PASSWORD:-}"
|
||
|
|
[engineer]="engineer123"
|
||
|
|
[analyst]="analyst123"
|
||
|
|
[viewer]="viewer123"
|
||
|
|
)
|
||
|
|
# Secret client diharapkan (harus sama dgn env deploy) + URL publik (redirect/webOrigins).
|
||
|
|
declare -A CLIENT_SECRET=(
|
||
|
|
[trino]="${TRINO_CLIENT_SECRET:-}" [starrocks]="${STARROCKS_CLIENT_SECRET:-}"
|
||
|
|
[airflow]="${AIRFLOW_CLIENT_SECRET:-}" [openmetadata]="${OPENMETADATA_CLIENT_SECRET:-}"
|
||
|
|
[grafana]="${GRAFANA_CLIENT_SECRET:-}"
|
||
|
|
)
|
||
|
|
declare -A CLIENT_PUBLIC=(
|
||
|
|
[trino]="${TRINO_PUBLIC_URL:-}" [starrocks]="${STARROCKS_PUBLIC_URL:-}"
|
||
|
|
[airflow]="${AIRFLOW_PUBLIC_URL:-}" [openmetadata]="${OPENMETADATA_PUBLIC_URL:-}"
|
||
|
|
[grafana]="${GRAFANA_PUBLIC_URL:-}"
|
||
|
|
)
|
||
|
|
# Origin (skema://alamat:port) tempat BROWSER membuka komponen — WAJIB ada di
|
||
|
|
# redirectUris. Deteksi kesalahan umum: redirectUris hanya `https://localhost:12443/*`
|
||
|
|
# padahal user membuka `https://<ip-host>:12443` → Keycloak menolak "invalid redirect_uri".
|
||
|
|
# <client> = "<scheme> <host_var> <port_var> <default_port>"
|
||
|
|
declare -A CLIENT_ORIGIN=(
|
||
|
|
[trino]="https TRINO_HOST TRINO_HTTPS_PORT 12443"
|
||
|
|
[grafana]="http GRAFANA_HOST GRAFANA_PORT 12300"
|
||
|
|
[openmetadata]="http OPENMETADATA_HOST OPENMETADATA_PORT 12085"
|
||
|
|
[airflow]="http AIRFLOW_HOST AIRFLOW_PORT 12082"
|
||
|
|
[starrocks]="http STARROCKS_FE_HOST STARROCKS_HTTP_PORT 12030"
|
||
|
|
)
|
||
|
|
# Alamat efektif: nilai var bila berupa ALAMAT (mengandung titik); kalau kosong/co-located
|
||
|
|
# → alamat host ini (SICABE_HOST_ADDRESS, multi-host). Kalau KEDUANYA tak ada → KOSONG
|
||
|
|
# (JANGAN fallback ke localhost: itu membuat cek origin terhadap localhost selalu PASS).
|
||
|
|
comp_addr() {
|
||
|
|
local v="${!1:-}"
|
||
|
|
if [[ "${v}" == *.* && "${v}" != "127.0.0.1" ]]; then echo "${v}"; return; fi
|
||
|
|
echo "${SICABE_HOST_ADDRESS:-}"
|
||
|
|
}
|
||
|
|
expected_origin() { # <client> → "skema://alamat:port" (atau kosong bila alamat tak diketahui)
|
||
|
|
local spec="${CLIENT_ORIGIN[$1]:-}"; [[ -n "${spec}" ]] || { echo ""; return; }
|
||
|
|
local scheme hv pv dport addr; read -r scheme hv pv dport <<< "${spec}"
|
||
|
|
addr="$(comp_addr "${hv}")"; [[ -n "${addr}" ]] || { echo ""; return; }
|
||
|
|
printf '%s://%s:%s' "${scheme}" "${addr}" "${!pv:-${dport}}"
|
||
|
|
}
|
||
|
|
|
||
|
|
PASS=0; FAIL_N=0; WARN_N=0
|
||
|
|
pass() { echo -e " ${PASS} $1"; PASS=$((PASS + 1)); }
|
||
|
|
fail() { echo -e " ${FAIL} $1"; FAIL_N=$((FAIL_N + 1)); }
|
||
|
|
warn() { echo -e " ${WARN} $1"; WARN_N=$((WARN_N + 1)); }
|
||
|
|
info() { echo -e " ${INFO} $1"; }
|
||
|
|
|
||
|
|
# ── Keycloak helper ──────────────────────────────────────────────────────────
|
||
|
|
kc_admin_token() {
|
||
|
|
curl -sf -X POST "${KC}/realms/master/protocol/openid-connect/token" \
|
||
|
|
-d client_id=admin-cli -d "username=${KEYCLOAK_ADMIN:-admin}" \
|
||
|
|
-d "password=${KEYCLOAK_ADMIN_PASSWORD:-}" -d grant_type=password 2>/dev/null |
|
||
|
|
python3 -c "import sys,json;print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo ""
|
||
|
|
}
|
||
|
|
kc_get() { curl -sf -H "Authorization: Bearer ${KC_TOKEN}" "${KC}/admin/realms/${REALM}$1" 2>/dev/null; }
|
||
|
|
|
||
|
|
# client_credentials → cetak "OK" atau "HTTP<code>" atau "ERR".
|
||
|
|
cc_grant() { # <client_id> <secret> → "OK" / "HTTP<code>"
|
||
|
|
local code
|
||
|
|
code="$(curl -s -o /dev/null -w '%{http_code}' -X POST \
|
||
|
|
"${KC}/realms/${REALM}/protocol/openid-connect/token" \
|
||
|
|
-d "client_id=$1" -d "client_secret=$2" -d grant_type=client_credentials 2>/dev/null || true)"
|
||
|
|
code="${code:-000}"
|
||
|
|
[[ "${code}" == "200" ]] && echo OK || echo "HTTP${code}"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Uji NYATA redirect_uri via endpoint /auth (publik — tak perlu admin): apakah Keycloak
|
||
|
|
# MENERIMA redirect (→ halaman/redirect login) atau MENOLAK ("Invalid parameter: redirect_uri").
|
||
|
|
kc_auth_probe() { # <client_id> <redirect_uri> → OK | REJECTED | HTTP<code>
|
||
|
|
local out code
|
||
|
|
out="$(curl -s -w $'\n%{http_code}' -G "${KC}/realms/${REALM}/protocol/openid-connect/auth" \
|
||
|
|
--data-urlencode "client_id=$1" --data-urlencode "redirect_uri=$2" \
|
||
|
|
--data-urlencode "response_type=code" --data-urlencode "scope=openid" 2>/dev/null || true)"
|
||
|
|
code="${out##*$'\n'}"
|
||
|
|
if printf '%s' "${out}" | grep -qi 'Invalid parameter: redirect_uri' || [[ "${code}" == "400" ]]; then
|
||
|
|
echo REJECTED; return
|
||
|
|
fi
|
||
|
|
case "${code}" in 200|302|303) echo OK ;; *) echo "HTTP${code:-000}" ;; esac
|
||
|
|
}
|
||
|
|
# password grant → cetak "preferred_username|roles" (dari id_token) atau "".
|
||
|
|
user_login() { # <user> <pw>
|
||
|
|
curl -s -X POST "${KC}/realms/${REALM}/protocol/openid-connect/token" \
|
||
|
|
-d client_id=admin-cli -d "username=$1" -d "password=$2" \
|
||
|
|
-d grant_type=password -d scope=openid 2>/dev/null |
|
||
|
|
python3 -c "
|
||
|
|
import sys,json,base64
|
||
|
|
try: r=json.load(sys.stdin)
|
||
|
|
except Exception: print(''); raise SystemExit
|
||
|
|
t=r.get('id_token') or r.get('access_token') or ''
|
||
|
|
if not t: print(''); raise SystemExit
|
||
|
|
p=t.split('.')[1]; p+='='*(-len(p)%4)
|
||
|
|
try: c=json.loads(base64.urlsafe_b64decode(p))
|
||
|
|
except Exception: print(''); raise SystemExit
|
||
|
|
print(c.get('preferred_username','') + '|' + ','.join(c.get('realm_access',{}).get('roles') or []))
|
||
|
|
" 2>/dev/null || echo ""
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── A. Realm & roles ─────────────────────────────────────────────────────────
|
||
|
|
check_realm_roles() {
|
||
|
|
local disc issuer
|
||
|
|
disc="$(curl -sf "${KC}/realms/${REALM}/.well-known/openid-configuration" 2>/dev/null || true)"
|
||
|
|
if [[ -z "${disc}" ]]; then
|
||
|
|
fail "OIDC discovery realm '${REALM}' tidak merespon (${KC})"; return
|
||
|
|
fi
|
||
|
|
issuer="$(echo "${disc}" | python3 -c "import sys,json;print(json.load(sys.stdin).get('issuer',''))" 2>/dev/null)"
|
||
|
|
pass "OIDC discovery OK (issuer=${issuer})"
|
||
|
|
if [[ -z "${KC_TOKEN}" ]]; then
|
||
|
|
warn "Tanpa admin Keycloak — cek statis (roles/client/user) DILEWATI"; return
|
||
|
|
fi
|
||
|
|
local roles missing=""
|
||
|
|
roles="$(kc_get "/roles" | python3 -c "import sys,json;print(' '.join(r['name'] for r in json.load(sys.stdin)))" 2>/dev/null || echo "")"
|
||
|
|
for r in "${REQUIRED_ROLES[@]}"; do [[ " ${roles} " == *" ${r} "* ]] || missing+=" ${r}"; done
|
||
|
|
[[ -z "${missing}" ]] && pass "Roles lengkap: ${REQUIRED_ROLES[*]}" || fail "Role belum ada:${missing}"
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── B. Clients (statis) ──────────────────────────────────────────────────────
|
||
|
|
check_clients() {
|
||
|
|
[[ -n "${KC_TOKEN}" ]] || return 0
|
||
|
|
local cid js ok
|
||
|
|
# Bila origin host TAK BISA dihitung (env tanpa alamat cluster) → beri tahu SEKALI;
|
||
|
|
# cek origin per client di-skip (bukan PASS palsu). Sumber benar: --env-file bundle
|
||
|
|
# atau SICABE_CLIENT_REDIRECT_ORIGINS.
|
||
|
|
local _c _have=0
|
||
|
|
[[ -n "${SICABE_CLIENT_REDIRECT_ORIGINS:-}" ]] && _have=1
|
||
|
|
for _c in "${REQUIRED_CLIENTS[@]}"; do [[ -n "$(expected_origin "${_c}")" ]] && { _have=1; break; }; done
|
||
|
|
[[ ${_have} -eq 0 ]] && warn "Origin host tak bisa dihitung (env tanpa alamat cluster) — jalankan dgn --env-file generated/host-<nama>/.env, atau set SICABE_CLIENT_REDIRECT_ORIGINS. Cek origin DILEWATI (bukan PASS)."
|
||
|
|
for cid in "${REQUIRED_CLIENTS[@]}"; do
|
||
|
|
js="$(kc_get "/clients?clientId=${cid}")"
|
||
|
|
if [[ -z "${js}" || "${js}" == "{}" || "${js}" == "[]" ]]; then
|
||
|
|
fail "Client '${cid}' TIDAK ADA"; continue
|
||
|
|
fi
|
||
|
|
# Ambil bidang penting + secret + redirect + mapper dalam satu pass.
|
||
|
|
read -r ok pub std svc auth secret redirects mapper < <(echo "${js}" | python3 -c "
|
||
|
|
import sys,json
|
||
|
|
d=json.load(sys.stdin) or []
|
||
|
|
if not d: print('0 - - - - - - -'); raise SystemExit
|
||
|
|
c=d[0]
|
||
|
|
print(
|
||
|
|
'1' if c.get('enabled') else '0',
|
||
|
|
c.get('publicClient'),
|
||
|
|
'1' if c.get('standardFlowEnabled') else '0',
|
||
|
|
'1' if c.get('serviceAccountsEnabled') else '0',
|
||
|
|
c.get('clientAuthenticatorType',''),
|
||
|
|
c.get('secret',''),
|
||
|
|
','.join(c.get('redirectUris') or []),
|
||
|
|
','.join(c.get('redirectUris') or []))
|
||
|
|
" 2>/dev/null)
|
||
|
|
[[ "${ok}" == "1" ]] && pass "Client '${cid}' enabled" || fail "Client '${cid}' DISABLED"
|
||
|
|
[[ "${pub}" == "False" ]] && pass " ${cid}: publicClient=false (confidential)" \
|
||
|
|
|| fail " ${cid}: publicClient=${pub} (harus False)"
|
||
|
|
[[ "${std}" == "1" ]] && pass " ${cid}: standardFlowEnabled" || fail " ${cid}: standardFlow OFF"
|
||
|
|
[[ "${svc}" == "1" ]] && pass " ${cid}: serviceAccountsEnabled" || warn " ${cid}: serviceAccounts OFF"
|
||
|
|
[[ "${auth}" == "client-secret" ]] && pass " ${cid}: clientAuthenticatorType=client-secret" \
|
||
|
|
|| fail " ${cid}: authenticator='${auth}' (harus client-secret)"
|
||
|
|
# Secret sinkron dgn env.
|
||
|
|
if [[ -n "${CLIENT_SECRET[$cid]}" ]]; then
|
||
|
|
[[ "${secret}" == "${CLIENT_SECRET[$cid]}" ]] && pass " ${cid}: secret sinkron dgn env" \
|
||
|
|
|| fail " ${cid}: secret BEDA dari env (komponen akan gagal login)"
|
||
|
|
else
|
||
|
|
warn " ${cid}: *_CLIENT_SECRET kosong di env — tak bisa membandingkan secret"
|
||
|
|
fi
|
||
|
|
# redirectUris: localhost + (bila ada) PUBLIC_URL.
|
||
|
|
if [[ "${redirects}" == *"http://localhost:*"* || "${redirects}" == *"https://localhost:*"* ]]; then
|
||
|
|
pass " ${cid}: redirectUris memuat localhost"
|
||
|
|
else
|
||
|
|
warn " ${cid}: redirectUris TANPA localhost (${redirects})"
|
||
|
|
fi
|
||
|
|
local puburl="${CLIENT_PUBLIC[$cid]}"
|
||
|
|
if [[ -n "${puburl}" ]]; then
|
||
|
|
if [[ "${redirects}" == *"${puburl}"* ]]; then
|
||
|
|
pass " ${cid}: redirectUris memuat PUBLIC_URL (${puburl})"
|
||
|
|
else
|
||
|
|
fail " ${cid}: redirectUris TANPA PUBLIC_URL '${puburl}' (login browser via alamat publik akan ditolak)"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
# Origin host (tempat browser membuka komponen) — WAJIB ada, bukan hanya localhost.
|
||
|
|
local exp; exp="$(expected_origin "${cid}")"
|
||
|
|
if [[ -n "${exp}" ]]; then
|
||
|
|
if [[ "${redirects}" == *"${exp}"* ]]; then
|
||
|
|
pass " ${cid}: redirectUris memuat origin host (${exp})"
|
||
|
|
else
|
||
|
|
fail " ${cid}: redirectUris TANPA origin host '${exp}' — login browser via alamat itu DITOLAK (invalid redirect_uri)"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
# Origin client dari CLUSTER (SICABE_CLIENT_REDIRECT_ORIGINS) — host existing yang
|
||
|
|
# menjalankan bootstrap tak tahu alamat host cluster, jadi daftar ini disuplai ke .env.
|
||
|
|
local o
|
||
|
|
for o in ${SICABE_CLIENT_REDIRECT_ORIGINS:-}; do
|
||
|
|
[[ -n "${o}" ]] || continue
|
||
|
|
if [[ "${redirects}" == *"${o}"* ]]; then
|
||
|
|
pass " ${cid}: redirectUris memuat origin cluster (${o})"
|
||
|
|
else
|
||
|
|
fail " ${cid}: redirectUris TANPA origin cluster '${o}'"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── C. Users (statis) + password default ─────────────────────────────────────
|
||
|
|
check_users() {
|
||
|
|
[[ -n "${KC_TOKEN}" ]] || return 0
|
||
|
|
local u js
|
||
|
|
for u in "${REQUIRED_USERS[@]}"; do
|
||
|
|
js="$(kc_get "/users?username=${u}&exact=true")"
|
||
|
|
if [[ -z "${js}" || "${js}" == "[]" ]]; then fail "User '${u}' TIDAK ADA"; continue; fi
|
||
|
|
local uid enabled roles
|
||
|
|
uid="$(echo "${js}" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[0]['id'] if d else '')" 2>/dev/null)"
|
||
|
|
enabled="$(echo "${js}" | python3 -c "import sys,json;d=json.load(sys.stdin);print('1' if d and d[0].get('enabled') else '0')" 2>/dev/null)"
|
||
|
|
roles="$(kc_get "/users/${uid}/role-mappings/realm" | python3 -c "import sys,json;print(' '.join(r['name'] for r in json.load(sys.stdin)))" 2>/dev/null || echo "")"
|
||
|
|
[[ "${enabled}" == "1" ]] && pass "User '${u}' ada + enabled" || fail "User '${u}' DISABLED"
|
||
|
|
[[ " ${roles} " == *" ${USER_ROLE[$u]} "* ]] && pass " ${u}: realm role '${USER_ROLE[$u]}'" \
|
||
|
|
|| fail " ${u}: role '${USER_ROLE[$u]}' TIDAK ada (peran: ${roles:-—})"
|
||
|
|
# Password masih DEFAULT (hardcoded)?
|
||
|
|
local dp="${USER_DEFAULT_PW[$u]}"
|
||
|
|
if [[ -n "${dp}" ]] && [[ "$(user_login "${u}" "${dp}")" == "${u}|"* ]]; then
|
||
|
|
warn " ${u}: password masih DEFAULT (hardcoded) — ganti untuk produksi"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── D. client_credentials ────────────────────────────────────────────────────
|
||
|
|
check_cc() {
|
||
|
|
local cid res
|
||
|
|
for cid in "${REQUIRED_CLIENTS[@]}"; do
|
||
|
|
[[ -n "${CLIENT_SECRET[$cid]}" ]] || { warn "cc ${cid}: secret env kosong"; continue; }
|
||
|
|
res="$(cc_grant "${cid}" "${CLIENT_SECRET[$cid]}")"
|
||
|
|
[[ "${res}" == "OK" ]] && pass "client_credentials '${cid}' → token OK" \
|
||
|
|
|| fail "client_credentials '${cid}' GAGAL (${res})"
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── E2. Redirect URI — validasi NYATA via /auth (bukan sekadar bandingkan daftar) ──
|
||
|
|
check_redirect_validation() {
|
||
|
|
local cid o exp rs any=0
|
||
|
|
for cid in "${REQUIRED_CLIENTS[@]}"; do
|
||
|
|
exp="$(expected_origin "${cid}")"
|
||
|
|
if [[ -n "${exp}" ]]; then
|
||
|
|
any=1
|
||
|
|
rs="$(kc_auth_probe "${cid}" "${exp}/")"
|
||
|
|
case "${rs}" in
|
||
|
|
OK) pass "${cid}: Keycloak MENERIMA redirect '${exp}/*' (uji nyata via /auth)";;
|
||
|
|
REJECTED) fail "${cid}: Keycloak MENOLAK redirect '${exp}/*' — invalid redirect_uri (belum terdaftar di client)";;
|
||
|
|
*) warn "${cid}: uji redirect '${exp}/*' tak konklusif (${rs})";;
|
||
|
|
esac
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
# Origin dari daftar cluster (SICABE_CLIENT_REDIRECT_ORIGINS) — uji terhadap tiap client.
|
||
|
|
for o in ${SICABE_CLIENT_REDIRECT_ORIGINS:-}; do
|
||
|
|
[[ -n "${o}" ]] || continue
|
||
|
|
any=1
|
||
|
|
for cid in "${REQUIRED_CLIENTS[@]}"; do
|
||
|
|
rs="$(kc_auth_probe "${cid}" "${o}/")"
|
||
|
|
[[ "${rs}" == "OK" ]] && pass "${cid}: Keycloak MENERIMA origin cluster '${o}/*'" \
|
||
|
|
|| fail "${cid}: Keycloak MENOLAK origin cluster '${o}/*' (${rs})"
|
||
|
|
done
|
||
|
|
done
|
||
|
|
if [[ ${any} -eq 0 ]]; then
|
||
|
|
warn "redirect_uri tak bisa diuji (env tanpa alamat cluster / SICABE_CLIENT_REDIRECT_ORIGINS)"
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
# Kontrol: redirect ngawur HARUS ditolak → membuktikan Keycloak memang memvalidasi.
|
||
|
|
rs="$(kc_auth_probe "trino" "https://sicabe-invalid.example/blocked")"
|
||
|
|
[[ "${rs}" == "REJECTED" ]] \
|
||
|
|
&& pass "kontrol: redirect ngawur DITOLAK Keycloak (validasi redirect aktif)" \
|
||
|
|
|| warn "kontrol: redirect ngawur '${rs}' — Keycloak mungkin TIDAK memvalidasi redirect_uri"
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── E. Login user (password grant) ───────────────────────────────────────────
|
||
|
|
check_user_login() {
|
||
|
|
local u out pref
|
||
|
|
for u in "${REQUIRED_USERS[@]}"; do
|
||
|
|
local pw="${USER_DEFAULT_PW[$u]}"
|
||
|
|
[[ -n "${pw}" ]] || { warn "login '${u}': password tak diketahui — lewati"; continue; }
|
||
|
|
out="$(user_login "${u}" "${pw}")"
|
||
|
|
pref="${out%%|*}"
|
||
|
|
if [[ "${pref}" == "${u}" ]]; then
|
||
|
|
pass "login '${u}' OK (preferred_username=${pref}; roles=${out#*|})"
|
||
|
|
elif [[ -z "${out}" ]]; then
|
||
|
|
fail "login '${u}' GAGAL (user/password salah, atau user belum ada)"
|
||
|
|
else
|
||
|
|
fail "login '${u}' OK tapi preferred_username='${pref}' (harus = uid LDAP '${u}')"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
# ── main ─────────────────────────────────────────────────────────────────────
|
||
|
|
echo -e "${BOLD}Verifikasi Keycloak — realm '${REALM}' @ ${KC}${NC}"
|
||
|
|
echo ""
|
||
|
|
KC_TOKEN="$(kc_admin_token)"
|
||
|
|
if [[ -z "${KC_TOKEN}" ]]; then
|
||
|
|
warn "Mode READ-ONLY (login admin gagal / tanpa admin) — cek statis dilewati"
|
||
|
|
else
|
||
|
|
info "Token admin diperoleh — cek statis dijalankan"
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
echo -e "${BOLD}A. Realm & roles${NC}"; check_realm_roles
|
||
|
|
echo -e "${BOLD}B. Clients (statis)${NC}"; check_clients
|
||
|
|
echo -e "${BOLD}C. Users (statis)${NC}"; check_users
|
||
|
|
echo -e "${BOLD}D. Fungsional — client_credentials (uji SECRET client, bukan redirect)${NC}"; check_cc
|
||
|
|
echo -e "${BOLD}E. Fungsional — login user${NC}"; check_user_login
|
||
|
|
echo -e "${BOLD}F. Redirect URI — validasi NYATA via /auth${NC}"; check_redirect_validation
|
||
|
|
echo ""
|
||
|
|
echo -e "${BOLD}Ringkasan: ${PASS} PASS · ${FAIL_N} FAIL · ${WARN_N} WARN${NC}"
|
||
|
|
[[ ${FAIL_N} -eq 0 ]] && { echo -e "${GREEN}✓ Keycloak siap.${NC}"; exit 0; }
|
||
|
|
echo -e "${RED}✗ Ada ${FAIL_N} kegagalan — perbaiki sebelum melanjutkan.${NC}"
|
||
|
|
exit 1
|