Files
sicabe-core/deploy/scripts/check-trino-keystore.sh

172 lines
8.2 KiB
Bash
Executable File

#!/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-<nama>/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 <sha256-keystore> # CI (exit 1 bila beda)
#
# Opsi: --host <nama> (paksa host ini) · --user <u> · --dest <dir-remote>
# 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() { # <host>
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 <nama>." >&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() { # <name> <addr>
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 <nama> (atau recreate Trino & Airflow)"
exit 1