ADR-0008 — Secret de transit SLY par application et trajectoire mTLS¶
Date : 2026-06-10
Catégorie : A (convention de sécurité technique, dans le cadre de l'architecture inter-app déjà validée)
Décideur : Lead architecte DYNORS
Statut : Acceptée — palier 1 implémenté 2026-06-11
Implémentation palier 1 :
- core 4f6ef51 (SlyTransitV2 HKDF verification + SlyTransitFilter modes SHARED/DUAL/PER_APP + 14 tests red-team)
- dynors-platform eb0caf8 (SlyTransitHkdf + SlyForwardSigningFilter pose les 2 signatures en DUAL)
- dynors-internal 93fcf60 (slots Codex TAIL_SLY_TRANSIT_MODE + TAIL_SLY_TRANSIT_KEY_V2)
Test pivot du blast radius : SlyTransitV2ContractTest.redTeamCleExfiltreeNePeutPasForgerVersAutreBackend — vérifie qu'une clé k_pai exfiltrée du backend paiement ne permet pas de signer un transit valide vers FISCAL. Objectif §Suivi satisfait.
Reste à faire :
- Provisionnement Vault des clés V2 par app/cercle (Ansible dynors-storage-pattern).
- Bascule progressive SHARED → DUAL → PER_APP app par app, paiement d'abord (sensibilité haute).
- Palier 2 mTLS : ADR de suivi dédié à écrire quand la PKI interne est prête.
Contexte¶
Tout appel inter-app DYNORS transite par SLY, qui signe chaque requête forwardée avec un HMAC-SHA256 (X-Sly-Timestamp + X-Sly-Signature). Chaque backend vérifie cette signature via SlyTransitFilter.
À ce jour, le secret de signature est unique et partagé par tout l'écosystème :
- côté SLY :
dynors.interapp.forward-secret: ${SLY_FORWARD_SECRET}(voirselebeyone/src/main/resources/application.yml,SlyForwardSigningFilter). - côté backends : le même
SLY_FORWARD_SECRETest injecté dans FISCAL, BOOKS, DYNORS PAIEMENT, JARAAF, et tous les satellites, et vérifié par unSlyTransitFilteridentique.
C'est une clé symétrique unique. Conséquence directe sur la surface d'attaque : la compromission d'un seul backend (ou d'un seul secret mal géré dans un seul environnement client) donne la capacité de forger des signatures de transit valides vers n'importe quel autre backend, y compris le hub paiement. Le rayon de souffle d'une fuite est l'écosystème entier.
Pour un hub qui manipule de l'argent (/paiement/**, payouts, settlement), ce niveau de couplage cryptographique est inacceptable. Le document de sécurité DYNORS PAIEMENT évoque déjà un mesh mTLS (Istio/Linkerd) comme cible — cette ADR formalise la trajectoire et un palier intermédiaire actionnable tout de suite.
Décision¶
Remplacer le secret de transit unique par une stratégie à deux paliers :
Palier 1 (court terme) — secret de transit dérivé par source-app¶
SLY ne signe plus avec un secret global, mais avec un secret dérivé par application appelante :
k_app = HKDF-SHA256(masterKey = SLY_FORWARD_ROOT, info = "sly-transit:" + sourceApp)
signature = HMAC-SHA256(k_app, timestamp + "." + method + "." + path + "." + bodySha256)
- La racine
SLY_FORWARD_ROOTne quitte jamais SLY (Vault, lecture SLY uniquement). - Chaque backend reçoit uniquement son propre
k_app(chemin Vaultdynors/{env}/{app}/sly-transit#key), jamais la racine ni les clés des autres apps. SlyTransitFiltervérifie avec sa clé locale ; il ne peut donc valider que les requêtes signées pour lui, et ne détient pas de quoi forger vers un tiers.- Le
source-appentre dans la dérivation : un backend compromis ne peut pas se faire passer pour une autre app appelante.
Palier 2 (cible) — mTLS de service à service¶
Sur le mesh (à partir du chemin /paiement/**, puis FISCAL/BOOKS), passer à mTLS : SLY présente un certificat client, chaque backend valide l'identité par le SAN du certificat (spiffe://dynors/{app}). La signature HMAC applicative devient une défense en profondeur, plus l'unique barrière.
L'ordre de migration est dicté par la sensibilité : paiement d'abord, puis FISCAL et BOOKS, puis les satellites.
Conséquences¶
Bénéfices¶
- La compromission d'un backend ne donne plus aucun pouvoir de forge vers les autres : le rayon de souffle est réduit à l'app compromise.
- L'usurpation de
source-appdevient cryptographiquement impossible (lesource-appest lié à la clé). - Rotation ciblée : on peut révoquer/roter la clé d'une seule app sans toucher aux autres (impossible aujourd'hui — toute rotation est globale et bloquante).
- Compatible avec la stratégie Vault/ESO existante (un chemin de secret par app, principe du moindre privilège déjà en vigueur).
Coûts / contraintes induites¶
- SLY doit dériver
k_apppar appel (coût négligeable, HKDF mis en cache parsource-app). - Chaque backend doit recevoir un secret dédié au lieu du secret partagé → mise à jour des
ExternalSecret/manifestes SIRRAT (un slot Vault par app au lieu d'un slot commun). - Migration non instantanée : nécessite une fenêtre de double vérification (voir Plan d'exécution) pour ne pas casser l'inter-app en cours de bascule.
SlyTransitFilter(fourni pardynors-commons) doit accepter transitoirement les deux schémas, puis être durci.
Composants impactés¶
dynors-platform/selebeyone—SlyForwardSigningFilter,SlyProperties(racine + dérivation).dynors-commons—SlyTransitFilter(vérification par clé locale, support double-schéma transitoire).- Manifestes SIRRAT /
ExternalSecretde chaque app — un slotsly-transitpar app. - Codex SIRRAT —
TAIL_SECRET_*: référence du secret de transit par app. - DYNORS PAIEMENT en premier consommateur de la cible mTLS.
Alternatives considérées¶
Alternative A — Statu quo (secret unique partagé). Écartée : rayon de souffle = écosystème entier, rotation globale bloquante, usurpation source-app triviale en cas de fuite. Incompatible avec un hub financier.
Alternative B — Secrets indépendants par app, gérés à la main (pas de dérivation). Écartée : N secrets à générer/distribuer/roter manuellement, surface d'erreur opérationnelle élevée. La dérivation HKDF depuis une racine donne le même isolement avec une seule racine à protéger.
Alternative C — Aller directement au mTLS sans palier 1. Écartée comme étape unique : le mTLS demande une maturité mesh (PKI interne, rotation de certs, sidecars) qui n'est pas en place sur tous les environnements (notamment on-premise client / docker-compose). Le palier 1 réduit le risque immédiatement sans dépendre de cette maturité ; le mTLS reste la cible.
Plan d'exécution¶
- HKDF dans SLY : implémenter la dérivation
k_app+ cache, derrière un flagdynors.interapp.transit-mode = SHARED | PER_APP. DéfautSHAREDau déploiement. - Double vérification backends :
SlyTransitFilteraccepte signatureSHAREDouPER_APPpendant la fenêtre de migration. - Provisionnement Vault : créer
dynors/{env}/{app}/sly-transit#keypour chaque app, dérivé hors-ligne depuis la racine. Mettre à jour les manifestes SIRRAT. - Bascule par app : passer
transit-mode = PER_APPapp par app, en commençant par DYNORS PAIEMENT. Vérifier les métriques d'erreur de signature à chaque étape. - Durcissement : une fois toutes les apps en
PER_APP, retirer le supportSHAREDdeSlyTransitFilteret supprimerSLY_FORWARD_SECRETglobal. - mTLS (cible) : ADR de suivi dédié quand la PKI interne est prête, en commençant par le chemin paiement.
Suivi¶
Critère de succès palier 1 : après bascule, aucun backend ne détient un secret permettant de signer un transit vers une autre app ; la rotation de la clé d'une app n'impacte aucune autre.
Critère de mesure : un test de sécurité (red-team interne) prouve qu'un secret de transit exfiltré depuis l'app A ne permet pas d'appeler /paiement/** au nom de l'app B.
Signal de régression : toute nouvelle app qui réutilise le secret partagé après la fin de migration est un défaut de conformité à refuser en revue.
Références¶
- Règle inter-app :
.cursor/rules/security-interapp-tenants.mdc§3, §5 - Stratégie secrets :
.cursor/rules/vault-secrets-dynors.mdc - Sécurité paiement (cible mTLS) :
docs/projet-nouveau/DynorsPaiement/DYNORS_PAIEMENT_SYNTHESE_PDF_v1_0.md§6 - Gouvernance URLs / SLY :
claude-handoff-dynors/DYNORS_SLY_URL_GOVERNANCE_PORTAL.md