Français
  • saas
  • multi-locataire
  • postgres
  • sécurité au niveau des lignes
  • rls
  • fonction de déclenchement
  • architecture multi-locataires
  • architecture mono-locataire

Mise en œuvre de la multi-location avec PostgreSQL : Apprenez à travers un exemple simple et réel

Découvrez comment mettre en œuvre une architecture multi-locataires avec la sécurité au niveau des lignes (RLS) de PostgreSQL et les rôles de base de données à travers un exemple réel pour une isolation sécurisée des données entre les locataires.

Yijun
Yijun
Developer

Dans certains de nos articles précédents, nous avons exploré le concept de multi-location et ses applications dans les produits et scénarios commerciaux réels.

Dans cet article, nous allons explorer comment mettre en œuvre une architecture multi-locataires pour votre application en utilisant PostgreSQL d'un point de vue technique.

Qu'est-ce que l'architecture mono-locataire ?

L'architecture mono-locataire fait référence à une architecture logicielle où chaque client dispose de sa propre instance dédiée de l'application et de la base de données.

Dans cette architecture, les données et les ressources de chaque locataire sont complètement isolées des autres locataires.

Mono-location

Qu'est-ce que l'architecture multi-locataires ?

L'architecture multi-locataires est une architecture logicielle où plusieurs clients (locataires) partagent la même instance d'application et l'infrastructure tout en maintenant l'isolement des données. Dans cette architecture, une seule instance du logiciel sert plusieurs locataires, chaque locataire ayant ses données séparées des autres par divers mécanismes d'isolement.

Multi-location

Architecture mono-locataire et architecture multi-locataires

L'architecture mono-locataire et l'architecture multi-locataires diffèrent sur des aspects tels que l'isolement des données, l'utilisation des ressources, l'évolutivité, la gestion et la maintenance, et la sécurité.

Dans l'architecture mono-locataire, chaque client dispose d'un espace de données indépendant, ce qui conduit à une utilisation plus faible des ressources mais relativement plus simple pour la personnalisation. En général, le logiciel mono-locataire est adapté aux besoins spécifiques des clients, comme les systèmes d'inventaire pour un fournisseur de tissus particulier ou une application de blog personnel. La caractéristique commune est que chaque client occupe une instance séparée du service d'application, facilitant la personnalisation pour répondre à des exigences spécifiques.

Dans une architecture multi-locataires, plusieurs locataires partagent les mêmes ressources sous-jacentes, entraînant une utilisation plus élevée des ressources. Cependant, il est crucial d'assurer l'isolement et la sécurité des données.

L'architecture multi-locataires est souvent l'architecture logicielle préférée lorsque les fournisseurs de services offrent des services standardisés à différents clients. Ces services ont généralement de faibles niveaux de personnalisation, et tous les clients partagent la même instance d'application. Lorsqu'une application nécessite une mise à jour, la mise à jour d'une instance d'application équivaut à mettre à jour l'application pour tous les clients. Par exemple, la gestion de la relation client (CRM) est une exigence standardisée. Ces systèmes utilisent généralement une architecture multi-locataires pour fournir le même service à tous les locataires.

Stratégies d'isolement des données des locataires dans une architecture multi-locataires

Dans une architecture multi-locataires, tous les locataires partagent les mêmes ressources sous-jacentes, rendant crucial l'isolement des ressources entre eux. Cet isolement n'a pas nécessairement besoin d'être physique ; il suffit de s'assurer que les ressources entre les locataires ne sont pas visibles les unes pour les autres.

Dans la conception de l'architecture, divers degrés d'isolement des ressources entre les locataires peuvent être atteints :

Isolé à partagé

En général, plus les ressources sont partagées entre les locataires, plus le coût d'itération et de maintenance du système est faible. Inversement, moins les ressources sont partagées, plus le coût est élevé.

Commencer la mise en œuvre de la multi-location avec un exemple réel

Dans cet article, nous allons utiliser un système CRM comme exemple pour introduire une architecture multi-locataires simple mais pratique.

Nous reconnaissons que tous les locataires utilisent les mêmes services standards, nous avons donc décidé de partager les mêmes ressources de base entre tous les locataires, et nous avons mis en place une isolation des données entre différents locataires au niveau de la base de données en utilisant la sécurité au niveau des lignes de PostgreSQL.

De plus, nous allons créer une connexion de données séparée pour chaque locataire afin de faciliter une meilleure gestion des autorisations des locataires.

Ensuite, nous allons présenter comment mettre en œuvre cette architecture multi-locataires.

Comment mettre en œuvre une architecture multi-locataires avec PostgreSQL

Ajouter un identifiant de locataire pour toutes les ressources

Dans un système CRM, nous aurons beaucoup de ressources et elles sont stockées dans différentes tables. Par exemple, les informations des clients sont stockées dans la table customers.

Avant de mettre en œuvre la multi-location, ces ressources ne sont associées à aucun locataire :

Pour différencier les locataires possédant différentes ressources, nous introduisons une table tenants pour stocker les informations des locataires (où db_user et db_user_password sont utilisés pour stocker les informations de connexion à la base de données pour chaque locataire, qui seront détaillées ci-dessous). De plus, nous ajoutons un champ tenant_id à chaque ressource pour identifier à quel locataire elle appartient :

Désormais, chaque ressource est associée à un tenant_id, ce qui nous permet théoriquement d'ajouter une clause where à toutes les requêtes pour restreindre l'accès aux ressources pour chaque locataire :

Cela semble simple et faisable au premier regard. Cependant, cela présentera les problèmes suivants :

  • Presque chaque requête inclura cette clause where, encombrant le code et le rendant plus difficile à maintenir, surtout lors de l'écriture de déclarations de jointure complexes.
  • Les nouveaux arrivants dans le code peuvent facilement oublier d'ajouter cette clause where.
  • Les données entre différents locataires ne sont pas réellement isolées, chaque locataire ayant encore la possibilité d'accéder aux données appartenant à d'autres locataires.

Par conséquent, nous n'adopterons pas cette approche. Au lieu de cela, nous allons utiliser la sécurité au niveau des lignes de PostgreSQL pour répondre à ces préoccupations. Cependant, avant de procéder, nous créerons un compte de base de données dédié pour chaque locataire pour accéder à cette base de données partagée.

Configurer les rôles DB pour les locataires

Il est une bonne pratique d'attribuer un rôle de base de données à chaque utilisateur pouvant se connecter à la base de données. Cela permet de mieux contrôler l'accès de chaque utilisateur à la base de données, facilitant l'isolement des opérations entre différents utilisateurs et améliorant la stabilité et la sécurité du système.

Puisque tous les locataires ont les mêmes permissions d'opération de base de données, nous pouvons créer un rôle de base pour gérer ces permissions :

Ensuite, pour différencier chaque rôle de locataire, un rôle hérité du rôle de base est attribué à chaque locataire lors de sa création :

Ensuite, les informations de connexion à la base de données pour chaque locataire seront stockées dans la table tenants :

iddb_userdb_user_password
x2euiccrm_tenant_x2euicpa55w0rd

Ce mécanisme offre à chaque locataire son propre rôle de base de données, et ces rôles partagent les autorisations accordées au rôle crm_tenant.

Nous pouvons ensuite définir l'étendue des autorisations pour les locataires en utilisant le rôle crm_tenant :

  • Les locataires devraient avoir un accès CRUD à toutes les tables de ressources du système CRM.
  • Les tables non liées aux ressources du système CRM devraient être invisibles pour les locataires (en supposant uniquement la table systems).
  • Les locataires ne devraient pas être en mesure de modifier la table tenants, et seuls les champs id et db_user devraient leur être visibles pour interroger leur propre identifiant de locataire lors de l'exécution d'opérations sur la base de données.

Une fois les rôles pour les locataires configurés, lorsqu'un locataire demande l'accès au service, nous pouvons interagir avec la base de données en utilisant le rôle de base de données représentant ce locataire :

Protéger les données des locataires en utilisant la sécurité au niveau des lignes de PostgreSQL

Jusqu'à présent, nous avons établi des rôles de base de données correspondants pour les locataires, mais cela ne restreint pas l'accès aux données entre les locataires. Ensuite, nous allons utiliser la fonctionnalité de sécurité au niveau des lignes de PostgreSQL pour limiter l'accès de chaque locataire à ses propres données.

Dans PostgreSQL, les tables peuvent avoir des politiques de sécurité des lignes qui contrôlent quelles lignes peuvent être accédées par des requêtes ou modifiées par des commandes de manipulation de données. Cette fonctionnalité est également connue sous le nom de RLS (Row-Level Security).

Par défaut, les tables n'ont pas de politique de sécurité des lignes. Pour utiliser RLS, vous devez l'activer pour la table et créer des politiques de sécurité qui s'exécutent chaque fois que la table est accédée.

Prenons la table customers dans le système CRM comme exemple, nous allons activer RLS et créer une politique de sécurité pour restreindre chaque locataire à n'accéder qu'aux données de ses propres clients :

Dans l'instruction de création de la politique de sécurité :

  • for all (optionnel) indique que cette politique d'accès sera utilisée pour les opérations select, insert, update, et delete sur la table. Vous pouvez spécifier une politique d'accès pour des opérations spécifiques en utilisant for suivi du mot-clé de la commande.
  • to crm_tenant indique que cette politique s'applique aux utilisateurs avec le rôle de base de données crm_tenant, c'est-à-dire tous les locataires.
  • as restrictive spécifie le mode d'application de la politique, indiquant que l'accès doit être strictement limité. Par défaut, une table peut avoir plusieurs politiques, plusieurs politiques permissives seront combinées avec une relation OR. Dans ce scénario, nous déclarons cette politique comme restrictive car nous voulons que cette vérification de politique soit obligatoire pour les utilisateurs appartenant aux locataires du système CRM.
  • L'expression using définit les conditions pour l'accès réel, restreignant l'utilisateur de la base de données qui interroge actuellement à ne voir que les données appartenant à leur locataire respectif. Cette contrainte s'applique aux lignes sélectionnées par une commande (select, update, ou delete).
  • L'expression with check définit la contrainte nécessaire lors de la modification des lignes de données (insert ou update), garantissant que les locataires ne peuvent ajouter ou mettre à jour que des enregistrements pour eux-mêmes.

Utiliser RLS pour contraindre l'accès des locataires à nos tables de ressources offre plusieurs avantages :

  • Cette politique ajoute effectivement where tenant_id = (select id from tenants where db_user = current_user) à toutes les opérations de requête (select, update, ou delete). Par exemple, quand vous exécutez select * from customers, c'est équivalent à exécuter select * from customers where tenant_id = (select id from tenants where db_user = current_user). Cela élimine le besoin d'ajouter explicitement des conditions where dans le code de l'application, le simplifiant et réduisant la probabilité d'erreurs.
  • Cela contrôle centralement les permissions d'accès aux données entre différents locataires au niveau de la base de données, atténuant le risque de vulnérabilités ou d'incohérences dans l'application, améliorant ainsi la sécurité du système.

Cependant, il y a quelques points à noter :

  • Les politiques RLS sont exécutées pour chaque ligne de données. Si les conditions de requête dans la politique RLS sont trop complexes, cela pourrait avoir un impact significatif sur la performance du système. Heureusement, notre vérification de données de locataire est assez simple et n'affectera pas la performance. Si vous prévoyez de mettre en œuvre d'autres fonctionnalités en utilisant RLS plus tard, vous pouvez suivre les recommandations de performance de Row-Level Security de Supabase pour optimiser la performance de RLS.
  • Les politiques RLS ne remplissent pas automatiquement tenant_id lors des opérations insert. Elles ne font que restreindre les locataires à insérer leurs propres données. Cela signifie que lors de l'insertion de données, nous devons toujours fournir l'ID du locataire, ce qui est incohérent avec le processus de requête et peut conduire à des confusions pendant le développement, augmentant la probabilité d'erreurs (cela sera abordé dans les étapes suivantes).

En plus de la table customers, nous devons appliquer les mêmes opérations à toutes les tables de ressources du système CRM (ce processus peut être un peu fastidieux, mais nous pouvons écrire un programme pour le configurer lors de l'initialisation de la table), isolant ainsi les données des différents locataires.

Créer une fonction de déclenchement pour l'insertion de données

Comme mentionné précédemment, RLS (Row-Level Security) nous permet d'exécuter des requêtes sans nous soucier de l'existence de tenant_id, car la base de données le gère automatiquement. Cependant, pour les opérations insert, nous devons toujours spécifier manuellement le tenant_id correspondant.

Afin d'obtenir une commodité similaire à RLS pour l'insertion de données, nous avons besoin que la base de données puisse gérer automatiquement tenant_id lors de l'insertion de données.

Cela a un avantage clair : au niveau du développement d'application, nous n'avons plus besoin de considérer à quel locataire les données appartiennent, réduisant la probabilité d'erreurs et allégeant notre charge mentale lorsque nous développons des applications multi-locataires.

Heureusement, PostgreSQL fournit une puissante fonctionnalité de déclenchement.

Les déclenchements sont des fonctions spéciales associées aux tables qui exécutent automatiquement des actions spécifiques (comme l'insertion, la mise à jour, ou la suppression) lorsqu'elles sont effectuées sur la table. Ces actions peuvent être déclenchées au niveau des lignes (pour chaque ligne) ou au niveau des instructions (pour l'ensemble de l'instruction). Avec les déclencheurs, nous pouvons exécuter une logique personnalisée avant ou après des opérations spécifiques de la base de données, ce qui nous permet d'atteindre facilement notre objectif.

Tout d'abord, créons une fonction de déclenchement set_tenant_id à exécuter avant chaque insertion de données :

Ensuite, associons cette fonction de déclenchement à la table customers pour les opérations d'insertion (similaire à l'activation de RLS pour une table, cette fonction de déclenchement doit être associée à toutes les tables pertinentes) :

Ce déclencheur garantit que les données insérées contiennent le bon tenant_id. Si les nouvelles données incluent déjà un tenant_id, la fonction de déclenchement ne fait rien. Sinon, elle remplit automatiquement le champ tenant_id en fonction des informations de l'utilisateur actuel dans la table tenants.

De cette façon, nous obtenons une gestion automatique de tenant_id au niveau de la base de données lors de l'insertion de données par les locataires.

Résumé

Dans cet article, nous avons approfondi l'application pratique de l'architecture multi-locataires, en utilisant un système CRM comme exemple pour démontrer une solution pratique utilisant la base de données PostgreSQL.

Nous discutons de la gestion des rôles de base de données, du contrôle d'accès, et de la fonctionnalité de sécurité au niveau des lignes de PostgreSQL pour garantir l'isolement des données entre les locataires. De plus, nous utilisons des fonctions de déclenchement pour réduire la charge cognitive des développeurs dans la gestion de différents locataires.

C'est tout pour cet article. Si vous souhaitez améliorer davantage votre application multi-locataires avec la gestion des accès utilisateurs, vous pouvez consulter Un guide facile pour commencer avec les organisations Logto - pour construire une application multi-locataires pour plus d'informations.