Project

General

Profile

Actions

Feature #1455

open
RA

Externaliser tous les secrets vers AWS SSM Parameter Store (KMS) — supprimer service-configuration.yaml

Feature #1455: Externaliser tous les secrets vers AWS SSM Parameter Store (KMS) — supprimer service-configuration.yaml

Added by Redmine Admin 14 days ago.

Status:
Submitted
Priority:
High
Assignee:
-
Start date:
07/21/2026
Due date:
% Done:

0%

Estimated time:
spec_ref:
build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
pr_url:
security_key:
severity:
paused:

Description

### Problème

Tous les secrets de production de Qwarks Connect vivent **en clair dans un fichier YAML posé à côté du binaire** : `service-configuration.yaml`, lu au démarrage par `cmd/qwarksconnectapi/main.go:69` (`os.ReadFile("./service-configuration.yaml")`). Le fichier est gitignoré, mais cela ne protège que du prochain commit — pas de ce qui a déjà eu lieu, ni de qui peut lire le disque de l'EC2.

Conséquences concrètes aujourd'hui :

- **Des clés API Kraken de production sont toujours présentes dans l'historique Git** de `QwarksConnectApi` : ajoutées dans `4e62f32`, fichier supprimé dans `90a28a3`. **Une suppression ne purge pas l'historique** — ces clés restent lisibles par quiconque clone le dépôt, et elles donnent accès à des fonds réels.
- Aucune rotation possible sans redéploiement : changer une clé = éditer un fichier sur la machine et relancer le service.
- Aucune traçabilité : impossible de savoir qui a lu ou modifié un secret, ce qui est directement attendu au titre de **DORA** et du périmètre **PSCA/MiCA**.
- Le fichier doit être recopié à la main sur chaque environnement, hors pipeline — c'est aussi une source de dérive prod/préprod.

### Contexte (technique)

Le chargement est centralisé, ce qui est une bonne nouvelle : **un seul point d'entrée à réécrire**, `parseYaml()` dans `cmd/qwarksconnectapi/main.go:68-82`.

En revanche, le périmètre est plus large que les seules clés Kraken. La struct `config` (`main.go:32-66`) porte **7 classes de secrets distinctes** :

| Secret | Déclaration | Portée si compromis |
|---|---|---|
| `salt` | `main.go:42` | Authentification : forge de jetons / dérivation de mots de passe |
| `db_pass` | `main.go:47` | Accès complet à la base RDS de production |
| `api_kraken_keys[].secret_key` + `.private_key` | `internal/rest/client/kraken.go:42-46` | **Fonds réels** (trading / retrait), N comptes |
| `api_ringover.api_key` | `internal/rest/client/ringover.go:24-27` | Envoi de SMS (codes de sécurité) |
| `api_mailchimp.api_key` | `internal/rest/client/mailchimp.go:23-26` | Envoi d'emails au nom de Qwarks |
| `api_sharepoint.cert_password` | `internal/rest/client/sharepoint.go:34-42` | Accès Microsoft Graph / SharePoint |
| Certificat SharePoint (`cert_path`) | `internal/rest/client/sharepoint.go:40` | **Fichier de certificat sur disque**, à traiter comme un secret binaire |

`QwarksConnectMaintenance` a par ailleurs **sa propre configuration** avec son propre `db_pass` (`databaseMigrations/cmd/databasemigrations/main.go:21`) — à migrer également, sans quoi le mot de passe RDS reste en clair sur un autre chemin.

**Ne migrer que les clés Kraken n'a pas de sens** : le fichier resterait sur disque avec le salt d'authentification et le mot de passe RDS dedans. Le lot pertinent est le fichier entier.

### Comportement proposé

- Introduire un port **`SecretsProvider`** (interface Go), avec deux implémentations :
- **`FileSecretsProvider`** — lit le YAML actuel. Conservé pour le dev local et les tests d'intégration, qui ne doivent pas dépendre d'AWS.
- **`SSMSecretsProvider`** — AWS SSM Parameter Store, type `SecureString`, chiffré via une **clé KMS dédiée** (CMK, pas la clé AWS par défaut), région `eu-west-3`.
- Sélection par variable d'environnement (`SECRETS_BACKEND=file|ssm`), résolue **une fois au démarrage**. `parseYaml()` devient un détail d'implémentation derrière le port, et le reste du code ne change pas : la struct `config` reste la même, seule sa source change.
- **Convention de nommage** hiérarchique : `/qwarks-connect/<env>/<domaine>/<clé>` (ex. `/qwarks-connect/prod/kraken/<account_name>/secret_key`, `/qwarks-connect/prod/db/password`). Chargement par préfixe via `GetParametersByPath` (récursif, `WithDecryption`) → une seule série d'appels au démarrage, pas un appel par secret.
- **Certificat SharePoint** : stocké en `SecureString` (base64) et matérialisé au démarrage dans un fichier temporaire à permissions restreintes, ou chargé en mémoire — à préférer si le client Graph l'accepte.
- **IAM en moindre privilège** : rôle d'instance EC2 autorisé sur `ssm:GetParametersByPath` limité au préfixe de **son** environnement, plus `kms:Decrypt` sur la seule CMK concernée. Préprod ne doit pas pouvoir lire les paramètres de prod.
- **Échec explicite au démarrage** : si un secret attendu est absent ou non déchiffrable, le service refuse de démarrer avec un message nommant le paramètre manquant. Jamais de repli silencieux sur une valeur vide.
- **Ne jamais logger une valeur de secret** : ajouter une méthode `String()`/`MarshalJSON` masquante sur les structs porteuses de secrets, pour qu'un `log.Printf("%+v", config)` ne puisse pas les recracher.
- Une fois la migration validée en préprod puis en prod, **supprimer `service-configuration.yaml` des machines** et retirer le fichier du processus de déploiement.

### Point ouvert à trancher : SSM Parameter Store ou Secrets Manager ?

La note technique OKX (§3) mentionnait **AWS Secrets Manager**. Ma recommandation est **SSM Parameter Store `SecureString` + CMK KMS**, sauf objection :

- Le seul avantage réel de Secrets Manager est la **rotation automatique par Lambda** — or ni Kraken, ni Ringover, ni Mailchimp n'exposent d'API de rotation programmatique. La rotation restera manuelle dans les deux cas.
- Parameter Store est **nettement moins cher** (paliers standard gratuits contre ~0,40 $/secret/mois), et ici le nombre de paramètres est élevé car les clés Kraken sont par compte.
- Chiffrement KMS, versioning, audit CloudTrail et politiques IAM par préfixe sont **identiques** entre les deux.

Si la conformité exige littéralement « Secrets Manager » parce que c'est le terme inscrit au dossier DORA, le port `SecretsProvider` rend le choix interchangeable : seule l'implémentation change, à coût quasi nul. **À valider avec Marc / la conformité avant implémentation.**

### Prérequis infra (hors code)

- Création de la CMK KMS `eu-west-3` et de sa politique de clé.
- Rôle IAM + instance profile attachés à l'EC2 client (`connect.qwarks.fr`) et à la machine de préprod — **à coordonner avec le client, qui possède le compte AWS**.
- Peuplement initial des paramètres (prod + préprod) à partir des fichiers existants, puis destruction de ces fichiers.

### Hors périmètre (à traiter séparément)

- **Purge de l'historique Git** (`git filter-repo` / BFG + force-push) : réécrit l'historique pour tous les clones et impose une coordination avec l'ensemble des personnes ayant cloné le dépôt. Ticket dédié. **La rotation des clés Kraken, elle, fait partie de ce ticket** — sans elle, la fuite reste exploitable même après purge.
- Migration des secrets frontend (`configNextJs/next.config.js.*`) : à évaluer dans un second temps.

### Lien avec l'epic OKX

Ce ticket **couvre la Phase 3 de l'epic #1454** (Backup OKX). Il est volontairement sorti de l'epic pour ne pas attendre un chantier de 8 à 14 semaines : les clés exposées le sont dès aujourd'hui. Lors de la création des tickets enfants de #1454, la Phase 3 devra référencer ce ticket plutôt que d'être redécoupée. Le `SecretsProvider` livré ici sera directement réutilisé pour les credentials OKX (clé + secret + **passphrase**).

## Acceptance criteria
- [ ] Un port `SecretsProvider` existe, avec une implémentation fichier (dev/tests) et une implémentation AWS SSM Parameter Store (`SecureString` + CMK KMS, `eu-west-3`).
- [ ] Le backend est sélectionné par variable d'environnement et résolu une seule fois au démarrage ; le reste du code consomme la même struct `config` qu'avant.
- [ ] **Les 7 classes de secrets** sont migrées : `salt`, `db_pass`, clés Kraken (par compte), Ringover, Mailchimp, mot de passe du certificat SharePoint, et le certificat SharePoint lui-même.
- [ ] Le `db_pass` de `QwarksConnectMaintenance` (`databaseMigrations/cmd/databasemigrations/main.go:21`) est migré par le même mécanisme.
- [ ] Les paramètres suivent la convention `/qwarks-connect/<env>/<domaine>/<clé>` et sont chargés par préfixe (`GetParametersByPath`, `WithDecryption`).
- [ ] Le rôle IAM est en moindre privilège : lecture restreinte au préfixe de son propre environnement + `kms:Decrypt` sur la seule CMK. **Vérifié par un test négatif : la préprod ne peut pas lire un paramètre de prod.**
- [ ] Un secret manquant ou non déchiffrable fait **échouer le démarrage** avec un message nommant le paramètre — aucun repli silencieux.
- [ ] Aucune valeur de secret ne peut apparaître dans les logs : les structs porteuses masquent leur contenu (`String()` / `MarshalJSON`), avec un test dédié.
- [ ] **Les clés API Kraken de production ont été révoquées et régénérées** (les précédentes sont dans l'historique Git), et les nouvelles ne transitent jamais par le dépôt.
- [ ] `service-configuration.yaml` est **absent des machines de prod et de préprod**, et ne fait plus partie de la procédure de déploiement.
- [ ] Les tests d'intégration tournent sans accès AWS (backend fichier).
- [ ] La procédure de rotation d'un secret est documentée : modifier le paramètre → redémarrer le service, sans rebuild ni édition de fichier sur la machine.

## Classification
- feature

## Complexity
- 6/10 — Le chargement est déjà centralisé en un seul point (`parseYaml()`), donc le code à écrire est contenu et sans piège algorithmique. La difficulté est ailleurs : le périmètre réel couvre 7 classes de secrets sur deux dépôts (pas seulement les clés Kraken), le certificat SharePoint est un secret binaire qui demande un traitement à part, la mise en place IAM/KMS dépend du compte AWS du client et sort du code, et une erreur de migration empêche le service de démarrer en production — la bascule doit donc être jouée en préprod d'abord, avec un plan de retour.


Related issues 1 (1 open0 closed)

Related to Feature #1454: [EPIC] Backup OKX : parité fonctionnelle Kraken → OKX, bascule (failover) et masquage des comptesSubmitted07/21/2026

Actions
Actions

Also available in: PDF Atom