#!/usr/bin/env bash # ============================================================================= # eBdesk SiCABE — Cek konsistensi keystore TLS Trino + password.db ANTAR-HOST # # Dua hal yang WAJIB benar agar klien (Great Expectations / OpenMetadata / dbt) # bisa bicara ke Trino: # 1) keystore TLS (generated/host-/config/trino/keystore.p12) IDENTIK di # semua host → hasil SATU `--generate` + `--sync`. # 2) password.db punya service account (svc_dbt) & cocok dgn TRINO_SVC_DBT_PASSWORD # di bundle .env (kalau tidak → 401 "Access Denied: Invalid credentials"). # Catatan: bcrypt memakai salt acak → berkas password.db TIDAK dibandingkan # byte-per-byte antar-host, melainkan DIVERIFIKASI thd password yang diharapkan. # # Pemakaian: # ./scripts/check-trino-keystore.sh # host ini saja # ./scripts/check-trino-keystore.sh --inventory cluster.p3.yml # ./scripts/check-trino-keystore.sh --expect # CI (exit 1 bila beda) # # Opsi: --host (paksa host ini) · --user · --dest # default: user $USER, dest = cermin lokasi repo ini di bawah $HOME # (mis. /home/u/sicabe-core/deploy → ~/sicabe-core/deploy) # # Keluar: 0 = semua OK · 1 = ada BEDA/MISSING · 2 = error (berkas/inventory tak ada) # ============================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${SCRIPT_DIR}" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/lib/common.sh" 2>/dev/null || true source "${SCRIPT_DIR}/config/environment.defaults.sh" 2>/dev/null || true if [[ -f "${SCRIPT_DIR}/.env" ]]; then set -a; source "${SCRIPT_DIR}/.env"; set +a; fi INV=""; SSH_USER="${USER:-root}"; EXPECT=""; FORCE_HOST="" # Tujuan remote default = cermin lokasi repo ini di bawah $HOME (bebas nama repo: # `sicabe-core` → ~/sicabe-core/deploy). Fallback bila lib/common.sh gagal dimuat. if declare -F sicabe_default_dest >/dev/null 2>&1; then DEST="$(sicabe_default_dest "${SCRIPT_DIR}")" else DEST="~/$(basename "$(dirname "${SCRIPT_DIR}")")/deploy" fi while [[ $# -gt 0 ]]; do case "$1" in --inventory) INV="${2:-}"; shift 2 ;; --user) SSH_USER="${2:-}"; shift 2 ;; --dest) DEST="${2:-}"; shift 2 ;; --expect) EXPECT="${2:-}"; shift 2 ;; --host) FORCE_HOST="${2:-}"; shift 2 ;; -h|--help) sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "Argumen tidak dikenal: $1" >&2; exit 2 ;; esac done C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_BLD=$'\033[1m'; C_RST=$'\033[0m' # ── keystore ───────────────────────────────────────────────────────────────── ks_path() { echo "${SCRIPT_DIR}/generated/host-$1/config/trino/keystore.p12"; } ks_sha() { sha256sum "$1" 2>/dev/null | cut -d' ' -f1; } ks_pw() { sed -n 's/^http-server.https.keystore.key=//p' \ "${SCRIPT_DIR}/generated/host-$1/config/trino/config-coordinator.properties" 2>/dev/null | head -1; } ks_fp() { openssl pkcs12 -in "$(ks_path "$1")" -nokeys -passin "pass:$(ks_pw "$1")" 2>/dev/null \ | openssl x509 -noout -fingerprint -sha256 2>/dev/null | sed 's/^sha256 Fingerprint=//'; } # ── password.db (service account) ──────────────────────────────────────────── pw_path() { echo "${SCRIPT_DIR}/generated/host-$1/config/trino/password.db"; } pw_expect() { sed -n 's/^TRINO_SVC_DBT_PASSWORD=//p' \ "${SCRIPT_DIR}/generated/host-$1/.env" 2>/dev/null | head -1; } # Status lokal: NOFILE / MISSING / PWBAD / OK pw_verify() { # local f exp; f="$(pw_path "$1")" [[ -f "${f}" ]] || { echo NOFILE; return; } grep -q '^svc_dbt:' "${f}" 2>/dev/null || { echo MISSING; return; } exp="$(pw_expect "$1")" if command -v htpasswd >/dev/null 2>&1 && [[ -n "${exp}" ]]; then htpasswd -vb "${f}" svc_dbt "${exp}" >/dev/null 2>&1 && echo OK || echo PWBAD else echo OK # ada, tapi tak bisa diverifikasi (htpasswd/pw tak ada) fi } # ── host ini ───────────────────────────────────────────────────────────────── detect_self() { local lip d addr; lip="$(hostname -I 2>/dev/null || true)" for d in "${SCRIPT_DIR}"/generated/host-*; do [[ -f "${d}/.env" ]] || continue addr="$(sed -n 's/^SICABE_HOST_ADDRESS=//p' "${d}/.env" | head -1)" [[ -n "${addr}" && " ${lip} " == *" ${addr} "* ]] && { basename "${d}" | sed 's/^host-//'; return 0; } done return 1 } SELF="${FORCE_HOST}" [[ -z "${SELF}" ]] && SELF="$(detect_self || true)" if [[ -z "${SELF}" ]]; then mapfile -t _dirs < <(ls -d "${SCRIPT_DIR}"/generated/host-* 2>/dev/null) [[ ${#_dirs[@]} -eq 1 ]] && SELF="$(basename "${_dirs[0]}" | sed 's/^host-//')" fi if [[ -z "${SELF}" || ! -f "$(ks_path "${SELF}")" ]]; then echo "${C_RED}Keystore host ini tak ditemukan.${C_RST} Jalankan ./install.sh --generate dulu, atau pakai --host ." >&2 exit 2 fi SELF_SHA="$(ks_sha "$(ks_path "${SELF}")")" EXPECT_PW="$(pw_expect "${SELF}")" echo "${C_BLD}Cek konsistensi keystore + password.db Trino${C_RST}" echo " host ini : ${SELF}" echo " keystore : sha256=${SELF_SHA}" echo " cert : $(ks_fp "${SELF}")" echo " pw svc_dbt: $(pw_verify "${SELF}") (OK/MISSING/PWBAD/NOFILE)" echo "" if [[ -n "${EXPECT}" ]]; then if [[ "${SELF_SHA}" == "${EXPECT}" ]]; then echo "${C_GRN}✓ sha256 keystore cocok dengan --expect.${C_RST}"; exit 0 fi echo "${C_RED}✗ sha256 keystore BERBEDA dari --expect (${EXPECT}).${C_RST}"; exit 1 fi if [[ -z "${INV}" ]]; then echo " (tanpa --inventory — bandingkan di tiap host, atau pakai --inventory)" exit 0 fi [[ -f "${INV}" ]] || { echo "${C_RED}Inventory tak ada: ${INV}${C_RST}" >&2; exit 2; } PAIRS="$(python3 "${SCRIPT_DIR}/lib/cluster/inventory.py" hosts "${INV}" 2>/dev/null)" \ || { echo "${C_RED}Gagal membaca inventory: ${INV}${C_RST}" >&2; exit 2; } # Verifikasi password.db di host LAIN via SSH (bcrypt salt acak → verifikasi, bukan hash). remote_pw_status() { # ssh -o ConnectTimeout=8 -o BatchMode=yes "${SSH_USER}@${2}" \ "f='${DEST}/generated/host-${1}/config/trino/password.db' [ -f \"\$f\" ] || { echo NOFILE; exit 0; } grep -q '^svc_dbt:' \"\$f\" || { echo MISSING; exit 0; } if command -v htpasswd >/dev/null 2>&1; then htpasswd -vb \"\$f\" svc_dbt '${EXPECT_PW}' >/dev/null 2>&1 && echo OK || echo PWBAD; else echo OK; fi" \ 2>/dev/null || echo SSHFAIL } pw_icon() { case "$1" in OK) printf '%s%s%s' "${C_GRN}" "$1" "${C_RST}" ;; *) printf '%s%s%s' "${C_RED}" "$1" "${C_RST}" ;; esac; } mismatch=0; checked=0 while read -r name addr; do [[ -z "${name}" ]] && continue if [[ "${name}" == "${SELF}" ]]; then _ksok=1; _pw="$(pw_verify "${SELF}")" else _rsha="$(ssh -o ConnectTimeout=8 -o BatchMode=yes "${SSH_USER}@${addr}" \ "sha256sum ${DEST}/generated/host-${name}/config/trino/keystore.p12 2>/dev/null | cut -d' ' -f1" 2>/dev/null || true)" if [[ -z "${_rsha}" ]]; then _ksok=-1; elif [[ "${_rsha}" == "${SELF_SHA}" ]]; then _ksok=1; else _ksok=0; fi _pw="$(remote_pw_status "${name}" "${addr}")" fi _kslabel="(host ini)"; _kscol="" case "${_ksok}" in 1) [[ "${name}" != "${SELF}" ]] && { _kslabel="keystore ✓"; _kscol="${C_GRN}"; } ;; 0) _kslabel="keystore ✗ BEDA"; _kscol="${C_RED}"; mismatch=1 ;; -1) _kslabel="keystore ? ssh gagal"; _kscol="${C_YEL}"; mismatch=1 ;; esac [[ "${_pw}" == "OK" ]] || mismatch=1 printf ' %-9s %-15s %s%-16s%s pw svc_dbt %s\n' \ "${name}" "${addr}" "${_kscol}" "${_kslabel}" "${C_RST}" "$(pw_icon "${_pw}")" checked=$((checked + 1)) done <<< "${PAIRS}" echo "" if [[ ${mismatch} -eq 0 ]]; then echo "${C_GRN}✓ keystore identik + password.db OK di semua host (${checked} host).${C_RST}" exit 0 fi echo "${C_RED}✗ Ada ketidakcocokan (keystore BEDA / password.db MISSING/PWBAD).${C_RST}" echo " Perbaiki (di SATU host — control):" echo " ./install.sh --generate --inventory ${INV} && ./install.sh --sync" echo " lalu di tiap host: ./install.sh --host (atau recreate Trino & Airflow)" exit 1