feat: socle sécurité — socket-proxy, réseaux, egress (v0.3.0)

Modèle de menace centré prompt-injection + défenses par couche
(surface réduite, socket-proxy obligatoire, scoping tokens, egress
ollama.com only, secrets par référence, lecture seule renforcée, audit).
Documente réseaux interne/egress et filtrage Docker en lecture seule P1.
Normalise les fins de ligne (.gitattributes).

Palier de risque : n/a (doc + conventions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kantin-Petit
2026-06-23 01:00:45 +02:00
parent c096fa9467
commit 0f63b3addd
6 changed files with 161 additions and 0 deletions
View File
+34
View File
@@ -0,0 +1,34 @@
# Réseaux & exposition
> Invariant : **une seule surface exposée**, le backend CHLOVA. Tout le reste
> reste interne. Voir `docs/security.md`.
## Réseaux Docker
| Réseau | Type | Membres | But |
|---|---|---|---|
| `chlova-internal` | `internal: true` | backend, ollama, mcp-n8n, mcp-portainer, socket-proxy, (n8n, portainer existants) | bus interne ; **aucun accès externe** |
| `chlova-egress` | bridge | ollama uniquement | sortie contrôlée vers `ollama.com` |
- `chlova-internal` est déclaré `internal: true` : **pas de route vers l'extérieur**.
- Seul **Ollama** est aussi sur `chlova-egress` (il doit joindre `ollama.com`).
- Le backend n'a **pas** besoin d'egress en Phase 1 (Telegram en long-polling sort
vers `api.telegram.org` → à autoriser explicitement quand la surface est activée).
## Ports publiés
- **Phase 1 : aucun.** La surface Telegram fonctionne en long-polling (le bot
appelle Telegram, rien n'écoute en entrée).
- Phases ultérieures (API/UI CHLOVA) : exposer **uniquement** le backend derrière
**Traefik + TLS**, et/ou via **VPN mesh** (Tailscale/Wireguard). Jamais Ollama,
n8n, Portainer ou un serveur MCP.
## Egress (pare-feu)
- Politique par défaut : **deny**.
- Autoriser : `ollama.com` (proxy cloud), et `api.telegram.org` quand la surface
Telegram est active.
- À appliquer au niveau hôte (iptables/nftables ou réseau Docker dédié) ; documenté
ici, mis en œuvre côté infra serveur (hors dépôt si géré par l'hôte).
## Rappels
- Ollama **n'a aucune auth native** → ne jamais lui publier de port.
- Accès Docker **uniquement** via `socket-proxy` (jamais le socket brut).
+39
View File
@@ -0,0 +1,39 @@
# socket-proxy — accès Docker filtré
`tecnativa/docker-socket-proxy` s'intercale entre le serveur MCP Portainer (et
toute brique ayant besoin de l'API Docker) et le socket Docker de l'hôte. Il
**filtre les endpoints** de l'API Docker via des variables d'env (0 = refusé,
1 = autorisé). C'est le seul à monter `/var/run/docker.sock` (en **lecture seule**).
## Phase 1 — lecture seule
Autoriser uniquement les endpoints de lecture, refuser tout le reste.
| Variable | Valeur P1 | Effet |
|---|---|---|
| `CONTAINERS` | `1` | lister/inspecter conteneurs |
| `IMAGES` | `1` | lister/inspecter images |
| `NETWORKS` | `1` | lister/inspecter réseaux |
| `VOLUMES` | `1` | lister/inspecter volumes |
| `SERVICES`/`TASKS`/`NODES` | `1` | lecture swarm (si applicable) |
| `INFO` | `1` | `docker info` |
| `VERSION` | `1` | `docker version` |
| `POST` | `0` | **refuse toute mutation** (create/start/stop/rm…) |
| `EXEC` | `0` | **refuse `exec`** dans un conteneur |
| `AUTH`/`SECRETS`/`CONFIGS` | `0` | refuse l'accès aux secrets/configs |
| `BUILD`/`COMMIT`/`IMAGES_CREATE` | `0` | refuse build/commit/pull |
> `POST=0` est la barrière clé : la plupart des actions destructrices passent par
> des requêtes POST. En Phase 1 on ne l'active **jamais**.
## Mise en œuvre
Le service est défini dans `infra/docker-compose.yml` (service `socket-proxy`),
branché **uniquement** sur `chlova-internal`, sans port publié. Le socket hôte est
monté en `:ro`. Le MCP Portainer pointe vers `tcp://socket-proxy:2375` au lieu du
socket brut.
## Rollback / durcissement
- Pour revenir en arrière : remettre toutes les variables d'écriture à `0` et
redéployer. Le passage en Phase 2 (écriture) n'ouvrira que les endpoints
**strictement** nécessaires à la capacité ajoutée, après review (voir
`docs/security.md`, checklist Phase 2).
- Image **épinglée** (tag + digest) dans le compose, jamais `:latest`.