xpeditis2.0/infra/prod/scripts/harden-seed-data.sh
2026-09-14 20:02:12 +02:00

169 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# Neutralisation des comptes de demonstration -- OUTIL DE SECOURS ET D'AUDIT
# =============================================================================
#
# LE CAS NOMINAL N'A PLUS BESOIN DE CE SCRIPT.
#
# Le traitement est desormais fait par les migrations, donc automatiquement et
# sans risque d'oubli :
#
# 1730000000007-SeedTestUsers ne s'execute plus en
# production (APP_ENV)
# 1756000000000-NeutralizeSeedAccountsInProduction filet de securite
# 1756000000001-BootstrapAdminFromEnv cree VOTRE administrateur
#
# Ce script reste utile dans trois situations :
#
# - une base de production migree AVANT l'ajout de la garde APP_ENV ;
# - un deploiement ou APP_ENV n'etait pas correctement positionne (le journal
# du Job de migration affiche alors "Seeded test users successfully") ;
# - une verification manuelle : il affiche l'etat des comptes sans rien
# changer s'il n'y a rien a changer.
#
# ssh deploy@<db-01>
# sudo bash /opt/xpeditis/infra-prod/scripts/harden-seed-data.sh
#
# RAPPEL DU PROBLEME
#
# La migration 1730000000007-SeedTestUsers cree trois comptes dont le mot de
# passe est ecrit en clair dans le depot :
#
# admin@xpeditis.com role ADMIN Password123!
# manager@xpeditis.com role MANAGER Password123!
# user@xpeditis.com role USER Password123!
#
# Sur une base de production, cela donne un acces complet a la plateforme a
# quiconque a lu le depot.
#
# SEULS les comptes dont le hash est encore celui de SeedTestUsers sont traites :
# un compte dont le mot de passe a ete change est un vrai compte, et le
# neutraliser verrouillerait l'administrateur reellement utilise. (Un compte
# remis a Password123! via "mot de passe oublie" aurait un autre sel : le test
# de connexion de preflight-check.sh le detecte.)
#
# Si le compte de demonstration est le SEUL administrateur actif, PostgreSQL
# refuse la neutralisation (declencheur trg_users_keep_active_admin, SQLSTATE
# XP001) et la transaction est annulee : creez ou promouvez d'abord votre
# administrateur, puis relancez ce script.
#
# Ce script ne SUPPRIME pas les lignes (des cles etrangeres peuvent y pointer,
# et une suppression en cascade dans audit_logs serait pire). Il :
# 1. renomme les adresses vers un domaine invalide -- ce qui libere au passage
# admin@xpeditis.com pour votre vrai compte ;
# 2. remplace le mot de passe par une valeur aleatoire inutilisable ;
# 3. desactive les comptes (is_active = false).
#
# Idempotent : peut etre relance sans risque.
set -euo pipefail
COMPOSE_DIR="${COMPOSE_DIR:-/opt/xpeditis/data-node}"
ENV_FILE="${ENV_FILE:-${COMPOSE_DIR}/.env.data}"
[[ -f "$ENV_FILE" ]] || { echo "Fichier d'environnement introuvable : $ENV_FILE" >&2; exit 1; }
# shellcheck disable=SC1090
set -a; source "$ENV_FILE"; set +a
psql_run() {
docker compose -f "${COMPOSE_DIR}/docker-compose.data.yml" --env-file "$ENV_FILE" \
exec -T -u postgres postgres psql -v ON_ERROR_STOP=1 -d "$POSTGRES_DB" "$@"
}
# Hash Argon2id de "Password123!" pose par la migration SeedTestUsers.
SEED_HASH='$argon2id$v=19$m=65536,t=3,p=4$Uj+yeQiaqgBFqyTJ5FX3Cw$wpRCYORyFwjQFSuO3gpmzh10gx9wjYFOCvVZ8TVaP8Q'
echo ">>> Etat avant intervention"
psql_run -v seed_hash="$SEED_HASH" <<'SQL'
SELECT email, role, is_active, password_hash = :'seed_hash' AS mot_de_passe_public
FROM users
WHERE email IN ('admin@xpeditis.com','manager@xpeditis.com','user@xpeditis.com');
SQL
echo
echo ">>> Neutralisation"
psql_run -v seed_hash="$SEED_HASH" <<'SQL'
BEGIN;
-- Mot de passe remplace par une valeur aleatoire : le format reste un hash
-- Argon2 valide en apparence, mais aucun mot de passe ne peut y correspondre.
UPDATE users
SET
email = 'seed-desactive-' || substr(id::text, 1, 8) || '@invalid.local',
-- md5(random()) plutot que gen_random_bytes : pas besoin de l'extension
-- pgcrypto, qui n'est pas installee sur cette base.
password_hash = '$argon2id$v=19$m=65536,t=3,p=4$' || md5(random()::text)
|| '$' || md5(random()::text) || md5(clock_timestamp()::text),
is_active = false,
updated_at = NOW()
WHERE email IN ('admin@xpeditis.com', 'manager@xpeditis.com', 'user@xpeditis.com')
AND password_hash = :'seed_hash';
COMMIT;
SQL
echo
echo ">>> Etat apres intervention"
psql_run -c "
SELECT email, role, is_active
FROM users
WHERE email LIKE 'seed-desactive-%@invalid.local';
"
echo
echo ">>> Controle : aucun compte ne doit garder le mot de passe public"
RESTE=$(psql_run -tA -v seed_hash="$SEED_HASH" <<'SQL'
SELECT count(*) FROM users
WHERE email IN ('admin@xpeditis.com','manager@xpeditis.com','user@xpeditis.com')
AND password_hash = :'seed_hash';
SQL
)
if [[ "$RESTE" != "0" ]]; then
echo "ECHEC : ${RESTE} compte(s) de demonstration acceptent encore Password123!." >&2
exit 1
fi
echo "OK : aucun compte de demonstration n'accepte le mot de passe public."
echo
echo ">>> Organisations de demonstration presentes (a examiner, non modifiees)"
# Non supprimees automatiquement : les comptes desactives y sont rattaches, et
# une suppression en cascade toucherait aussi audit_logs.
psql_run -c "
SELECT id, name, created_at
FROM organizations
WHERE name IN ('Test Freight Forwarder Inc.','Demo Shipping Company','Sample Shipper Ltd.');
"
cat <<'NEXT'
-----------------------------------------------------------------------------
Etape suivante : disposer d'un administrateur.
La migration 1756000000001-BootstrapAdminFromEnv s'en charge normalement, a
partir de BOOTSTRAP_ADMIN_EMAIL (ConfigMap). Si elle a ete ignoree parce qu'un
ADMIN actif existait alors -- typiquement le compte de demonstration que ce
script vient de neutraliser -- relancez simplement le Job de migration :
kubectl -n xpeditis-prod delete job -l app.kubernetes.io/name=xpeditis-migrate
# puis redeployez, ou rejouez le Job pour le tag courant
Elle ne rejouera pas les migrations deja appliquees ; pour forcer uniquement
l'amorcage, creez votre compte a la main :
1. Inscrivez-vous sur https://app.xpeditis.com/fr/register
2. Promouvez le compte :
UPDATE users SET role = 'ADMIN' WHERE email = '<votre adresse>';
Dans les deux cas, verifiez qu'il n'existe qu'un seul ADMIN actif :
SELECT email, role, is_active FROM users WHERE role = 'ADMIN';
Puis controlez depuis l'exterieur que l'ancien compte est bien mort :
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
https://api.xpeditis.com/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@xpeditis.com","password":"Password123!"}'
# Attendu : 401 (et surtout pas 200 ni 201)
-----------------------------------------------------------------------------
NEXT