119 lines
7.3 KiB
Markdown
119 lines
7.3 KiB
Markdown
|
|
# Audit de sécurité CodexPi
|
||
|
|
|
||
|
|
Date de révision : 13 septembre 2026.
|
||
|
|
|
||
|
|
## Architecture réseau
|
||
|
|
|
||
|
|
```text
|
||
|
|
Ordinateur du LAN ── HTTP :8765 ─────────┐
|
||
|
|
▼
|
||
|
|
Tailscale Serve :443 (Funnel interdit) ─► nginx Docker
|
||
|
|
├── Django/Uvicorn :8001 (réseau Docker uniquement)
|
||
|
|
└── FastAPI/Uvicorn :8000 (réseau Docker uniquement)
|
||
|
|
```
|
||
|
|
|
||
|
|
Le binding Compose publie uniquement nginx sur `0.0.0.0:8765`; FastAPI et Django restent internes au réseau Docker. Le port est destiné au LAN de confiance et ne doit jamais être redirigé sur la box. Le projet ne crée aucune règle UPnP, DMZ, redirection de port ou Tailscale Funnel.
|
||
|
|
|
||
|
|
## Menaces et protections
|
||
|
|
|
||
|
|
| Menace | Protection |
|
||
|
|
|---|---|
|
||
|
|
| exposition Internet accidentelle | aucune redirection de box, Tailscale Serve privé, refus de Funnel |
|
||
|
|
| interception d'identifiants | HTTPS via Tailscale recommandé ; accès HTTP limité au LAN de confiance ; cookies HttpOnly/SameSite Strict |
|
||
|
|
| brute force | limite par IP, délai progressif, verrouillage temporaire, journal des refus |
|
||
|
|
| détournement de session/CSRF | session Django côté serveur, jeton CSRF de session, contrôle Origin WebSocket |
|
||
|
|
| traversal/symlink | chemins canonisés, UUID projet, racines autorisées et interdites |
|
||
|
|
| injection de commandes | arguments structurés et validateurs stricts, aucune saisie dans un shell |
|
||
|
|
| téléversement malveillant | PNG/JPEG/WebP vérifiés par signature, 10 Mo max, UUID serveur, stockage privé, lecture authentifiée |
|
||
|
|
| action destructive | confirmation explicite, surface Git/Docker limitée, aucun prune global |
|
||
|
|
| élévation de privilèges | conteneurs non-root, capabilités supprimées, no-new-privileges, aucun sudo Codex |
|
||
|
|
| fuite de secrets | `.env`, bases, clés et logs ignorés par Git ; journaux sans credentials |
|
||
|
|
| mode développement | `DEBUG=False`, aucun `--reload`, contrôles Django de déploiement |
|
||
|
|
|
||
|
|
Le socket Docker monté dans le conteneur API demeure une capacité privilégiée nécessaire aux commandes Docker. L'accès CodexPi et au tailnet doit donc être réservé à des utilisateurs de confiance ; `FULL ACCESS` doit rester exceptionnel.
|
||
|
|
|
||
|
|
## Authentification et journalisation
|
||
|
|
|
||
|
|
Django gère les comptes, les mots de passe Argon2 et les sessions en base. FastAPI valide ces mêmes sessions avant tout accès REST ou WebSocket. Les cookies sont `HttpOnly` et `SameSite=Strict`; l'attribut `Secure` est désactivé lorsque l'accès direct HTTP par IP est activé.
|
||
|
|
|
||
|
|
`data/security.log` enregistre : connexion réussie/refusée, IP source/Tailscale transmise par nginx, date, session créée/supprimée, refus WebSocket, changement de paramètres de sécurité et activation/désactivation de `FULL ACCESS`.
|
||
|
|
|
||
|
|
Le journal ne reçoit jamais : mots de passe, cookies, jetons CSRF, tokens Codex, credentials OpenAI ou contenu de `.env`.
|
||
|
|
|
||
|
|
Les photos jointes restent dans `data/uploads/`, hors Git. Elles ne sont jamais publiées par nginx comme répertoire statique : FastAPI contrôle la session avant chaque lecture. Les prompts en attente et leurs références d'images sont persistés en SQLite et traités un par un par un worker non-root.
|
||
|
|
|
||
|
|
## Ports CodexPi
|
||
|
|
|
||
|
|
| Port | Exposition | Usage |
|
||
|
|
|---:|---|---|
|
||
|
|
| 443 | interface Tailscale uniquement | HTTPS privé Tailscale Serve |
|
||
|
|
| 8765 | réseau local | nginx, accès par IP et cible de Tailscale Serve |
|
||
|
|
| 8000 | réseau Docker seulement | FastAPI/Uvicorn |
|
||
|
|
| 8001 | réseau Docker seulement | Django/Uvicorn |
|
||
|
|
|
||
|
|
Les autres ports visibles par `ss -tulpn` appartiennent à l'hôte ou à d'autres applications et ne sont pas modifiés par CodexPi.
|
||
|
|
|
||
|
|
## Firewall et fail2ban
|
||
|
|
|
||
|
|
Si UFW est actif, autorisez éventuellement `8765/tcp` uniquement depuis le sous-réseau local de confiance. Ne publiez jamais `8000` ou `8001` et n'autorisez pas `8765` depuis Internet.
|
||
|
|
|
||
|
|
Une jail fail2ban optionnelle est fournie sous `deploy/fail2ban/`. Le verrouillage applicatif est déjà actif ; avant toute activation fail2ban, sauvegardez la configuration existante, exécutez `fail2ban-client -t` et testez le filtre avec `fail2ban-regex`.
|
||
|
|
|
||
|
|
## Procédure d'accès
|
||
|
|
|
||
|
|
Sur le Pi :
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd /home/config/CodexPi
|
||
|
|
./scripts/install.sh
|
||
|
|
./scripts/create-admin.sh
|
||
|
|
./scripts/tailscale-serve.sh --check
|
||
|
|
./scripts/tailscale-serve.sh --apply
|
||
|
|
```
|
||
|
|
|
||
|
|
Le script affiche l'adresse MagicDNS exacte, typiquement `https://codexpi.<tailnet>.ts.net`. Elle doit être ouverte uniquement depuis un appareil connecté au même tailnet. La page Django demande ensuite l'identifiant CodexPi.
|
||
|
|
|
||
|
|
Sur iPhone/iPad : installez Tailscale depuis l'App Store, acceptez le profil VPN, connectez-vous au même compte/tailnet, activez Tailscale, puis ouvrez l'adresse HTTPS affichée par le script dans Safari. Le site peut ensuite être ajouté à l'écran d'accueil comme PWA.
|
||
|
|
|
||
|
|
## Vérifier l'absence d'accès public
|
||
|
|
|
||
|
|
1. Sur le téléphone, coupez le Wi-Fi et désactivez Tailscale.
|
||
|
|
2. Ouvrez l'adresse `https://codexpi.<tailnet>.ts.net` : elle doit être inaccessible.
|
||
|
|
3. Ne testez pas un port interne en l'ouvrant sur la box ; utilisez un scanner externe sur l'IP publique et confirmez qu'aucun service `8765`, `8000` ou `8001` n'apparaît.
|
||
|
|
4. Sur le Pi, exécutez `ss -tulpn` : CodexPi peut montrer `0.0.0.0:8765`, mais jamais `0.0.0.0:8000` ou `0.0.0.0:8001`.
|
||
|
|
5. Réactivez Tailscale : la même adresse HTTPS doit fonctionner et demander la connexion Django.
|
||
|
|
|
||
|
|
## Révocation d'accès
|
||
|
|
|
||
|
|
- Révoquer toutes les sessions web : `./scripts/revoke-sessions.sh`, puis saisir `REVOKE`.
|
||
|
|
- Retirer la publication privée : `tailscale serve --https=443 off` ; utilisez `tailscale serve reset` seulement si le Pi ne publie rien d'autre.
|
||
|
|
- Déconnecter le Pi : `sudo tailscale down`.
|
||
|
|
- Révoquer définitivement un appareil depuis la page Machines de l'administration Tailscale.
|
||
|
|
- Changer le mot de passe Django avec `docker compose run --rm django-web python3 manage.py changepassword <utilisateur>`.
|
||
|
|
|
||
|
|
## Audit exécutable
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd /home/config/CodexPi
|
||
|
|
./scripts/security-audit.sh
|
||
|
|
./scripts/check-secrets.sh --worktree
|
||
|
|
./scripts/check-secrets.sh --history
|
||
|
|
```
|
||
|
|
|
||
|
|
Les secrets applicatifs sont externes au code : `.env` et `data/` sont ignorés,
|
||
|
|
les identifiants GitLab/Gitea et SSH sont chiffrés dans les données persistantes,
|
||
|
|
les clés GitHub sont créées sous `data/github/`, et la session du Codex CLI reste
|
||
|
|
dans le dossier `.codex` monté depuis l'hôte. Pour un gestionnaire de secrets,
|
||
|
|
montez des fichiers en lecture seule et définissez `CODEXPI_SECRET_KEY_FILE` et
|
||
|
|
`CODEXPI_DJANGO_SECRET_KEY_FILE`.
|
||
|
|
|
||
|
|
Le script vérifie en lecture seule les ports, Compose, Tailscale/Funnel, UFW, fail2ban, permissions, propriétaires, secrets Git, headers HTTP, refus anonyme, debug Django/FastAPI et contrôles de déploiement.
|
||
|
|
|
||
|
|
## Résultat de la revue du code
|
||
|
|
|
||
|
|
- **Architecture Compose : conforme** — seul nginx déclare `ports`, sur le port LAN 8765.
|
||
|
|
- **Authentification/cookies/CSRF/WebSocket : conforme dans l'implémentation** — Django reste l'autorité de session et FastAPI la valide.
|
||
|
|
- **Traversal/injection/actions destructrices : protections conservées** — validation de projet et commandes structurées.
|
||
|
|
- **Secrets/debug : conforme dans le dépôt** — secrets ignorés, Django `DEBUG=False`, aucun reload.
|
||
|
|
- **État réseau réel du Pi : à confirmer après démarrage** avec `./scripts/security-audit.sh`, car un partage SMB ne permet pas d'interroger `ss`, UFW, Tailscale ou les conteneurs en cours d'exécution.
|