README.md 9,91 ko
Newer Older
Massiles Ghernaout's avatar
Massiles Ghernaout a validé
## Projet Docker Swarm – Bruteforce MD5

Massiles Ghernaout's avatar
Massiles Ghernaout a validé
![coverage](https://www-apps.univ-lehavre.fr/forge/gm213204/killemd5/badges/master/coverage.svg?job=build-and-test)
Massiles Ghernaout's avatar
Massiles Ghernaout a validé
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.

Massiles Ghernaout's avatar
Massiles Ghernaout a validé
### 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.
Massiles Ghernaout's avatar
Massiles Ghernaout a validé
- **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`).
Massiles Ghernaout's avatar
Massiles Ghernaout a validé

### 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
    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.

Massiles Ghernaout's avatar
Massiles Ghernaout a validé
### Prérequis

- Docker et Docker Swarm initialisé (`docker swarm init`).
- Node.js (pour le développement local du backend et du frontend).

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.

---
Massiles Ghernaout's avatar
Massiles Ghernaout a validé

### À faire / améliorations possibles
Massiles Ghernaout's avatar
Massiles Ghernaout a validé

- 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.
Massiles Ghernaout's avatar
Massiles Ghernaout a validé