Sécurité multi-tenant : les pièges spécifiques au SaaS B2B
Isolation logique, contexte de tenant, caches partagés, tâches asynchrones : les endroits où la frontière entre clients cède dans une architecture SaaS mutualisée.
- Multi-tenant
- SaaS
Dans un SaaS B2B, la promesse implicite faite à chaque client est simple : ses données ne sont visibles que par lui. Cette promesse ne repose sur aucune barrière physique. Elle tient à une condition de filtrage présente dans le code, et à sa présence dans tous les chemins d’accès, sans exception.
C’est ce qui rend la sécurité multi-tenant particulière : ce n’est pas une fonctionnalité qu’on implémente une fois, c’est une propriété qu’il faut maintenir à chaque nouvelle ligne de code.
Trois modèles d’isolation, trois profils de risque
Base de données par client. Chaque organisation dispose de sa propre base. L’isolation est forte, une requête mal filtrée ne peut pas franchir la frontière. En contrepartie, l’exploitation devient lourde au-delà de quelques dizaines de clients, et le risque se déplace : il porte sur le mécanisme d’aiguillage qui choisit la base à interroger.
Schéma par client. Compromis intermédiaire, avec les mêmes bénéfices atténués et une complexité de migration réelle.
Base partagée avec colonne discriminante. Le modèle le plus répandu : toutes les organisations cohabitent dans les mêmes tables, distinguées par une colonne organization_id. Souple, économique, facile à exploiter, et entièrement dépendant du code applicatif pour la séparation. C’est ce modèle qui concentre l’essentiel des constats.
Le choix n’est pas seulement technique : il détermine ce qui se passe le jour où un développeur oublie une clause de filtrage.
Le piège central : le filtrage laissé à la charge du développeur
Dans une base partagée, la question déterminante est : que se passe-t-il si quelqu’un oublie le filtre ?
Si la réponse est « la requête renvoie les données de tous les clients », l’architecture est fragile par construction. Chaque nouvel endpoint, chaque nouveau rapport, chaque script de maintenance est une occasion de fuite. La discipline individuelle ne suffit pas à l’échelle d’une équipe qui grandit et se renouvelle.
Trois approches déplacent la charge de la discipline vers le système :
- Le filtrage automatique dans la couche d’accès aux données : le contexte d’organisation est porté par la requête en cours, et toute requête est filtrée par défaut. Contourner ce filtre exige une action explicite, visible en revue de code.
- La sécurité au niveau des lignes, appliquée directement par le moteur de base de données : la politique est vérifiée par le SGBD, même si le code applicatif l’oublie.
- Les tests automatisés de séparation : une suite de tests qui, pour chaque endpoint, tente d’accéder aux ressources d’une autre organisation et vérifie que la réponse est un refus. Cette suite est le seul filet qui grandit en même temps que l’application.
Les endroits où la frontière cède habituellement
Même avec un filtrage correct sur le chemin principal, plusieurs zones échappent régulièrement au contrôle.
Les caches. Une clé de cache construite sur l’identifiant de ressource sans y intégrer l’organisation fait servir à un client le résultat calculé pour un autre. La règle : toute clé de cache dérivant de données de tenant doit inclure l’identifiant de tenant.
Les traitements asynchrones. Une tâche de fond s’exécute hors du contexte d’une requête HTTP. Le mécanisme qui portait l’identité de l’utilisateur n’existe plus, et le code de la tâche tourne souvent avec les droits maximaux. Le contexte d’organisation doit être transmis explicitement dans la charge utile du message, et vérifié à l’exécution.
La recherche. Les moteurs d’indexation stockent fréquemment tous les documents dans un index commun, la séparation reposant sur un filtre ajouté à la requête. Un filtre oublié, ou une requête construite à partir d’entrées utilisateur, expose l’index entier. Les agrégations et le comptage de résultats fuitent parfois des informations même quand les documents eux-mêmes restent inaccessibles.
Les fichiers. Un document stocké sous un chemin prévisible dans un espace commun, servi par une URL sans contrôle d’accès, sort du périmètre du filtrage applicatif. Les URL signées à durée limitée sont préférables, à condition que la durée soit courte et le lien non indexable.
Les identifiants séquentiels. Ils ne créent pas la faille, mais ils la rendent triviale à exploiter et à découvrir massivement. Des identifiants non devinables (UUID, identifiants aléatoires) constituent une défense en profondeur utile, jamais un substitut au contrôle d’accès.
L’accès support. Presque tous les SaaS disposent d’une fonction permettant à un collaborateur interne de consulter le compte d’un client pour l’aider. C’est un contournement légitime de la séparation, et donc une cible de choix. Elle exige : une authentification renforcée, une journalisation systématique de chaque usage, une durée limitée, et idéalement une notification au client concerné.
Les fuites indirectes
Certaines informations traversent la frontière sans qu’aucune donnée ne soit lue :
- Un message d’erreur qui distingue « ressource inexistante » de « accès refusé » confirme l’existence d’un objet appartenant à un autre client.
- Un compteur global (nombre total d’utilisateurs, identifiants incrémentaux visibles) renseigne sur l’activité commerciale de l’éditeur et de ses clients.
- Un sous-domaine par client permet d’énumérer la liste des clients, information parfois confidentielle.
- Des différences de temps de réponse révèlent l’existence d’un enregistrement.
Ces fuites sont rarement critiques isolément. Elles deviennent significatives quand elles alimentent une attaque ciblée, ou quand la simple liste des clients d’un éditeur constitue une information commercialement sensible.
Ce qu’il faut tester, et comment
Un test sérieux de séparation multi-tenant suppose deux organisations réellement peuplées, avec des données distinctes et identifiables, et des comptes dans chacune. La démarche consiste ensuite à parcourir systématiquement la surface : pour chaque endpoint, chaque paramètre acceptant un identifiant, chaque export, chaque webhook, on substitue une référence appartenant à l’autre organisation et on observe la réponse.
Ce travail est fastidieux, et c’est précisément pourquoi il est rarement fait en interne. Il gagne à être partiellement automatisé : une fois la mécanique en place dans la suite de tests, elle protège durablement contre les régressions, la catégorie de défaut la plus fréquente sur ce sujet, puisqu’un contrôle correct un jour peut disparaître lors d’une refonte six mois plus tard.