Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker. Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# Security Fixes Applied — Phase 3
|
||||
|
||||
Date: 2026-05-06
|
||||
|
||||
## SEC-FIX-006 — Validation des entrées discovery
|
||||
|
||||
### Problème
|
||||
- `dns_server` acceptait n'importe quelle chaîne (risque d'injection dans le résolveur DNS)
|
||||
- `/api/discovery/ping` acceptait n'importe quelle chaîne comme IP (transmise au sous-processus ping)
|
||||
- Plusieurs targets pouvaient contourner le cap de 1024 : 5 × /22 = 5110 hôtes
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`backend/routers/discovery.py`**
|
||||
- `ScanRequest.dns_server` : `field_validator` qui appelle `ipaddress.ip_address(v)` — rejette toute valeur non-IP (422)
|
||||
- `PingRequest.ips` : `field_validator` qui valide chaque IP — rejette toute entrée malformée avant d'appeler le sous-processus (422)
|
||||
- `MAX_HOSTS_TOTAL = 4096` : cap global sur la somme des hôtes de tous les targets — rejette si dépassé (400)
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-007 — Validation Pydantic métier
|
||||
|
||||
### Problème
|
||||
Aucun validator sur les schémas métier : valeurs hors-domaine, CIDR invalides, couleurs arbitraires, URLs arbitraires, IPs invalides, types inconnus acceptés silencieusement.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`backend/routers/vlans.py`**
|
||||
- `vlan_id` : 1–4094 (norme 802.1Q)
|
||||
- `name` : non vide, max 100 caractères (strip)
|
||||
- `cidr` : `ipaddress.ip_network(strict=False)` si non vide
|
||||
- `color` : regex `^#[0-9a-fA-F]{6}$`
|
||||
|
||||
**`backend/routers/devices.py`**
|
||||
- `name` : non vide, max 100 caractères (strip)
|
||||
- `description` : max 500 caractères
|
||||
- `type` : enum des 18 types valides
|
||||
- `virt_type` : enum `{null, baremetal, lxc, qemu}`
|
||||
- `url` : `urlparse` — schéma `http`/`https` + netloc non vide
|
||||
- `InterfaceCreate.name` : non vide, max 50 caractères
|
||||
- `InterfaceCreate.ip_address` : `ipaddress.ip_address(v)` si non vide
|
||||
|
||||
Toutes les erreurs de validation retournent 422 (comportement standard FastAPI/Pydantic).
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-008 — En-têtes HTTP sécurité Nginx
|
||||
|
||||
### Problème
|
||||
Aucun en-tête de sécurité HTTP : pas de CSP, pas de protection contre le MIME sniffing ou le framing.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`frontend/nginx.conf`**
|
||||
```
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: DENY
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self';
|
||||
```
|
||||
|
||||
Tous les en-têtes utilisent le flag `always` pour s'appliquer aussi aux réponses d'erreur.
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-009 — Documentation TLS/HTTPS
|
||||
|
||||
### Statut : CORRIGÉ EN PHASE 2/3
|
||||
|
||||
Documentation ajoutée au `README.md` lors de la session précédente :
|
||||
- Section HTTPS avec exemple nginx reverse-proxy complet
|
||||
- `docker-compose.override.yml` pour limiter l'exposition au loopback
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-012 — Logs d'audit structurés
|
||||
|
||||
### Problème
|
||||
Aucune trace des événements d'authentification : connexions, échecs, changements de mot de passe.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`backend/routers/auth.py`**
|
||||
- Logger stdlib `logging.getLogger("audit")` — aucune dépendance supplémentaire
|
||||
- Helper `_log_audit(event, **kw)` : émet une ligne JSON `{"event": ..., "ts": ..., ...}`
|
||||
- Événements loggés :
|
||||
- `auth.login.success` — username, ip
|
||||
- `auth.login.failure` — username, ip
|
||||
- `auth.login.rate_limited` — ip, username (si disponible), reason (`ip` | `username`)
|
||||
- `auth.token_rejected` — username, reason (`user_not_found` | `version_mismatch`)
|
||||
- `auth.account.password_changed` — username
|
||||
- `auth.account.username_changed` — old_username, new_username
|
||||
- `auth.account.bad_password` — username
|
||||
|
||||
Activation dans Docker via la config de logging uvicorn (stdout par défaut).
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-013 — PRAGMA foreign_keys=ON
|
||||
|
||||
### Problème
|
||||
SQLite désactive les contraintes de clés étrangères par défaut. Les suppressions en cascade ne sont pas enforced.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`backend/database.py`**
|
||||
```python
|
||||
@event.listens_for(Engine, "connect")
|
||||
def _set_sqlite_pragma(dbapi_conn, _record):
|
||||
if isinstance(dbapi_conn, sqlite3.Connection):
|
||||
cursor = dbapi_conn.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
```
|
||||
|
||||
Le listener s'exécute sur chaque nouvelle connexion SQLite, y compris les connexions de test.
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-014 — Dépendance `cytoscape` non utilisée
|
||||
|
||||
### Statut : CORRIGÉ EN PHASE 2
|
||||
|
||||
`cytoscape` supprimé de `frontend/package.json` lors de la session précédente.
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-015 — Import JSON sans limite de taille ni validation de schéma
|
||||
|
||||
### Problème
|
||||
Un fichier JSON de plusieurs gigaoctets pouvait être chargé en mémoire. Aucune validation du schéma avant traitement.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`frontend/src/App.vue`**
|
||||
- Vérification `file.size > 5 * 1024 * 1024` avant `file.text()` — rejette avec message localisé
|
||||
- Validation de schéma :
|
||||
- Le JSON doit être un objet (pas un tableau, pas null)
|
||||
- `vlans` et `devices`, si présents, doivent être des tableaux
|
||||
|
||||
**`frontend/src/i18n.js`**
|
||||
- Clé `importTooLarge` ajoutée (fr, en, es)
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-016 — `v-html` dans App.vue
|
||||
|
||||
### Problème
|
||||
`v-html` utilisé pour injecter des entités HTML (`■`, `◆`, `▣`) dans les boutons de navigation. Risque XSS si la source venait à être dynamique.
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`frontend/src/App.vue`**
|
||||
- Entités HTML remplacées par leurs équivalents Unicode directs (`■`, `◆`, `▣`)
|
||||
- `v-html="tab.icon"` remplacé par `{{ tab.icon }}` (interpolation texte — échappe automatiquement)
|
||||
|
||||
---
|
||||
|
||||
## SEC-FIX-017 — Nettoyage du code orphelin Links (backend)
|
||||
|
||||
### Problème
|
||||
La vue Liens avait été retirée du frontend en phase 2/3, mais le backend conservait :
|
||||
- `backend/routers/links.py` — router toujours enregistré dans main.py
|
||||
- `class Link` dans `models.py` — ORM orphelin
|
||||
- Référence explicite à `models.Link` dans `delete_device` (devices.py)
|
||||
- La table `links` en base — avec FK vers `devices`, ce qui bloquerait les suppressions d'équipements avec `PRAGMA foreign_keys=ON`
|
||||
|
||||
### Corrections appliquées
|
||||
|
||||
**`backend/routers/links.py`** — Supprimé
|
||||
|
||||
**`backend/models.py`** — Classe `Link` supprimée
|
||||
|
||||
**`backend/routers/devices.py`** — Suppression de la requête `db.query(models.Link).filter(...).delete()` dans `delete_device`
|
||||
|
||||
**`backend/main.py`**
|
||||
- Import `links` retiré
|
||||
- `app.include_router(links.router, ...)` retiré
|
||||
- Nouvelle migration `_migrate_drop_links_table()` : DROP TABLE links si elle existe (avec `PRAGMA foreign_keys=OFF/ON` pour éviter les erreurs FK pendant la migration)
|
||||
|
||||
---
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
| Fichier | Fix(es) |
|
||||
|---------|---------|
|
||||
| `backend/database.py` | SEC-FIX-013 |
|
||||
| `backend/models.py` | SEC-FIX-017 |
|
||||
| `backend/main.py` | SEC-FIX-017 |
|
||||
| `backend/routers/auth.py` | SEC-FIX-012 |
|
||||
| `backend/routers/vlans.py` | SEC-FIX-007 |
|
||||
| `backend/routers/devices.py` | SEC-FIX-007, SEC-FIX-017 |
|
||||
| `backend/routers/discovery.py` | SEC-FIX-006 |
|
||||
| `backend/routers/links.py` | SEC-FIX-017 (supprimé) |
|
||||
| `backend/tests/test_validation.py` | Tests SEC-FIX-006, 007, 013, 017 (nouveau) |
|
||||
| `frontend/nginx.conf` | SEC-FIX-008 |
|
||||
| `frontend/src/App.vue` | SEC-FIX-015, SEC-FIX-016 |
|
||||
| `frontend/src/i18n.js` | SEC-FIX-015 |
|
||||
Reference in New Issue
Block a user