#!/usr/bin/env bash # ============================================================================= # Controle go / no-go avant ouverture au public # ============================================================================= # export KUBECONFIG=~/.kube/xpeditis-prod.yaml # bash scripts/preflight-check.sh # # Chaque point BLOQUANT en echec doit etre corrige avant d'ouvrir le service. # Les points d'AVERTISSEMENT peuvent etre acceptes consciemment et notes dans # le registre des risques. set -uo pipefail NAMESPACE=xpeditis-prod API="${PROD_API_URL:-https://api.xpeditis.com}" DB_HOST="${DB_PRIVATE_IP:-10.10.1.20}" DB_PUBLIC_IP="${DB_PUBLIC_IP:-}" BLOCK=0 WARN=0 ok() { printf ' \033[32m[OK]\033[0m %s\n' "$1"; } block() { printf ' \033[31m[BLOQUANT]\033[0m %s\n' "$1"; BLOCK=$((BLOCK+1)); } warn() { printf ' \033[33m[ATTENTION]\033[0m %s\n' "$1"; WARN=$((WARN+1)); } section() { printf '\n\033[1m%s\033[0m\n' "$1"; } # --------------------------------------------------------------------------- section "1. Cluster" if kubectl get nodes >/dev/null 2>&1; then ok "Cluster joignable" if kubectl get nodes --no-headers | grep -qv ' Ready'; then block "Un noeud n'est pas Ready" else ok "Tous les noeuds sont Ready" fi else block "Cluster injoignable : rien d'autre ne peut etre verifie" fi if kubectl -n "$NAMESPACE" get pods --no-headers 2>/dev/null | grep -qE 'CrashLoop|ImagePull|Error|Pending'; then block "Des pods ne sont pas sains dans $NAMESPACE" else ok "Tous les pods de $NAMESPACE sont sains" fi for d in xpeditis-backend xpeditis-frontend; do READY=$(kubectl -n "$NAMESPACE" get deploy "$d" -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo 0) [[ "${READY:-0}" -ge 2 ]] && ok "$d : ${READY} replicas prets" \ || warn "$d : ${READY:-0} replica(s) pret(s), 2 attendus (pas de haute disponibilite)" done # --------------------------------------------------------------------------- section "2. Secrets" if kubectl -n "$NAMESPACE" get secret xpeditis-backend-secrets >/dev/null 2>&1; then ok "Secrets applicatifs presents" JWT=$(kubectl -n "$NAMESPACE" get secret xpeditis-backend-secrets -o jsonpath='{.data.JWT_SECRET}' 2>/dev/null | base64 -d 2>/dev/null || echo "") [[ ${#JWT} -ge 32 ]] && ok "JWT_SECRET fait ${#JWT} caracteres" || block "JWT_SECRET trop court (${#JWT} caracteres, 32 minimum)" # Les secrets publies dans infra/preprod/docker-stack.preprod.yml sont dans # l'historique Git : les retrouver en production serait une compromission. for compromis in "4C4tQC8qym" "9Lc3M9qoPBeHLKHDXGUf1" "hXiy5GMPswMtxMZujjS2O" "RBJfD0QVXC5JDfAHCwdUW"; do if kubectl -n "$NAMESPACE" get secret xpeditis-backend-secrets -o json 2>/dev/null \ | grep -q "$(printf '%s' "$compromis" | base64 | cut -c1-12)"; then block "Un secret de preprod (present dans Git, donc compromis) est reutilise en production" fi done ok "Aucun secret de preprod detecte" STRIPE=$(kubectl -n "$NAMESPACE" get secret xpeditis-backend-secrets -o jsonpath='{.data.STRIPE_SECRET_KEY}' 2>/dev/null | base64 -d 2>/dev/null || echo "") case "$STRIPE" in sk_live_*) ok "Cle Stripe en mode LIVE" ;; sk_test_*) block "Cle Stripe en mode TEST : aucun paiement reel ne sera encaisse" ;; *) warn "Cle Stripe absente ou non reconnue" ;; esac else block "Secrets applicatifs absents" fi if kubectl -n "$NAMESPACE" get secret regcred >/dev/null 2>&1; then ok "Acces au registre configure" else block "Secret 'regcred' absent : aucune image ne pourra etre telechargee" fi if sudo k3s secrets-encrypt status 2>/dev/null | grep -qi 'Encryption Status: Enabled'; then ok "Chiffrement des Secrets au repos actif" else warn "Chiffrement des Secrets au repos non verifiable depuis ce poste (a controler sur app-01)" fi # --------------------------------------------------------------------------- section "3. TLS et exposition" CERT=$(kubectl -n "$NAMESPACE" get certificate xpeditis-wildcard -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") [[ "$CERT" == "True" ]] && ok "Certificat wildcard emis" || block "Certificat wildcard non pret" ISSUER=$(kubectl -n "$NAMESPACE" get certificate xpeditis-wildcard -o jsonpath='{.spec.issuerRef.name}' 2>/dev/null || echo "") [[ "$ISSUER" == "letsencrypt-prod" ]] && ok "Emetteur : letsencrypt-prod" \ || block "Emetteur '$ISSUER' : un certificat de staging n'est pas reconnu par les navigateurs" if curl -sSI --max-time 15 "${API}/api/v1/health" 2>/dev/null | grep -qi '^strict-transport-security:'; then ok "HSTS actif" else block "En-tete HSTS absent" fi if [[ -n "$DB_PUBLIC_IP" ]]; then if command -v nc >/dev/null; then if nc -z -w3 "$DB_PUBLIC_IP" 5432 2>/dev/null; then block "PostgreSQL repond sur l'IP PUBLIQUE de db-01" else ok "PostgreSQL injoignable publiquement" fi if nc -z -w3 "$DB_PUBLIC_IP" 6379 2>/dev/null; then block "Redis repond sur l'IP PUBLIQUE de db-01" else ok "Redis injoignable publiquement" fi fi else warn "DB_PUBLIC_IP non fournie : exposition de la base non verifiee" fi # --------------------------------------------------------------------------- section "4. Comptes de demonstration" # La migration 1730000000007-SeedTestUsers creait admin@xpeditis.com avec le mot # de passe "Password123!", ecrit en clair dans le depot. Elle ne s'execute plus # quand NODE_ENV=production, et 1756000000000 neutralise ces comptes s'ils # existent malgre tout. # # Ce controle verifie le RESULTAT en conditions reelles, pas l'intention : c'est # le seul moyen de detecter un NODE_ENV mal positionne ou une base restauree # depuis une sauvegarde anterieure. Le controle le plus important de la liste. for compte in admin manager user; do CODE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 15 \ -X POST "${API}/api/v1/auth/login" \ -H 'Content-Type: application/json' \ -d "{\"email\":\"${compte}@xpeditis.com\",\"password\":\"Password123!\"}" 2>/dev/null || echo "000") case "$CODE" in 200|201) block "Le compte de demonstration ${compte}@xpeditis.com accepte encore Password123! (HTTP ${CODE})" ;; 000) warn "Impossible de tester ${compte}@xpeditis.com (API injoignable)" ;; *) ok "${compte}@xpeditis.com neutralise (HTTP ${CODE})" ;; esac done echo " Verifier aussi qu'il n'existe qu'un seul ADMIN actif, sur db-01 :" echo " SELECT email, role, is_active FROM users WHERE role = 'ADMIN';" # --------------------------------------------------------------------------- section "5. Sauvegardes" cat <<'MANUEL' Ces points se verifient sur db-01, ils ne peuvent pas l'etre d'ici : ssh deploy@ systemctl list-timers 'xpeditis-*' # timers actifs sudo journalctl -u xpeditis-backup -n 30 # derniere execution sudo /opt/xpeditis/data-node/backup/pg-restore.sh verify Une sauvegarde jamais restauree n'est pas une sauvegarde. Le test de restauration DOIT avoir ete passe au moins une fois avant l'ouverture. MANUEL # --------------------------------------------------------------------------- section "6. Fonctionnement" if bash "$(dirname "${BASH_SOURCE[0]}")/smoke-test.sh" >/dev/null 2>&1; then ok "Tests de fumee au vert" else block "Tests de fumee en echec (relancer smoke-test.sh pour le detail)" fi # --------------------------------------------------------------------------- printf '\n=========================================\n' printf ' Bloquants : %d Avertissements : %d\n' "$BLOCK" "$WARN" printf '=========================================\n' if [[ "$BLOCK" -gt 0 ]]; then printf '\n\033[31mNO-GO\033[0m : corrigez les points bloquants avant d ouvrir au public.\n' exit 1 fi printf '\n\033[32mGO\033[0m : les controles bloquants sont passes.\n' [[ "$WARN" -gt 0 ]] && printf 'Consignez les %d avertissement(s) dans le registre des risques.\n' "$WARN" exit 0