Files
Metamorph/tasks/todo.md

285 lines
20 KiB
Markdown
Raw Normal View History

2026-05-11 06:16:00 +02:00
---
type: todo
date: "2026-05-08"
tags: [todo, plan]
status: in_progress
project: Metamorph
spec: tasks/spec.md
---
# Metamorph — Plan d'implémentation
> Découpage en 14 milestones livrables indépendamment. Chaque milestone a une **DoD** vérifiable. Cocher au fil de l'eau, documenter les écarts dans `CHANGELOG.md`, retours d'expérience dans `tasks/lessons.md`.
## Convention
- ☐ = à faire · ☑ = fait · ⚠ = bloqué (commenter) · ↻ = en cours
- Branches : `feature/m<N>-<slug>` · commits : `feat(m<N>): …` / `fix(m<N>): …`
- Chaque PR doit : passer lint/typecheck, mettre à jour `CHANGELOG.md`, mettre à jour `README.md` si surface utilisateur.
- **Chaque milestone livre un fichier `tasks/testing-m<N>.md`** (procédure manuelle + automatisée) **et au moins un spec Playwright `e2e/tests/m<N>-*.spec.ts`**.
- À la fin de chaque milestone : lancer le subagent `spec-reviewer` (HARD RULE 4 du CLAUDE.md global) avant de marquer le milestone done.
---
## M0 — Bootstrap repo & infra ☐
**But** : squelette buildable de bout en bout sans aucune feature métier.
-`backend/` (Flask 3, Python 3.12, `pyproject.toml` avec uv ou poetry, structure `app/{api,core,db,models,services,i18n}`)
-`frontend/` (Vite + React 18 + TS strict, Tailwind 3, ESLint + Prettier, alias `@/`)
- ☐ Tokens design `tasks/design.md` traduits en `frontend/tailwind.config.ts` (palette CSS vars, typo JetBrains Mono / IBM Plex Sans, radii 3/4/6/10).
- ☐ Composants UI de base : `<Card>`, `<Tag>`, `<SectionHeader>` (avec `// `), `<FlowNode>`, `<Button>` — fidèles au design.
-`docker-compose.yml` : services `api`, `db` (postgres:16-alpine), `front` (nginx servant le bundle Vite).
- ☐ Dockerfile multi-stage par service ; volumes nommés `metamorph_db`, `metamorph_evidence`.
-`Makefile` : `dev`, `build`, `up`, `down`, `migrate`, `seed-mitre`, `lint`, `test`.
- ☐ Pré-commit hook : `ruff` (back), `eslint`+`tsc --noEmit` (front).
-`README.md` minimal (run en dev, run en prod, variables d'env attendues).
-`.gitignore` : `.env`, `*.exe`, `*.dll`, `__pycache__/`, `node_modules/`, `dist/`, `data/`.
-`.env.example` documenté (`POSTGRES_*`, `JWT_SECRET`, `LOG_LEVEL`, `FRONT_ORIGIN`).
- ☐ Logs JSON structurés sur stdout (`python-json-logger`).
**DoD** : `make up` démarre les 3 conteneurs ; `curl http://localhost:${HOST_FRONT_PORT:-8080}/api/v1/health` renvoie `{ "status": "ok", "version": "..." }` (proxifié par nginx via `api:8000`) ; le front sur `:8080` affiche une page d'accueil au design RTOps ; **`make e2e` passe les 8 tests Playwright** ; rapport HTML dans `e2e/playwright-report/`. Procédure complète : `tasks/testing-m0.md`. *En prod, la TLS est terminée par un reverse proxy externe (cf. spec §6 NF-network) — la stack compose ne sert que du HTTP.*
---
## M1 — Schéma DB & migrations Alembic ☐
**But** : modèle de données complet versionné, sans logique métier.
- ☐ Configurer SQLAlchemy 2.x + Alembic.
- ☐ Tables auth/RBAC : `users`, `groups`, `permissions`, `user_groups`, `group_permissions`, `invitations`, `refresh_tokens`.
- ☐ Tables MITRE : `mitre_tactics`, `mitre_techniques`, `mitre_subtechniques` (avec `external_id`, `name`, `description`, `url`).
- ☐ Tables templates : `test_templates`, `test_template_mitre_tags` (jointure many-to-many tactic/technique/subtechnique), `scenario_templates`, `scenario_template_tests` (avec `position`).
- ☐ Tables missions : `missions`, `mission_members`, `mission_scenarios` (snapshot), `mission_tests` (snapshot + state), `mission_test_mitre_tags`, `mission_categories` (custom).
- ☐ Tables exécution : `evidence_files` (FK `mission_test_id`, `sha256`, `mime`, `size_bytes`, `storage_path`, `original_filename`).
- ☐ Tables paramétrage : `detection_levels` (clé, label_fr, label_en, color_token, position, is_default), `settings` (key/value).
- ☐ Table notifications : `notifications` (FK user, type, payload JSONB, read_at, created_at).
- ☐ Soft delete : colonne `deleted_at` partout sauf tables jointures simples ; index partiel `WHERE deleted_at IS NULL`.
- ☐ Audit minimal : `created_at`, `updated_at` partout.
- ☐ Migration initiale Alembic + commande `make migrate`.
**DoD** : `make migrate` applique le schéma sur une DB vide ; `\dt` montre toutes les tables ; les contraintes FK et les index sont en place.
---
## M2 — Auth, bootstrap, invitations ☑
**But** : un humain peut s'inscrire et se connecter.
- ☐ Hash mot de passe : `argon2-cffi` (params modérés, `time_cost=2, memory_cost=64MB`).
- ☐ JWT : `pyjwt`, HS256, claims `sub`, `iat`, `exp`, `type` (access|refresh), `jti`. Access 1h, refresh 30j.
- ☐ Stockage refresh tokens en DB (rotation à chaque usage, révocation au logout).
- ☐ Endpoints : `POST /auth/login`, `POST /auth/refresh`, `POST /auth/logout`, `GET /auth/me`, `POST /auth/change-password`.
- ☐ Bootstrap : commande `flask metamorph print-install-token` génère + persiste un token unique au 1er démarrage (si table `users` vide), écrit dans les logs au boot.
- ☐ Endpoint `POST /setup` : consomme le token d'install, crée le 1er admin (groupe `admin` seedé).
- ☐ Invitations : `POST /invitations` (admin, génère token 7j), `GET /invitations/{token}` (preview), `POST /invitations/{token}/accept` (création compte avec password choisi).
- ☐ Middleware d'auth Flask (`@require_auth`, `@require_perm("...")`).
- ☐ Rate-limit `flask-limiter` sur `/auth/*` (10/min/IP).
- ☐ Front : pages `/login`, `/setup`, `/register?token=…`, `/profile`. Stockage access en mémoire, refresh en cookie HTTPOnly Secure SameSite=Strict.
- ☐ Hook React `useAuth()` + interceptor TanStack Query (refresh auto sur 401).
- ☐ CORS strict (origin `FRONT_ORIGIN`).
**DoD** : `flask metamorph print-install-token` → /setup → création admin → login → /auth/me OK ; admin crée invitation → user s'inscrit via lien → login OK ; `/auth/refresh` renouvelle correctement.
---
## M3 — RBAC : groupes, permissions, gestion users ☑
**But** : admin peut composer des groupes custom et y assigner des users.
- ☐ Seed des permissions atomiques (familles spec §4) :
- `user.{read,create,update,delete}`, `group.{read,create,update,delete}`, `invitation.{create,revoke,read}`
- `test_template.{read,create,update,delete}`, `scenario_template.{read,create,update,delete}`
- `mission.{read,create,update,archive,delete}`, `mission.write_red_fields`, `mission.write_blue_fields`
- `detection_level.{read,update}`, `setting.{read,update}`, `mitre.sync`
- ☐ Seed des 3 groupes par défaut (`admin` = toutes, `redteam` = templates(read) + missions(read,create,update) + write_red_fields, `blueteam` = templates(read) + missions(read) + write_blue_fields).
- ☐ Endpoints CRUD `groups`, `permissions` (lecture seule), `users` (admin), `users/{id}/groups` (assign).
- ☐ Décorateur `@require_perm` qui vérifie l'union des perms via tous les groupes du user.
- ☐ Front : page Admin > Users (liste, recherche, modale d'édition des groupes), Admin > Groups (CRUD + multi-select des perms), Admin > Invitations (liste, créer, révoquer).
- ☐ UI : on n'affiche pas les actions interdites (mais le serveur reste l'arbitre).
**DoD** : un admin peut créer un groupe `pentest-2026-Q2` avec uniquement `mission.read` + `mission.write_red_fields`, l'attribuer à Bob ; Bob voit les missions auxquelles il est membre mais ne peut pas écrire dans les champs blue (HTTP 403 au niveau API).
---
## M4 — MITRE ATT&CK Enterprise ☐
**But** : le référentiel ATT&CK est interrogeable et tagué sur les tests.
- ☐ Téléchargement initial du STIX bundle Enterprise depuis `github.com/mitre/cti` (vérifier hash, pin une version).
- ☐ Parser STIX → tables `mitre_tactics` / `mitre_techniques` / `mitre_subtechniques` (extraire `external_id` ATT&CK, `name`, `description`, `url`, relations technique↔tactic).
- ☐ Commande `flask metamorph seed-mitre [--source <path|url>]`.
- ☐ Endpoint `POST /mitre/sync` (perm `mitre.sync`) qui re-pull depuis l'URL configurée (setting `mitre_source_url`).
- ☐ Persister `mitre_last_sync` dans `settings`.
- ☐ Endpoint `GET /mitre/tactics`, `/mitre/techniques?tactic=…`, `/mitre/subtechniques?technique=…` (pagination + recherche full-text simple sur `name`).
- ☐ Front : composant `<MitreTagPicker>` (autocomplete combiné Tactic > Technique > Sub-technique, multi-select).
**DoD** : après `make seed-mitre`, `GET /mitre/tactics` retourne 14 tactics Enterprise ; le picker permet de tagger un test avec « TA0002 / T1059.001 » et l'enregistrement est persistant.
---
## M5 — Templates : tests unitaires & scénarios ☐
**But** : admin peut bâtir le catalogue réutilisable.
- ☐ Modèle `test_template` : nom, description, objectif, procédure (markdown), prérequis (markdown), résultat attendu red, détection attendue blue, niveau OPSEC (`enum low/med/high`), tags libres (array text), IOCs attendus (array text), tags MITRE (multi).
- ☐ Endpoints CRUD `/test-templates` avec validation pydantic.
- ☐ Modèle `scenario_template` : nom, description, liste ordonnée de tests (`position`).
- ☐ Endpoints CRUD `/scenario-templates`, `PUT /scenario-templates/{id}/tests` (réordonnancement).
- ☐ Front : page Admin > Tests (liste filtrable par tactic / OPSEC / tag), modale d'édition (form complet avec markdown editor — `@uiw/react-md-editor` ou équivalent léger).
- ☐ Front : page Admin > Scénarios, drag-and-drop avec `@dnd-kit/sortable`.
- ☐ Filtres : recherche full-text sur nom/desc, facettes MITRE/OPSEC/tags.
**DoD** : admin crée 5 tests + 1 scénario de 3 tests réordonnés ; recharge la page → ordre persistant ; suppression soft-delete d'un template n'efface pas les scénarios.
---
## M6 — Missions & snapshot ☐
**But** : transformer les templates en missions vivantes.
- ☐ Modèle `mission` : nom, client/cible (texte), date_start, date_end, status (`enum draft/in_progress/completed/archived`), description (markdown), `visibility_mode` figé à `whitebox` v1.
-`mission_members` : (mission_id, user_id, role_hint `red|blue`) — rôle hint informatif, l'autorisation reste portée par les permissions.
- ☐ Lors de la création/modification d'une mission, sélection de scénarios → **snapshot** : copie complète des `scenario_templates` et `test_templates` dans `mission_scenarios` / `mission_tests` (y compris tags MITRE).
-`mission_tests` ajoute : `state` (`enum pending/executed/reviewed_by_blue/skipped/blocked`), `executed_at` (nullable), `executed_at_override` (bool), `red_command`, `red_output`, `red_comment`, `blue_comment`, `detection_level_id` (nullable).
- ☐ Endpoints : `POST /missions`, `GET /missions` (filtré par perms + membership pour les non-admin), `GET /missions/{id}` (avec scénarios+tests), `PUT /missions/{id}` (métadonnées + ajout de scénarios → snapshot), `POST /missions/{id}/transition` (drift de status), `DELETE /missions/{id}` (soft).
- ☐ Front : page Missions (liste + filtres status/client/dates), création (wizard 3 étapes : meta → scénarios → membres), vue mission (header + onglets Tests / Membres / Synthèse / Export).
- ☐ Vue mission : tableau des tests avec colonnes Tactic | Test | Statut | Niveau de détection | Last update, actions selon perms.
**DoD** : red crée une mission avec 1 scénario de 3 tests, ajoute Alice (red) et Bob (blue) ; modification ultérieure d'un test_template ne change rien dans la mission (snapshot préservé).
---
## M7 — Saisie red & blue sur un test ☐
**But** : exécution de la mission, le cœur du produit.
- ☐ Modale ou page dédiée `Mission > Test #N` avec deux zones distinctes (red / blue), bordures accentuées par couleur (rouge / cyan).
- ☐ Côté red : champ commande (mono), output (mono multiline), commentaire markdown, bouton « Marquer exécuté » qui set `state=executed` + `executed_at=now()` ; édition de `executed_at` derrière un toggle « override ».
- ☐ Côté blue : sélecteur `detection_level`, commentaire markdown, zone d'upload multi-fichiers (drag-and-drop).
- ☐ Upload preuves : `POST /missions/{id}/tests/{test_id}/evidence` (multipart, validation extension+MIME+taille≤25Mo, calcul SHA256, stockage `/data/evidence/<mission_id>/<test_id>/<sha256>{ext}`).
-`GET /evidence/{id}` (download, vérif perm) ; `DELETE /evidence/{id}` (soft).
- ☐ Permissions : tout endpoint d'écriture vérifie `mission.write_red_fields` ou `mission.write_blue_fields` selon le champ touché ; les deux peuvent coexister sur un même groupe (pas exclusifs en code).
- ☐ Bouton « Statut » avec choix `executed`, `reviewed_by_blue`, `skipped`, `blocked` (transitions contrôlées : pending↔skipped/blocked, executed→reviewed_by_blue).
- ☐ Indicateur « modifié par X il y a Ns » : polling `GET /missions/{id}/activity?since=…` toutes les 15 s tant que la page est active.
**DoD** : red et blue saisissent en parallèle sans conflit ; un user sans `write_blue_fields` reçoit 403 sur les champs blue ; un fichier .evtx de 24 Mo est uploadé, un de 26 Mo est rejeté ; le hash SHA256 est correct.
---
## M8 — Niveaux de détection custom ☐
**But** : la taxonomie d'icônes du slide est paramétrable.
- ☐ Seed initial : `detected_blocked` (red), `detected_alert` (orange), `logged_only` (yellow), `not_detected` (rose).
- ☐ Endpoints `/detection-levels` : list, create, update (label_fr, label_en, color_token, position, is_default).
- ☐ Garde-fou : empêcher la suppression si utilisé dans des `mission_tests` (proposer désactivation).
- ☐ Front : page Admin > Settings > Detection Levels (table + modale, picker de color_token parmi les 10 accents du design).
**DoD** : admin renomme `not_detected``missed`, ajoute `false_positive` avec accent purple ; les missions existantes affichent les nouveaux libellés ; un blueteamer voit la nouvelle option dans le sélecteur.
---
## M9 — Notifications in-app ☐
**But** : red et blue savent quand l'autre a agi.
- ☐ Service `notify(user_id, type, payload)` appelé sur transitions clés : `test_executed`, `test_reviewed_by_blue`, `evidence_added`, `mission_status_changed`.
- ☐ Endpoints `GET /notifications?unread_only=…`, `POST /notifications/{id}/read`, `POST /notifications/read-all`.
- ☐ Front : badge dans le header avec compteur, dropdown listant les 20 dernières + lien vers la mission/test.
- ☐ Polling `GET /notifications?unread_only=true` toutes les 30 s (ou WebSocket plus tard, hors scope).
**DoD** : Bob (blue) reçoit un badge « Test #4 prêt à review » 30 s max après qu'Alice (red) clique « Marquer exécuté ».
---
## M10 — Génération du slide reveal.js ☐
**But** : livrable client de fin de mission.
- ☐ Backend : endpoint `GET /missions/{id}/slide.html` qui calcule l'agrégat (tests groupés par MITRE Tactic, comptages par detection_level, plus regroupements custom si configurés).
- ☐ Côté serveur, on émet **un seul fichier HTML standalone** : reveal.js inliné (CSS + JS), tokens design.md inlinés, données JSON inlinées, **aucune ressource externe**.
- ☐ Layout : slide titre, slide « Méthodologie », une slide par Tactic avec liste des techniques/tests + icône colorée par detection_level, slide synthèse (matrice tactic × detection_level), slide annexes (preuves référencées par titre, sans binaires).
- ☐ Bouton « Export PDF » dans le slide → `print-pdf` reveal.js (`window.print()` + media query reveal).
- ☐ Front : page Mission > Synthèse avec preview iframe + bouton « Télécharger HTML ».
- ☐ Conformité design : `// ` headings en cyan, accents par detection_level, JetBrains Mono partout.
**DoD** : on télécharge `mission-X.html`, on l'ouvre offline dans Firefox/Chrome, navigation reveal OK, export PDF côté navigateur produit un PDF lisible.
---
## M11 — Exports JSON & CSV ☐
**But** : sortie des données brutes pour archivage.
-`GET /missions/{id}/export.json` : mission + scénarios + tests + niveaux de détection + métadonnées preuves (sans binaires, mais avec hash + filename).
-`GET /missions/{id}/export.csv` : une ligne par test (cols : test_name, mitre_tactic, mitre_technique, mitre_subtechnique, executed_at, status, red_command, detection_level, blue_comment_excerpt).
- ☐ Front : boutons d'export sur la page mission, headers `Content-Disposition: attachment`.
**DoD** : `curl -OJ` sur les deux endpoints donne deux fichiers cohérents et complets ; le JSON peut être réimporté dans le futur (laisser cet import en backlog).
---
## M12 — Soft delete & purge admin ☐
**But** : aucune perte de donnée par accident, ménage explicite.
- ☐ Toutes les `DELETE` du back deviennent `UPDATE deleted_at`.
- ☐ Tous les `GET` filtrent `deleted_at IS NULL` par défaut, paramètre `?include_deleted=true` réservé aux admins.
- ☐ Endpoint `POST /admin/purge` (perm admin) avec body `{entity, ids}` qui DELETE physiquement (suppression fichiers preuves incluse).
- ☐ Commande `flask metamorph purge-soft-deleted --older-than 30d` (manuelle, pas de cron auto).
- ☐ Front : page Admin > Trash (filtrée par entity), bouton « Restaurer » + bouton « Purger ».
**DoD** : suppression d'un test depuis l'UI → disparait des listes mais reste en DB ; admin peut le restaurer ; admin peut le purger définitivement, le fichier evidence associé disparait du disque.
---
## M13 — i18n FR / EN ☐
**But** : commutation de langue par utilisateur.
- ☐ Backend : `flask-babel`, deux locales `fr` / `en`. Messages d'erreur API via `gettext`. Fichier `messages.pot` extrait via `pybabel extract`.
- ☐ Frontend : `react-i18next`, namespaces par page, fichiers `frontend/src/i18n/{fr,en}/*.json`.
- ☐ Préférence user : champ `users.locale` (default `fr`), endpoint `PATCH /auth/me {locale}`, switch dans le header.
- ☐ Données MITRE conservées en EN (officielles, non traduites).
- ☐ Tous les libellés UI passent par `t('…')` — interdit le texte en dur.
**DoD** : Bob change sa langue en EN, recharge → toute l'UI en EN sauf les noms ATT&CK ; un message d'erreur API arrive aussi en EN.
---
## M14 — Polish, sécu, observabilité, doc ☐
**But** : prêt pour livraison.
- ☐ Logs JSON : `request_id`, `user_id`, `path`, `method`, `status`, `duration_ms`, `action` (libre côté service).
- ☐ Audit minimal : logger toute action sensible (`auth.login`, `mission.create`, `evidence.delete`, `admin.purge`).
- ☐ Rate-limit confirmé sur `/auth/*` et `/invitations/*`.
- ☐ Headers sécu : `Strict-Transport-Security` (si reverse proxy le pose, sinon doc), `Content-Security-Policy` strict côté front, `X-Frame-Options: DENY`, `Referrer-Policy: same-origin`.
- ☐ Validation : tailles max body globale (Flask `MAX_CONTENT_LENGTH`), schéma pydantic strict partout.
-`README.md` complet (déploiement, env, premier admin, sync MITRE, backup volumes).
-`CHANGELOG.md` à jour (Conventional changelog).
- ☐ Critères §10 de la spec : check 1 par 1 sur une démo end-to-end documentée dans `tasks/lessons.md`.
- ☐ Tests : pytest pour la logique critique (auth, RBAC, snapshot, upload, exports). Smoke E2E Playwright (non bloquant, but nice).
**DoD** : démo from-scratch sur Debian 13 — `git clone``make up` → setup admin → invite users → crée mission → exécute → annote → génère slide → export. Tous les 15 critères §10 spec validés.
---
## Backlog v2+ (rappel pour ne pas oublier)
- Bascule auth Keycloak/OIDC.
- API d'ingestion C2 externe (push automatique des résultats).
- Audit log détaillé + versioning par champ.
- 2FA TOTP self-service.
- Notifications mail.
- Intégration tunnel C2 (binaires fournis).
- Métriques Prometheus.
- Multi-tenancy / workspaces.
- Branding configurable (logos, couleurs).
---
## Hygiène de session
- Au début de chaque session : relire `tasks/lessons.md`, `CHANGELOG.md`, ce fichier.
- À la fin : mettre à jour les ☐/☑, ajouter une entrée `CHANGELOG.md`, capturer les apprentissages dans `tasks/lessons.md`.
- Pour tout doute architectural : repasser par AskUserQuestion avant d'ouvrir un éditeur.