test: partage un jeu de démonstration et des comptes de test
db/seeds/ était vide, et chaque outil de test semait ses données à la main : un site et deux relevés dans dast.yml, rien pour le reste. - db/seeds/demo.sql : trois sites, 72 heures de relevés relatives à now(), une prévision par site, quatorze alertes de tous types et sévérités, un rapport de dérive par site. Rejouable. - scripts/comptes-test.sh : administrateur par la CLI, lecteur et opérateur activés, et un compte laissé sur son mot de passe temporaire ; identifiants écrits en JSON (mode 600). Fonctionne en natif ou contre la stack Compose (BASE_URL, APP_CLI). - scripts/dast-token.sh s'appuie désormais dessus ; dast.yml sème demo.sql.
This commit is contained in:
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Contrainte : réservé à une base JETABLE (CI, e2e, charge sur le poste) - comptes-test.sh. Crée
|
||||
# un administrateur par la CLI, puis un lecteur, un opérateur et un compte laissé sur son mot de
|
||||
# passe temporaire, et écrit leurs identifiants en JSON dans $COMPTES_FICHIER.
|
||||
# Piège : un compte neuf est en `must_change_password`, que toute route gardée refuse. Seul
|
||||
# `premiere_connexion` garde ce statut : c'est le parcours du premier login de l'e2e.
|
||||
# Piège : `create-admin` refuse un second administrateur actif. Sur une base qui en a déjà un
|
||||
# (recette, poste peuplé), créer les comptes depuis l'interface d'administration.
|
||||
#
|
||||
# BASE_URL vise l'API (http://localhost:8000 par défaut, ou le proxy en https). APP_CLI lance la
|
||||
# CLI du backend : depuis apps/backend par défaut, ou `docker compose ... exec -T backend python
|
||||
# -m app.cli` contre la stack conteneurisée.
|
||||
|
||||
set -euo pipefail
|
||||
# Sans lui, une erreur dans `$(...)` ne s'arrête pas : la substitution rendrait un mot de passe vide.
|
||||
shopt -s inherit_errexit
|
||||
|
||||
BASE_URL="${BASE_URL:-http://localhost:8000}"
|
||||
API="$BASE_URL/api/v1"
|
||||
COMPTES_FICHIER="${COMPTES_FICHIER:?COMPTES_FICHIER=chemin du fichier JSON requis}"
|
||||
read -ra CLI <<<"${APP_CLI:-uv run --frozen --no-sync --no-build python -m app.cli}"
|
||||
SUFFIXE="$(openssl rand -hex 4)"
|
||||
|
||||
CURL=(curl -fsS)
|
||||
[[ "$BASE_URL" == https://* ]] && CURL+=(--insecure)
|
||||
|
||||
# Classes exigées par le validateur : majuscule, minuscule, chiffre, caractère spécial.
|
||||
nouveau_mot_de_passe() { echo "Test-$(openssl rand -hex 12)-Aa1!"; }
|
||||
|
||||
journal() { echo "comptes-test: $*" >&2; }
|
||||
|
||||
connexion() {
|
||||
local email="$1" mot_de_passe="$2"
|
||||
"${CURL[@]}" -X POST "$API/auth/login" -H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg e "$email" --arg p "$mot_de_passe" '{email:$e, password:$p}')" \
|
||||
| jq -r '.access_token'
|
||||
}
|
||||
|
||||
# `/auth/password` rend un nouveau jeton d'accès : pas de reconnexion, donc pas de second hachage
|
||||
# Argon2id ni de requête de plus dans la zone `auth` de nginx (30 par minute).
|
||||
changer_mot_de_passe() {
|
||||
local jeton="$1" ancien="$2" nouveau="$3"
|
||||
"${CURL[@]}" -X POST "$API/auth/password" \
|
||||
-H "Authorization: Bearer $jeton" -H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg a "$ancien" --arg n "$nouveau" '{current_password:$a, new_password:$n}')" \
|
||||
| jq -r '.access_token'
|
||||
}
|
||||
|
||||
creer_compte() {
|
||||
local jeton_admin="$1" email="$2" role="$3" reponse temporaire
|
||||
reponse="$("${CURL[@]}" -X POST "$API/users" -H "Authorization: Bearer $jeton_admin" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg e "$email" --arg r "$role" '{email:$e, role:$r}')")"
|
||||
temporaire="$(jq -r '.temporary_password // empty' <<<"$reponse")"
|
||||
[[ -n "$temporaire" ]] || { journal "mot de passe temporaire absent de la réponse : $reponse"; exit 1; }
|
||||
echo "$temporaire"
|
||||
}
|
||||
|
||||
# Rend le mot de passe définitif d'un compte créé par `creer_compte`.
|
||||
activer_compte() {
|
||||
local email="$1" temporaire="$2" definitif jeton
|
||||
definitif="$(nouveau_mot_de_passe)"
|
||||
jeton="$(connexion "$email" "$temporaire")"
|
||||
changer_mot_de_passe "$jeton" "$temporaire" "$definitif" >/dev/null
|
||||
echo "$definitif"
|
||||
}
|
||||
|
||||
EMAIL_ADMIN="test-admin-$SUFFIXE@enervision.fr"
|
||||
journal "création de l'administrateur $EMAIL_ADMIN"
|
||||
if ! SORTIE="$("${CLI[@]}" create-admin --email "$EMAIL_ADMIN" --generate)"; then
|
||||
journal "la création de l'administrateur a échoué : $SORTIE"
|
||||
exit 1
|
||||
fi
|
||||
MDP_TEMPORAIRE_ADMIN="$(sed -n 's/^Mot de passe généré, il ne sera plus affiché : //p' <<<"$SORTIE" | tr -d '\r')"
|
||||
[[ -n "$MDP_TEMPORAIRE_ADMIN" ]] || { journal "mot de passe introuvable dans la sortie : $SORTIE"; exit 1; }
|
||||
|
||||
MDP_ADMIN="$(nouveau_mot_de_passe)"
|
||||
JETON_ADMIN="$(connexion "$EMAIL_ADMIN" "$MDP_TEMPORAIRE_ADMIN")"
|
||||
JETON_ADMIN="$(changer_mot_de_passe "$JETON_ADMIN" "$MDP_TEMPORAIRE_ADMIN" "$MDP_ADMIN")"
|
||||
|
||||
EMAIL_LECTEUR="test-lecteur-$SUFFIXE@enervision.fr"
|
||||
EMAIL_OPERATEUR="test-operateur-$SUFFIXE@enervision.fr"
|
||||
EMAIL_PREMIERE="test-premiere-connexion-$SUFFIXE@enervision.fr"
|
||||
journal "création du lecteur, de l'opérateur et du compte de première connexion"
|
||||
TEMPORAIRE_LECTEUR="$(creer_compte "$JETON_ADMIN" "$EMAIL_LECTEUR" lecteur)"
|
||||
MDP_LECTEUR="$(activer_compte "$EMAIL_LECTEUR" "$TEMPORAIRE_LECTEUR")"
|
||||
TEMPORAIRE_OPERATEUR="$(creer_compte "$JETON_ADMIN" "$EMAIL_OPERATEUR" operateur)"
|
||||
MDP_OPERATEUR="$(activer_compte "$EMAIL_OPERATEUR" "$TEMPORAIRE_OPERATEUR")"
|
||||
MDP_PREMIERE="$(creer_compte "$JETON_ADMIN" "$EMAIL_PREMIERE" lecteur)"
|
||||
|
||||
umask 077
|
||||
jq -n \
|
||||
--arg ae "$EMAIL_ADMIN" --arg ap "$MDP_ADMIN" \
|
||||
--arg le "$EMAIL_LECTEUR" --arg lp "$MDP_LECTEUR" \
|
||||
--arg oe "$EMAIL_OPERATEUR" --arg op "$MDP_OPERATEUR" \
|
||||
--arg pe "$EMAIL_PREMIERE" --arg pp "$MDP_PREMIERE" \
|
||||
'{
|
||||
admin: {email: $ae, password: $ap},
|
||||
lecteur: {email: $le, password: $lp},
|
||||
operateur: {email: $oe, password: $op},
|
||||
premiere_connexion: {email: $pe, password: $pp}
|
||||
}' >"$COMPTES_FICHIER"
|
||||
journal "identifiants écrits dans $COMPTES_FICHIER"
|
||||
+12
-66
@@ -1,78 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prépare le scan DAST : crée un compte `lecteur` sur une API déjà démarrée, lui fait passer le
|
||||
# changement de mot de passe obligatoire, et écrit son jeton d'accès sur la sortie standard.
|
||||
#
|
||||
# Piège : un compte neuf est en `must_change_password`, et toute route gardée le refuse tant que
|
||||
# le mot de passe n'a pas été changé. Sans cette étape, ZAP ne verrait que 403 sur les routes
|
||||
# gardées et le scan ne testerait rien de l'API authentifiée.
|
||||
#
|
||||
# Contrainte : prépare le scan DAST sur une base JETABLE - dast-token.sh. Crée les comptes de
|
||||
# test (scripts/comptes-test.sh, mêmes variables) et écrit sur la sortie standard le seul jeton
|
||||
# d'accès du `lecteur`.
|
||||
# Contrainte : le compte du scan est `lecteur`, jamais `admin`. Un scan actif avec un jeton admin
|
||||
# frapperait POST /users ou la réinitialisation de mots de passe pour de bon.
|
||||
#
|
||||
# L'administrateur n'existe que pour créer ce compte (l'API n'a pas d'inscription publique).
|
||||
# À lancer depuis apps/backend, dans un environnement où DATABASE_URL et APP_SECRET_KEY visent
|
||||
# une base JETABLE : le script y crée deux comptes.
|
||||
|
||||
set -euo pipefail
|
||||
shopt -s inherit_errexit
|
||||
|
||||
ICI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_URL="${BASE_URL:-http://localhost:8000}"
|
||||
API="$BASE_URL/api/v1"
|
||||
SUFFIXE="$(openssl rand -hex 4)"
|
||||
EMAIL_ADMIN="dast-admin-$SUFFIXE@enervision.fr"
|
||||
EMAIL_LECTEUR="dast-lecteur-$SUFFIXE@enervision.fr"
|
||||
COMPTES="$(mktemp)"
|
||||
trap 'rm -f "$COMPTES"' EXIT
|
||||
|
||||
# Classes exigées par le validateur : majuscule, minuscule, chiffre, caractère spécial.
|
||||
nouveau_mot_de_passe() { echo "Dast-$(openssl rand -hex 12)-Aa1!"; }
|
||||
COMPTES_FICHIER="$COMPTES" BASE_URL="$BASE_URL" "$ICI/comptes-test.sh"
|
||||
|
||||
# Tout ce qui n'est pas la sortie finale part sur stderr : la sortie standard ne porte que le jeton.
|
||||
journal() { echo "dast-token: $*" >&2; }
|
||||
JETON="$(curl -fsS -X POST "$BASE_URL/api/v1/auth/login" -H 'Content-Type: application/json' \
|
||||
-d "$(jq '.lecteur | {email, password}' "$COMPTES")" | jq -r '.access_token')"
|
||||
|
||||
connexion() {
|
||||
local email="$1" mot_de_passe="$2"
|
||||
curl -fsS -X POST "$API/auth/login" -H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg e "$email" --arg p "$mot_de_passe" '{email:$e, password:$p}')" \
|
||||
| jq -r '.access_token'
|
||||
}
|
||||
CODE="$(curl -sS -o /dev/null -w '%{http_code}' "$BASE_URL/api/v1/sites" -H "Authorization: Bearer $JETON")"
|
||||
[[ "$CODE" == "200" ]] || { echo "dast-token: GET /sites répond $CODE avec le jeton du lecteur, attendu 200" >&2; exit 1; }
|
||||
|
||||
# Rend le nouveau jeton d'accès : `/auth/password` en émet un (avec l'`iat` de la session en
|
||||
# cours, cf. le piège documenté dans `app/api/deps.py`), pas seulement une confirmation. S'y fier
|
||||
# évite une reconnexion, donc un second hachage Argon2id (19456 Kio) et un aller-retour de
|
||||
# refresh-token superflus sur le chemin critique de la CI.
|
||||
changer_mot_de_passe() {
|
||||
local jeton="$1" ancien="$2" nouveau="$3"
|
||||
curl -fsS -X POST "$API/auth/password" \
|
||||
-H "Authorization: Bearer $jeton" -H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg a "$ancien" --arg n "$nouveau" '{current_password:$a, new_password:$n}')" \
|
||||
| jq -r '.access_token'
|
||||
}
|
||||
|
||||
journal "création de l'administrateur $EMAIL_ADMIN"
|
||||
if ! SORTIE="$(uv run --frozen --no-sync --no-build python -m app.cli create-admin --email "$EMAIL_ADMIN" --generate)"; then
|
||||
journal "la création de l'administrateur a échoué :"
|
||||
journal "$SORTIE"
|
||||
exit 1
|
||||
fi
|
||||
MDP_ADMIN="$(sed -n 's/^Mot de passe généré, il ne sera plus affiché : //p' <<<"$SORTIE")"
|
||||
[[ -n "$MDP_ADMIN" ]] || { journal "mot de passe administrateur introuvable dans la sortie :"; journal "$SORTIE"; exit 1; }
|
||||
|
||||
JETON="$(connexion "$EMAIL_ADMIN" "$MDP_ADMIN")"
|
||||
NOUVEAU_ADMIN="$(nouveau_mot_de_passe)"
|
||||
JETON="$(changer_mot_de_passe "$JETON" "$MDP_ADMIN" "$NOUVEAU_ADMIN")"
|
||||
|
||||
journal "création du lecteur $EMAIL_LECTEUR"
|
||||
REPONSE="$(curl -fsS -X POST "$API/users" -H "Authorization: Bearer $JETON" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg e "$EMAIL_LECTEUR" '{email:$e, role:"lecteur"}')")"
|
||||
MDP_TEMPORAIRE="$(jq -r '.temporary_password // empty' <<<"$REPONSE")"
|
||||
[[ -n "$MDP_TEMPORAIRE" ]] || { journal "mot de passe temporaire introuvable dans la réponse de POST /users :"; journal "$REPONSE"; exit 1; }
|
||||
|
||||
JETON="$(connexion "$EMAIL_LECTEUR" "$MDP_TEMPORAIRE")"
|
||||
NOUVEAU_LECTEUR="$(nouveau_mot_de_passe)"
|
||||
JETON="$(changer_mot_de_passe "$JETON" "$MDP_TEMPORAIRE" "$NOUVEAU_LECTEUR")"
|
||||
|
||||
# Vérifie que le jeton ouvre bien une route gardée avant de le rendre.
|
||||
CODE="$(curl -sS -o /dev/null -w '%{http_code}' "$API/sites" -H "Authorization: Bearer $JETON")"
|
||||
[[ "$CODE" == "200" ]] || { journal "GET /sites répond $CODE avec le jeton du lecteur, attendu 200"; exit 1; }
|
||||
|
||||
journal "jeton du lecteur prêt"
|
||||
echo "$JETON"
|
||||
|
||||
Reference in New Issue
Block a user