Suite de la revue de sécurité (commit précédent) : ni /auth/login (mot de passe COORDINATION) ni /auth/magic-link n'avaient de limite de tentatives — aucun verrou de compte, aucun throttling, avant ouverture au public. `app/auth/rate_limit.py` : limiteur en mémoire (un seul processus uvicorn, cf. Dockerfile), à créneau glissant, par clé (édition, email) plutôt que par IP — le VPS ne transmet pas encore l'IP réelle du client à travers Caddy, et le risque réel ici est de deviner le mot de passe d'un compte connu (peu de comptes protégés par mot de passe), pas une attaque distribuée sur des emails au hasard. - `/auth/login` : 5 tentatives/minute → 429 `error.too_many_requests`. - `/auth/magic-link` : 3 demandes/5 min (évite aussi le spam d'emails vers une victime ou vers le SMTP). Fixture `_reset_rate_limiters` (autouse) pour ne pas faire fuiter l'état d'un test à l'autre. Testé : dépassement, indépendance des clés, expiration de la fenêtre, scénarios bout en bout sur les deux endpoints. Non inclus dans ce commit (édités en parallèle par l'utilisateur, laissés en l'état) : backend/app/seed.py, frontend/tailwind.config.js. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
164 lines
7.0 KiB
Markdown
164 lines
7.0 KiB
Markdown
# Déploiement
|
|
|
|
Le pipeline [`.gitlab-ci.yml`](../.gitlab-ci.yml) se déclenche sur **chaque
|
|
push** :
|
|
|
|
1. tests (backend : `ruff` + `pytest` + `alembic check` ; frontend :
|
|
`typecheck` + `lint` + `build` + `smoke`) ;
|
|
2. sur `main` seulement : build des images `backend` et `frontend`, poussées
|
|
sur le registre de conteneurs du projet GitLab ;
|
|
3. copie de `deploy/docker-compose.prod.yml` sur le VPS par SSH ;
|
|
4. `docker compose pull`, migration Alembic, `up -d`.
|
|
|
|
Le VPS n'expose rien directement : un **Caddy déjà installé sur le
|
|
serveur** (service système, hors de ce dépôt) reçoit le trafic public sur
|
|
443 et reverse-proxy vers les deux conteneurs, qui n'écoutent que sur
|
|
`127.0.0.1`.
|
|
|
|
## Avant d'ouvrir au public — check-list sécurité
|
|
|
|
- `JWT_SECRET` : une vraie valeur générée (`openssl rand -hex 32`), jamais la
|
|
valeur par défaut de `.env.example`. Le backend refuse de démarrer en
|
|
`ENVIRONMENT=production` sinon (`app/config.py:check_production_secrets`).
|
|
- `EMAIL_ENABLED=true` avec un SMTP qui fonctionne (`docker compose run --rm
|
|
backend uv run python -m app.email.check <destinataire>` pour vérifier) —
|
|
sinon les liens magiques sont journalisés en clair au lieu d'être envoyés
|
|
(le backend avertit au démarrage tant que c'est le cas, voir plus bas).
|
|
- Édition créée via `python -m app.bootstrap` (étape 3), **jamais** via
|
|
`python -m app.seed` : ce dernier crée un compte de démonstration
|
|
(`coordination@lucioles.test`) avec un mot de passe versionné dans le
|
|
dépôt — un vrai identifiant de connexion COORDINATION public si la base de
|
|
démo devient la base de production.
|
|
- `CORS_ORIGINS` : sans objet ici tant que le frontend appelle l'API en
|
|
même origine via `/api/*` (voir le vhost Caddy, étape 5) — à ne renseigner
|
|
que si un autre client (domaine séparé, appli mobile) doit appeler l'API
|
|
directement.
|
|
- `/auth/login` et `/auth/magic-link` sont limités en tentatives par email
|
|
(5/min et 3/5 min) — voir `app/auth/rate_limit.py`. Limité par email et
|
|
non par IP (le VPS ne transmet pas encore l'IP réelle du client à travers
|
|
Caddy) : suffisant contre la force brute sur un compte connu, pas contre
|
|
une attaque distribuée sur de nombreux emails différents.
|
|
|
|
## Étape 0 — pousser le dépôt sur GitLab
|
|
|
|
```bash
|
|
# Créer d'abord un projet vide sur GitLab (sans README ni .gitignore), puis :
|
|
git remote add origin git@gitlab.com:<namespace>/<projet>.git
|
|
git push -u origin main
|
|
```
|
|
|
|
## Variables CI/CD à définir (Paramètres → CI/CD → Variables)
|
|
|
|
| Variable | Description |
|
|
|-------------------|-------------------------------------------------------------------|
|
|
| `SSH_HOST` | Hôte du VPS |
|
|
| `SSH_USER` | Utilisateur SSH |
|
|
| `SSH_KEY` | Clé privée SSH (format OpenSSH) — **masquée**, sur `main` seulement|
|
|
| `DEPLOY_PATH` | Dossier du projet sur le VPS (ex. `/opt/benevoles`) |
|
|
| `VITE_EDITION_ID` | UUID de l'édition de production — **à ajouter après** l'étape 3 |
|
|
|
|
`CI_REGISTRY`, `CI_REGISTRY_USER`, `CI_REGISTRY_PASSWORD` et
|
|
`CI_REGISTRY_IMAGE` sont fournies automatiquement par GitLab, rien à
|
|
configurer.
|
|
|
|
La clé publique correspondant à `SSH_KEY` doit être dans le
|
|
`~/.ssh/authorized_keys` de `SSH_USER` sur le VPS.
|
|
|
|
## Étape 1 — préparation du VPS (une fois)
|
|
|
|
```bash
|
|
mkdir -p /opt/benevoles
|
|
cd /opt/benevoles
|
|
cat > .env <<'EOF'
|
|
POSTGRES_USER=benevoles
|
|
POSTGRES_PASSWORD=<mot de passe fort>
|
|
POSTGRES_DB=benevoles
|
|
JWT_SECRET=<secret fort ; openssl rand -hex 32>
|
|
PUBLIC_BASE_URL=https://benevoles.mon-domaine.fr
|
|
|
|
# Emails (récapitulatifs de planning + liens de connexion). Laisser
|
|
# EMAIL_ENABLED=false tant que le SMTP n'est pas prêt : les emails sont alors
|
|
# seulement journalisés — un jeton de lien magique en clair dans les logs du
|
|
# conteneur permet de se connecter à la place de n'importe qui, donc à
|
|
# repasser à true avant l'ouverture au public (le backend affiche un
|
|
# avertissement au démarrage tant que c'est le cas).
|
|
EMAIL_ENABLED=true
|
|
SMTP_HOST=smtp.mon-domaine.fr
|
|
SMTP_PORT=587
|
|
SMTP_USER=no-reply@mon-domaine.fr
|
|
SMTP_PASSWORD=<mot de passe SMTP>
|
|
SMTP_STARTTLS=true
|
|
SMTP_SSL=false
|
|
SMTP_FROM=Festival <no-reply@mon-domaine.fr>
|
|
EOF
|
|
```
|
|
|
|
Le pipeline dépose `docker-compose.prod.yml` dans ce dossier et l'utilise tel
|
|
quel — il n'y a rien d'autre à y copier.
|
|
|
|
## Étape 2 — premier déploiement (backend + base seuls)
|
|
|
|
Le frontend a besoin de l'UUID d'une édition pour être construit (voir plus
|
|
bas) : le tout premier déploiement se fait donc en deux temps.
|
|
|
|
1. Sur GitLab, lancer le pipeline sur `main` normalement (Push → CI/CD →
|
|
Pipelines, ou un simple commit). Le job `build-frontend` échouera ou
|
|
produira une image sans édition tant que `VITE_EDITION_ID` n'existe pas :
|
|
c'est attendu, on l'ignore pour l'instant.
|
|
2. Une fois `build-backend` et `deploy` passés, `db` et `backend` tournent
|
|
sur le VPS.
|
|
|
|
## Étape 3 — créer l'édition de production
|
|
|
|
```bash
|
|
ssh SSH_USER@SSH_HOST
|
|
cd /opt/benevoles
|
|
docker compose -f docker-compose.prod.yml run --rm backend \
|
|
uv run python -m app.bootstrap \
|
|
--name "Festival 2026" --start 2026-07-01 --end 2026-07-04 \
|
|
--email coordination@mon-domaine.fr --password <mot-de-passe-solide>
|
|
```
|
|
|
|
La commande affiche l'UUID de l'édition créée. Récupérer aussi celui-ci à
|
|
tout moment avec :
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml exec -T db \
|
|
psql -U benevoles -tAc "select id from edition order by created_at desc limit 1"
|
|
```
|
|
|
|
## Étape 4 — brancher le frontend sur cette édition
|
|
|
|
Dans GitLab, ajouter la variable CI/CD `VITE_EDITION_ID` avec cet UUID, puis
|
|
relancer le pipeline sur `main` (bouton *Retry* sur le dernier pipeline, ou un
|
|
nouveau commit). Cette fois `build-frontend` construit une image qui pointe
|
|
sur la bonne édition, et `deploy` démarre le service `frontend`.
|
|
|
|
`VITE_EDITION_ID` ne change plus ensuite : elle ne se touche à nouveau que
|
|
pour une nouvelle édition (festival de l'année suivante) qu'on voudrait
|
|
publier sur ce même domaine.
|
|
|
|
## Étape 5 — le vhost Caddy
|
|
|
|
Caddy tourne déjà sur ce serveur (service système) : ajouter le bloc
|
|
[`deploy/Caddyfile.benevoles`](./Caddyfile.benevoles) à son `Caddyfile` (ou au
|
|
fichier qu'il importe pour les sites), avec le vrai nom de domaine, puis :
|
|
|
|
```bash
|
|
sudo caddy validate --config /etc/caddy/Caddyfile
|
|
sudo systemctl reload caddy
|
|
```
|
|
|
|
Le DNS du domaine doit déjà pointer (enregistrement `A`/`AAAA`) vers ce VPS —
|
|
Caddy obtient et renouvelle seul le certificat HTTPS (Let's Encrypt) au
|
|
premier accès.
|
|
|
|
Vérifier ensuite que `PUBLIC_BASE_URL` dans le `.env` du VPS (étape 1)
|
|
correspond bien à `https://<ce domaine>` — c'est cette valeur qui est utilisée
|
|
dans les liens des emails envoyés aux bénévoles.
|
|
|
|
## Déploiements suivants
|
|
|
|
Un simple push sur `main` suffit : tests → build → déploiement → migration →
|
|
redémarrage, sans aucune intervention sur le VPS ni sur Caddy.
|