refactor(adr-023/024/025): retirer exemples de code et scripts

Suppression de tous les exemples de code pour garder uniquement
les descriptions techniques :

ADR-023 (Architecture Modération) :
- Diagramme Mermaid → description flux textuelle
- Exemples SQL/Redis → description workflow
- Interface Go → description abstraction
- Dépendances → liste concise

ADR-024 (Monitoring et Observabilité) :
- Diagramme Mermaid → architecture textuelle
- Exemples PromQL → description métriques
- Config YAML alertes → liste alertes avec seuils
- Commandes bash WAL-E → description backup
- Runbooks → étapes sans commandes

ADR-025 (Sécurité et Secrets) :
- Diagramme Mermaid → flux secrets textuel
- Commandes bash Vault → description process
- Code Go encryption → architecture encryption
- Schéma SQL → contraintes textuelles
- Config Nginx → configuration TLS
- Code Go rate limiting → paramètres middleware

ADR restent 100% techniques et complets sans code concret.
Cohérence avec ADR-022 (même approche).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
jpgiannetti
2026-02-01 17:12:07 +01:00
parent 60dce59905
commit 81ccbf79e6
3 changed files with 120 additions and 392 deletions

View File

@@ -30,184 +30,62 @@ Stratégie **secrets management + encryption at rest + HTTPS** avec stack self-h
### Architecture Secrets
```mermaid
graph TB
subgraph Dev["Environnement Dev"]
EnvFile[".env file<br/>(local uniquement)"]
end
**Environnements** :
- **Développement** : Fichier .env local (non versionné)
- **Production** : HashiCorp Vault (self-hosted)
subgraph Prod["Production"]
Vault["HashiCorp Vault<br/>(secrets storage)"]
API["Backend Go API"]
DB["PostgreSQL<br/>(encrypted at rest)"]
Redis["Redis<br/>(TLS enabled)"]
end
subgraph Encryption["Encryption Layer"]
AES["AES-256-GCM<br/>(PII encryption)"]
TLS["TLS 1.3<br/>(transport)"]
end
subgraph Secrets["Secrets Stockés"]
JWT["JWT Signing Key<br/>(RS256 private key)"]
DBCreds["DB Credentials<br/>(user/pass)"]
Mangopay["Mangopay API Key<br/>(sandbox + prod)"]
EncKey["Encryption Master Key<br/>(AES-256)"]
end
EnvFile -.->|dev only| API
Vault --> API
Vault --- JWT
Vault --- DBCreds
Vault --- Mangopay
Vault --- EncKey
API --> AES
API --> TLS
AES --> DB
TLS --> DB
TLS --> Redis
classDef devStyle fill:#fff3e0,stroke:#e65100
classDef prodStyle fill:#e3f2fd,stroke:#1565c0
classDef encStyle fill:#f3e5f5,stroke:#6a1b9a
classDef secretStyle fill:#ffebee,stroke:#c62828
class Dev,EnvFile devStyle
class Prod,Vault,API,DB,Redis prodStyle
class Encryption,AES,TLS encStyle
class Secrets,JWT,DBCreds,Mangopay,EncKey secretStyle
```
**Flux** :
1. **Vault** stocke secrets sensibles (JWT signing key, DB credentials, Mangopay API key, encryption master key)
2. **Backend API** récupère secrets depuis Vault au démarrage
3. **Encryption layer** : AES-256-GCM pour PII, TLS 1.3 pour transport
4. **Stockage** : PostgreSQL (data encrypted at rest), Redis (TLS enabled)
### Secrets Management avec Vault
**Initialisation Vault** (one-time setup) :
```bash
# 1. Init Vault (génère unseal keys + root token)
vault operator init -key-shares=5 -key-threshold=3
1. Init Vault : génération 5 unseal keys + root token (Shamir secret sharing)
2. Unseal : 3 clés parmi 5 requises pour déverrouiller Vault
3. Login root + activation KV-v2 engine (path : `roadwave/`)
# 2. Unseal (3 clés requises parmi 5)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>
**Secrets stockés** :
- **JWT signing key** : Paire RS256 privée/publique
- **Database credentials** : Host, port, user, password (généré aléatoire 32 caractères)
- **Mangopay API** : Client ID, API key, webhook secret
# 3. Login root + création secrets
vault login <root-token>
vault secrets enable -path=roadwave kv-v2
```
**Stockage secrets** :
```bash
# JWT signing key (RS256 private key)
vault kv put roadwave/jwt private_key=@jwt-private.pem public_key=@jwt-public.pem
# Database credentials
vault kv put roadwave/database \
host=localhost \
port=5432 \
user=roadwave \
password=<généré-aléatoire-32-chars>
# Mangopay API
vault kv put roadwave/mangopay \
client_id=<sandbox-client-id> \
api_key=<sandbox-api-key> \
webhook_secret=<généré-aléatoire>
```
**Récupération depuis Go** :
```go
import vault "github.com/hashicorp/vault/api"
client, _ := vault.NewClient(&vault.Config{
Address: "http://vault:8200",
})
client.SetToken(os.Getenv("VAULT_TOKEN"))
secret, _ := client.KVv2("roadwave").Get(context.Background(), "database")
dbPassword := secret.Data["password"].(string)
```
**Récupération depuis Backend Go** :
- Utilisation SDK `hashicorp/vault/api`
- Authentification via token Vault (variable env VAULT_TOKEN)
- Récupération secrets via KVv2 engine
### Encryption PII (Field-level)
**Données chiffrées** (AES-256-GCM) :
- **GPS précis** : lat/lon (24h), puis geohash-5 seulement ([Règle 02](../regles-metier/02-conformite-rgpd.md))
- **Email** : chiffré en base, déchiffré à l'envoi
- **GPS précis** : lat/lon conservés 24h puis réduits à geohash-5 (~5km²) ([Règle 02](../regles-metier/02-conformite-rgpd.md))
- **Email** : chiffré en base, déchiffré uniquement à l'envoi
- **Numéro téléphone** : si ajouté (Phase 2)
**Architecture encryption** :
```go
type Encryptor struct {
masterKey []byte // 256 bits (32 bytes) depuis Vault
}
- Utilisation bibliothèque standard Go `crypto/aes` avec mode GCM (AEAD)
- Master key 256 bits (32 bytes) récupérée depuis Vault
- Chiffrement : génération nonce aléatoire + seal GCM → encodage base64
- Stockage : colonne `email_encrypted` en base PostgreSQL
func (e *Encryptor) Encrypt(plaintext string) (string, error) {
block, _ := aes.NewCipher(e.masterKey)
gcm, _ := cipher.NewGCM(block)
nonce := make([]byte, gcm.NonceSize())
rand.Read(nonce)
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Usage
email := "user@example.com"
encryptedEmail, _ := encryptor.Encrypt(email)
// Store in DB: "Ae3xK9... (base64 ciphertext)"
```
**Schema PostgreSQL** :
```sql
CREATE TABLE users (
id UUID PRIMARY KEY,
email_encrypted TEXT NOT NULL, -- AES-256-GCM chiffré
created_at TIMESTAMPTZ NOT NULL
);
-- Index sur email chiffré IMPOSSIBLE → utiliser hash pour recherche
CREATE INDEX idx_email_hash ON users(sha256(email_encrypted));
```
**Contraintes** :
- Index direct sur champ chiffré impossible
- Solution : index sur hash SHA-256 de l'email chiffré pour recherche
### HTTPS/TLS Configuration
**Let's Encrypt wildcard certificate** :
```bash
# Certbot avec DNS challenge (OVH API)
certbot certonly \
--dns-ovh \
--dns-ovh-credentials ~/.secrets/ovh.ini \
-d roadwave.fr \
-d *.roadwave.fr
- Méthode : Certbot avec DNS-01 challenge (API OVH)
- Domaines couverts : `roadwave.fr` + `*.roadwave.fr` (wildcard)
- Renouvellement : automatique via cron quotidien (30j avant expiration)
# Renouvellement auto (cron)
0 0 * * * certbot renew --post-hook "systemctl reload nginx"
```
**Nginx TLS config** :
```nginx
server {
listen 443 ssl http2;
server_name api.roadwave.fr;
ssl_certificate /etc/letsencrypt/live/roadwave.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/roadwave.fr/privkey.pem;
# TLS 1.3 uniquement
ssl_protocols TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384';
# HSTS (force HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
```
**Nginx TLS configuration** :
- Protocol : TLS 1.3 uniquement (pas de TLS 1.2 ou inférieur)
- Ciphers : TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384
- HSTS : max-age 1 an, includeSubDomains
- Security headers : X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin
## Alternatives considérées
@@ -295,24 +173,11 @@ server {
### Rate Limiting (Protection DDoS/Brute-force)
**Configuration Fiber** :
```go
import "github.com/gofiber/fiber/v3/middleware/limiter"
app.Use(limiter.New(limiter.Config{
Max: 100, // 100 requêtes
Expiration: 1 * time.Minute, // par minute
Storage: redisStorage, // Redis backend
KeyGenerator: func(c fiber.Ctx) string {
return c.IP() // Par IP
},
LimitReached: func(c fiber.Ctx) error {
return c.Status(429).JSON(fiber.Map{
"error": "Too many requests",
})
},
}))
```
**Configuration** :
- Middleware Fiber `limiter` avec backend Redis
- Limite : 100 requêtes par minute par IP (global)
- Clé de limitation : adresse IP client
- Réponse limitation : HTTP 429 "Too many requests"
**Rate limits par endpoint** :
- `/auth/login` : 5 req/min/IP (protection brute-force)
@@ -330,14 +195,11 @@ app.Use(limiter.New(limiter.Config{
| **Mangopay API key** | À la demande | Rotation manuelle si compromission |
| **Encryption master key** | Jamais (re-encryption massive) | Backup sécurisé uniquement |
**Process rotation DB credentials (Vault)** :
```bash
# Vault génère nouveau password + update PostgreSQL
vault write database/rotate-root/roadwave
# Application récupère nouveau password automatiquement
# Ancien password invalide après 1h grace period
```
**Process rotation DB credentials** :
- Vault génère automatiquement nouveau password
- Vault met à jour PostgreSQL avec nouveau password
- Application récupère nouveau password au prochain accès Vault
- Ancien password invalide après grace period de 1h
## Métriques de Succès