feat: application n8n Pilot complète (Expo managed workflow)
- Stack : Expo Router, Axios, Zustand, React Native Paper (thème sombre), date-fns - Sécurité : secrets dans Android Keystore via expo-secure-store, TLS obligatoire, headers X-N8N-API-KEY + X-App-Token injectés par intercepteur Axios - API : client.ts centralisé + workflows.ts + executions.ts (TypeScript strict) - Store : Zustand appStore avec chargement depuis secure store au démarrage - Hooks : usePolling (générique), useWorkflows, useExecutions - Composants : StatusBadge, WorkflowCard, ExecutionCard, SkeletonLoader - Screens : Dashboard, Workflows, Executions, Logs (détail exécution), Settings - Navigation Expo Router : 4 tabs + stack Logs + écran Setup initial - Docs : INSTALL.md, UPDATE.md, BACKUP.md, HAPROXY.md, SECURITY.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# Sécurité — n8n Pilot
|
||||
|
||||
## Modèle de menaces
|
||||
|
||||
### Acteurs et vecteurs
|
||||
|
||||
| Menace | Impact | Probabilité | Mitigation |
|
||||
|--------|--------|-------------|-----------|
|
||||
| Vol de l'appareil Android | Accès aux credentials n8n | Moyen | Android Keystore chiffré, biométrie optionnelle |
|
||||
| Interception réseau (MITM) | Exposition de la clé API | Faible (TLS) | TLS obligatoire, refus HTTP |
|
||||
| Fuite du token HAProxy | Accès non autorisé à n8n | Moyen | Rotation régulière, token hors bundle |
|
||||
| Compromission du build APK | Backdoor dans l'app | Faible | Keystore signé, builds EAS reproductibles |
|
||||
| Brute force API | Épuisement quotas n8n | Faible | Rate limiting HAProxy (429) |
|
||||
| Logs applicatifs exposés | Fuite de secrets | Faible | Jamais de log de secrets, même en dev |
|
||||
|
||||
---
|
||||
|
||||
## Principes appliqués
|
||||
|
||||
### Least Privilege (moindre privilège)
|
||||
|
||||
La clé API n8n doit être créée avec le **scope minimal** nécessaire :
|
||||
|
||||
```
|
||||
Recommandé :
|
||||
✓ workflows:list — lister les workflows
|
||||
✓ workflows:read — lire les détails
|
||||
✓ workflows:update — activer/désactiver (toggle)
|
||||
✓ executions:list — voir l'historique
|
||||
✓ executions:read — lire les logs
|
||||
✓ executions:delete — supprimer des exécutions
|
||||
✓ workflows:run — déclencher manuellement
|
||||
|
||||
Non recommandé :
|
||||
✗ credentials:* — inutile pour cette app
|
||||
✗ users:* — inutile pour cette app
|
||||
✗ admin:* — jamais
|
||||
```
|
||||
|
||||
**Comment créer la clé** dans n8n :
|
||||
1. Settings > n8n API
|
||||
2. Create API Key
|
||||
3. Nommer la clé "n8n-pilot-mobile"
|
||||
4. Définir les scopes minimaux ci-dessus
|
||||
5. Copier la clé — elle n'est affichée qu'une seule fois
|
||||
|
||||
---
|
||||
|
||||
### Defense in Depth (défense en profondeur)
|
||||
|
||||
L'accès à l'API n8n passe par **deux barrières indépendantes** :
|
||||
|
||||
```
|
||||
Requête → [TLS] → [X-App-Token HAProxy] → [X-N8N-API-KEY n8n] → Données
|
||||
```
|
||||
|
||||
Si un token est compromis :
|
||||
- Compromission du `X-App-Token` seul → HAProxy bloque, n8n jamais atteint
|
||||
- Compromission de la clé API seul → sans le `X-App-Token`, HAProxy bloque à 403
|
||||
- Les deux compromis → rotation immédiate des deux (procédures dans HAPROXY.md et BACKUP.md)
|
||||
|
||||
---
|
||||
|
||||
### Zero Trust (réseau local inclus)
|
||||
|
||||
n8n ne doit **pas** être accessible directement sur le réseau, même en local.
|
||||
Toutes les requêtes passent par HAProxy qui valide le token.
|
||||
|
||||
```
|
||||
# Bloquer l'accès direct au port n8n (iptables)
|
||||
iptables -A INPUT -p tcp --dport 5678 -s 127.0.0.1 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 5678 -j DROP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gestion des secrets — cycle de vie
|
||||
|
||||
### Stockage
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Appareil Android │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ Android Keystore (expo-secure-store) │ │
|
||||
│ │ n8n_base_url → https://n8n.example.com │ │
|
||||
│ │ n8n_api_key → clé API (chiffrée) │ │
|
||||
│ │ n8n_app_token → token HAProxy (chiffré) │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ État React (Zustand) — en mémoire uniquement : │
|
||||
│ config.baseUrl → copie non sensible │
|
||||
│ config.isConfigured → booléen │
|
||||
│ JAMAIS : apiKey, appToken en mémoire JS │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Transit
|
||||
|
||||
- Les secrets sont lus depuis le Keystore **à chaque requête** HTTP dans `client.ts`
|
||||
- Ils sont injectés dans les headers HTTP et **ne sont pas loggués**
|
||||
- Ils ne sont **jamais** :
|
||||
- Sérialisés dans AsyncStorage
|
||||
- Inclus dans des logs (même en dev)
|
||||
- Embarqués dans le bundle JS (`app.json`, `package.json`, code source)
|
||||
- Envoyés à des services tiers (analytics, crash reporters)
|
||||
|
||||
### Rotation
|
||||
|
||||
| Secret | Fréquence recommandée | Procédure |
|
||||
|--------|-----------------------|-----------|
|
||||
| Clé API n8n | Tous les 180 jours, ou en cas de compromission | Créer nouvelle clé dans n8n, mettre à jour dans l'app |
|
||||
| X-App-Token | Tous les 90 jours | Voir docs/HAPROXY.md |
|
||||
| Keystore EAS | Jamais (sauf compromission) | Voir docs/BACKUP.md |
|
||||
|
||||
---
|
||||
|
||||
## Checklist avant mise en production
|
||||
|
||||
### Application
|
||||
|
||||
- [ ] `app.json` : `userInterfaceStyle` = `"dark"` (pas d'info sensible en clair)
|
||||
- [ ] `.env` non commité dans git (`git status` ne montre pas `.env`)
|
||||
- [ ] Aucun `console.log` de valeurs sensibles dans le code
|
||||
- [ ] `__DEV__` gate sur tous les logs dans `errorHandler.ts`
|
||||
- [ ] Expo SDK à jour — vérifier les CVE : `npx expo-doctor`
|
||||
- [ ] Dépendances à jour : `npm audit`
|
||||
|
||||
### Infrastructure
|
||||
|
||||
- [ ] HAProxy : TLS 1.2 minimum, TLS 1.0/1.1 désactivés
|
||||
- [ ] Certificat TLS valide et non expiré
|
||||
- [ ] n8n inaccessible directement sur le port 5678 depuis l'extérieur
|
||||
- [ ] Rate limiting HAProxy activé (429 configuré)
|
||||
- [ ] Logs HAProxy ne contiennent pas les valeurs des headers secrets
|
||||
|
||||
### n8n
|
||||
|
||||
- [ ] Clé API avec scope minimal (voir § Least Privilege ci-dessus)
|
||||
- [ ] n8n mis à jour vers la dernière version stable
|
||||
- [ ] Sauvegardes n8n actives (workflows, credentials n8n)
|
||||
|
||||
### Appareil
|
||||
|
||||
- [ ] Verrouillage d'écran activé (PIN, pattern ou biométrie)
|
||||
- [ ] Biométrie configurée dans l'app si disponible
|
||||
- [ ] Appareil Android 8+ (API 26+) pour garantir la robustesse du Keystore
|
||||
Reference in New Issue
Block a user