## Projet Docker Swarm – Bruteforce MD5 ![coverage](https://www-apps.univ-lehavre.fr/forge/gm213204/killemd5/badges/master/coverage.svg?job=build-and-test) Ce dépôt contient une petite infrastructure Docker Swarm capable de bruteforcer des hash MD5 de manière scalable, ainsi qu’une application web pour piloter et monitorer le cluster. --- ### Sous-projets - **backend** : API Node.js/Express pour gérer les jobs de bruteforce, le **cache des hash** (Redis), l’état du cluster et le scaling des workers. - **worker** : service de bruteforce MD5 (conteneurs réplicables dans le Swarm). - **frontend** : application React permettant d’envoyer des hash, choisir les modes *gentil/normal/agressif* et visualiser l’état du cluster. - **infra** : configuration Docker Swarm (stack) et scripts éventuels. - **proxy** : service Node.js qui expose un WebSocket compatible avec le tester de l’enseignant et le traduit en appels HTTP vers le backend. - **CI** : workflows d’intégration continue (fichier `.gitlab-ci.yml`). --- ### Architecture #### Vue d’ensemble des services (stack Swarm) Tous les services tournent sur le réseau overlay `md5_net`. Le backend et le frontend exposent des ports sur l’hôte. > *Note: Veuillez installer l'extention vsCODE: Markdown Preview: Mermaid Support pour visualiser les diagrammes.* ```mermaid flowchart LR subgraph md5_net [Réseau md5_net] Frontend[frontend] Backend[api_backend] Redis[redis] Worker[hash_worker] Proxy[hash_proxy] Checker[hash_checker] end User[Utilisateur] -->|":5173"| Frontend Frontend -->|"HTTP :8080"| Backend Backend --> Redis Backend -->|"Docker API"| Worker Worker --> Redis Checker -->|"WebSocket :3000"| Proxy Proxy -->|"HTTP :8080"| Backend ``` **Flux utilisateur (UI)** : * Le frontend envoie des hash au backend (`POST /hash/manual`). * Le backend vérifie d’abord le **cache des hash** (`jobs:cache`) ; en cas de hit, il crée un job « synthétique » et renvoie immédiatement `202 { id }` sans mettre en file. Sinon, le job est mis en file Redis. * Les workers consomment la file, bruteforcent, écrivent les résultats et mettent à jour le cache * Le frontend récupère la liste des jobs via `GET /cluster/jobs`. * Le scaler ajuste le nombre de réplicas du service `hash_worker` en fonction de la charge. **Flux tester** : * Le conteneur `hash_checker` se connecte en WebSocket au `hash_proxy`. * Il envoie `search MD5_HASH BEGIN END`. * Le proxy appelle `POST /tester/search`. * Le backend applique le même middleware de cache, mais n’utilise le cache que si le résultat en cache est **trouvé** (`found: true`), afin de garantir la cohérence des plages `begin`/`end`. Sinon, le job est mis en file. * Le proxy poll `GET /tester/job/:id` et renvoie `found` ou `notfound` au checker. #### Flux de traitement d’un job (utilisateur ou tester) ```mermaid sequenceDiagram participant Client as Client (frontend ou proxy) participant Backend as api_backend participant Redis as redis participant Worker as hash_worker Client->>Backend: POST /hash/manual ou POST /tester/search Backend->>Redis: HGET jobs:cache (middleware cache) alt Cache hit (selon règles frontend/tester) Backend->>Redis: HSET jobs:results, jobs:status, jobs:meta, LPUSH jobs:recent Backend-->>Client: 202 { id } else Pas de cache Backend->>Redis: LPUSH jobs:pending, HSET jobs:status, jobs:meta, LPUSH jobs:recent Backend-->>Client: 202 { id } Worker->>Redis: BRPOP jobs:pending Worker->>Redis: SADD jobs:in_progress Worker->>Worker: bruteforce (optionnellement entre begin/end) Worker->>Redis: HSET jobs:results, jobs:status, jobs:cache Worker->>Redis: SREM jobs:in_progress end loop Polling Client->>Backend: GET /cluster/jobs ou GET /tester/job/:id Backend->>Redis: HGET jobs:meta, jobs:status, jobs:results Backend-->>Client: liste jobs ou résultat end ``` #### Rôle du proxy (intégration tester) Le proxy adapte le protocole WebSocket du tester à l’API HTTP du backend. ```mermaid flowchart LR subgraph Tester [Tester enseignant] Checker[hash_checker] end subgraph NotreStack [Notre stack] Proxy[hash_proxy] Backend[api_backend] end Checker -->|"search HASH BEGIN END"| Proxy Proxy -->|"POST /tester/search"| Backend Proxy -->|"GET /tester/job/:id"| Backend Backend -->|"found / notfound"| Proxy Proxy -->|"found HASH PLAINTEXT"| Checker ``` #### Cache des hash Pour éviter de re-bruteforcer un même hash déjà traité, un **cache** est géré dans Redis. - **Clé Redis** : `jobs:cache` (hash Redis : clé = hash MD5, valeur = JSON `{ found, plaintext, elapsedMs }`). - **Lecture (backend)** : un middleware Express `cacheHashOrNext` est appliqué à `POST /hash/manual` et `POST /tester/search`. Avant toute mise en file, il vérifie si le hash est présent dans `jobs:cache`. En cas de hit : - **Frontend** : tout résultat en cache (trouvé ou non) est réutilisé ; le backend crée un job « synthétique » (résultat, statut, métadonnées, liste des jobs récents) et renvoie `202 { id }` sans toucher à la file. - **Tester** : le cache n’est utilisé que si le résultat en cache a `found === true`, afin de ne pas renvoyer « non trouvé » pour une plage `begin`/`end` différente. - **Écriture (worker)** : après chaque bruteforce réussi, le worker écrit dans `jobs:cache` le résultat (trouvé ou non) associé au hash, pour les soumissions futures. Le worker ne consulte pas le cache : toute décision de cache est prise côté backend à la soumission. --- ### Prérequis - Docker et Docker Swarm initialisé (`docker swarm init`). - Node.js (pour le développement local du backend et du frontend). --- ### Auto-scaling Un **scaler** tourne dans le processus du backend. Il lit périodiquement la file Redis (`jobs:pending`) et le nombre de workers, puis ajuste le nombre de réplicas du service worker Swarm entre un min et un max configurables. Les seuils sont basés sur la charge (jobs par worker) : scale up si (jobs en attente / workers) dépasse un seuil, scale down si ça tombe en dessous d’un seuil plus bas (et qu’aucun job n’est en cours). Variables d’environnement (optionnelles, sur le service `api_backend`) : | Variable | Défaut | Description | |----------|--------|-------------| | `SCALER_ENABLED` | `true` | Mettre à `false` pour désactiver le scaler (ex. dev local sans Swarm). | | `SCALER_INTERVAL_MS` | `10000` | Période entre deux évaluations (ms). | | `SCALER_MIN_REPLICAS` | `1` | Nombre minimum de workers. | | `SCALER_MAX_REPLICAS` | `10` | Nombre maximum de workers. | | `SCALER_SCALE_UP_WHEN_JOBS_PER_WORKER_ABOVE` | `4` | Scale up si (jobs en attente / workers) ≥ cette valeur. | | `SCALER_SCALE_DOWN_WHEN_JOBS_PER_WORKER_BELOW` | `1` | Scale down si (jobs en attente / workers) ≤ cette valeur (et aucun job en cours). | --- ### Déploiement via Docker Swarm (stack) 1. Construire les images localement (ou via la CI) : - `docker build -t md5-swarm-backend:latest ./backend` - `docker build -t md5-swarm-worker:latest ./worker` - `docker build -t md5-swarm-frontend:latest ./frontend` 2. Initialiser Swarm si besoin : - `docker swarm init` 3. Déployer la stack : - `cd infra` - `docker stack deploy -c stack.yml md5-swarm` 4. Accéder à l’application : - Backend : `http://localhost:8080` - Frontend (proxy Nginx dans le conteneur) : `http://localhost:5173` La stack crée un service Redis, un backend qui gère la file de jobs dans Redis, un worker scalable pour le bruteforce, et un frontend pour piloter le tout. --- ### Intégration avec le tester de l’enseignant Le dépôt peut être testé automatiquement via l’image Docker `servuc/hash_extractor` fournie par l’enseignant : - **Proxy WebSocket** (`hash_proxy`) : - Service Node.js (dossier `proxy/`) qui expose l’interface WebSocket attendue par `hash_extractor`. - Il reçoit des commandes `search MD5_HASH BEGIN END`, les traduit en appels HTTP vers le backend et renvoie `found MD5_HASH PLAINTEXT` ou `notfound MD5_HASH ""` selon le résultat. - **Endpoints dédiés `/tester`** dans le backend : - `POST /tester/search` : accepte `{ hash, begin, end }`, crée un job de bruteforce borné et renvoie `{ id }`. - `GET /tester/job/:id` : renvoie un état normalisé du job, par exemple `{ status: "done", found: true, plaintext: "pina" }`. - **Worker range-aware** : - Le worker lit éventuellement `begin` et `end` dans le job et ne considère que les candidats compris dans cet intervalle (selon l’ordre de génération des chaînes). Dans `infra/stack.yml`, deux services supplémentaires sont définis : - `hash_proxy` : connecté au backend sur le réseau `md5_net`. - `hash_checker` : conteneur `servuc/hash_extractor:1.0.0` lancé en mode `c`, configuré avec `MASTER_WS=ws://hash_proxy:3000` pour piloter automatiquement le cluster. Pour lancer un test de bout en bout localement : - `cd infra && docker stack deploy -c stack.yml md5-swarm` - Surveiller les logs des services `hash_proxy`, `hash_checker`, `api_backend` et `hash_worker` avec `docker service logs -f ...` pour voir le scénario se dérouler. --- ### Tests & couverture - **Tests backend** : - `cd backend` - `npm test` - Les tests utilisent le runner intégré de Node.js (`node --test`) avec la collecte de couverture activée. - **CI GitLab** : - Le job `build-and-test` dans `.gitlab-ci.yml` exécute les tests backend, le build frontend et extrait le pourcentage de couverture à partir du rapport de Node. - Le badge en haut de ce README reflète la couverture de la branche par défaut. --- ### Licence Ce projet est distribué sous licence MIT. Voir le fichier `LICENSE.txt` pour plus de détails. --- ### À faire / améliorations possibles - Durcir la configuration de sécurité (CORS plus restrictif, `helmet`, etc.). - Ajouter davantage de tests (notamment sur le frontend). - Documenter en détail les endpoints de l’API et les scénarios d’utilisation côté UI.