RLS et base dédiée par projet : le choix d’isolation multi-tenant d’Aurabase
Cherchez « RLS multi-tenant postgres » et vous tombez presque partout sur le même schéma : une base partagée, une colonne tenant_id, une policy qui filtre les lignes. Ce n’est pas le modèle qu’Aurabase utilise pour isoler ses projets les uns des autres. Chaque projet reçoit sa propre base Postgres 16, jamais partagée avec un autre client — la RLS reste bien là, mais à un autre étage : dans votre base, pour vos propres utilisateurs.
« RLS » et « multi-tenant » se retrouvent accolés dans la quasi-totalité du contenu déjà publié sur le sujet — un choix légitime pour beaucoup d’architectures SaaS, mais qui n’est pas celui d’Aurabase pour séparer ses propres clients entre eux. Ce post explique la différence avec le code de provisioning réel (services/aura-provisioner) et les policies RLS effectivement posées, pas une description marketing simplifiée. Pour une vue d’ensemble de ce que couvre le moteur Postgres managé d’Aurabase au-delà de l’isolation, voir la documentation base de données.
- Entre projets, Aurabase isole par base Postgres dédiée, jamais par RLS seule — chaque projet a sa propre base physique, sur un cluster CNPG dédié (palier entreprise) ou sur le cluster CNPG de sa propre organisation (free/pro/team), jamais partagé avec une autre organisation.
- La RLS (
auth.uid(),auth.role(),auth.jwt()) reste active et recommandée à l’intérieur de votre base, pour isoler vos propres utilisateurs — même convention que Supabase. service_roleet les rôles d’administration par projet contournent la RLS par conception (BYPASSRLS) : un choix d’architecture assumé pour les opérations serveur, pas une faille.- Une régression déjà corrigée dans ce dépôt — des droits
PUBLICaccordés par erreur sur d’anciens schémas partagés — illustre concrètement pourquoi une frontière au niveau base résiste mieux qu’une frontière purement applicative.
Le raccourci que prend la plupart des guides RLS multi-tenant
Le pattern le plus documenté pour du multi-tenant Postgres tient en trois lignes : une seule base, une colonne tenant_id sur chaque table, une policy RLS qui compare cette colonne à une valeur extraite du JWT. C’est économique — un pool de connexions, un schéma, une seule instance à faire tourner — et ça marche bien quand les tenants sont nombreux, petits, et de faible enjeu individuel.
Le compromis est réel : la frontière entre deux clients devient une expression SQL, évaluée table par table. Une policy oubliée sur une nouvelle table, une connexion qui tourne avec un rôle superuser, un script de debug lancé en direct — chacun de ces incidents, pourtant banals en exploitation, peut exposer silencieusement les lignes de tous les tenants en même temps. La frontière de sécurité et la frontière technique (la base) sont alors exactement la même chose.
Deux architectures, jamais une base partagée entre projets
Depuis une consolidation récente du provisioner (repérée dans le code comme « Task 12 »), un projet Postgres actif chez Aurabase relève d’exactement deux architectures — les anciens modèles à base réellement partagée entre plusieurs projets ont été retirés du chemin de provisioning.
Le palier du projet décide laquelle des deux s’applique — et c’est le code qui tranche, pas une case cochée dans un dashboard :
| Dimension | FullyDedicated (entreprise) | SharedClusterDedicated (free/pro/team) |
|---|---|---|
| Cluster CNPG | Dédié à ce seul projet | Partagé, mais jamais entre deux organisations |
| Base Postgres | app, seul projet dessus | project_<uuid>, une par projet sur le cluster |
| Login PostgreSQL | Un seul projet sur le cluster : pas de risque d’appartenance croisée | Login par-projet (F-013), membre de ses seuls rôles tenant_<uuid> |
Sur les deux architectures, la base ou le cluster n’héberge jamais deux organisations différentes — la question n’est donc pas « vos données sont-elles isolées » mais « votre projet a-t-il du compute CloudNativePG pour lui seul, ou le partage-t-il avec d’autres projets de la même organisation ».
Cette consolidation à deux architectures est récente : le code portait auparavant deux voies supplémentaires — un « master partagé » où plusieurs projets cohabitaient dans la même base, isolés uniquement par schéma, et une variante postgrest_dedicated_shared_db. Une migration dédiée les a retirées et resserré la contrainte de la table projects aux deux seules valeurs restantes, précisément parce que le modèle à schémas partagés était la source du bug décrit plus bas.
Pourquoi une base dédiée bat une RLS partagée entre clients
Une base Postgres distincte est une frontière au niveau connexion, pas au niveau ligne. Un rôle applicatif connecté à la base du projet A ne peut tout simplement pas interroger les tables du projet B — il n’a pas de session ouverte dessus. Cette propriété tient même si une policy RLS est mal écrite, absente sur une table, ou contournée par un rôle élevé : le pire cas reste confiné à l’intérieur d’une seule base.
Ce dépôt porte d’ailleurs la trace d’un vrai bug qui illustre le risque inverse. Sous l’ancien modèle à schémas partagés (retiré depuis), provision_postgres_schema accordait par erreur des droits GRANT ALL ... TO PUBLIC sur chaque schéma de projet — PUBLIC s’appliquant à tout rôle de la base sans condition d’appartenance, un login isolé par projet pouvait lire et écrire dans le schéma d’un autre. Une migration correctrice (066) a retiré ces droits sur l’existant.
provisioning.rs le documente noir sur blanc : « chaque projet a déjà sa propre base Postgres physique ». Une frontière au niveau base rend une classe entière de bugs de ce type simplement inatteignable, plutôt que de compter sur le fait que chaque policy soit toujours écrite correctement.Un correctif du 23 août 2026 va dans le même sens : un REVOKE ALL ON SCHEMA public posé sans condition par le provisioner a été rendu conditionnel à la topologie, parce qu’il n’apportait une isolation réelle que sur l’ancien modèle à base partagée — sur les deux architectures actuelles, il bloquait sans bénéfice l’import de dumps SQL qui référencent explicitement public.<table>.
La RLS reste là — dans votre base, pour vos utilisateurs
Rien de ce qui précède ne rend la RLS inutile — elle change simplement d’étage. Une fois dans votre base de projet, Aurabase expose exactement la convention PostgREST reprise par Supabase : trois fonctions SQL qui lisent les claims du JWT posés par la gateway dans request.jwt.claims.
Ces helpers sont utilisés dans les policies réelles d’Aurabase elle-même — pas seulement documentés pour les vôtres. Voici la policy qui protège storage_objects, telle qu’elle est posée dans le dépôt (reformatée sur plusieurs lignes pour la lecture) :
Dans votre propre schéma project_<uuid>, celui qui porte vos tables applicatives, Aurabase ne pose délibérément aucune policy à votre place — le code le documente comme « modèle Supabase » : la RLS de vos tables reste de votre responsabilité, avec les mêmes fonctions, la même syntaxe.
service_role contourne la RLS — par conception, pas par accident
Postgres propose nativement un attribut de rôle, BYPASSRLS, qui ignore toutes les policies. Aurabase l’utilise volontairement sur deux familles de rôles : aura_service_role (le rôle serveur, jamais exposé côté navigateur) et le rôle d’administration propre à chaque projet, utilisé le temps d’opérations DDL comme ALTER SCHEMA ... OWNER TO.
Le rôle qui sert vos requêtes anon/authenticated — tenant_<uuid> — n’a lui aucun BYPASSRLS : la RLS s’y applique normalement, sans exception. En prime, les schémas système propres à chaque projet (_auth, _storage, _platform) reçoivent une RLS activée sans policy — donc un refus total par défaut pour tout rôle non-bypass, une défense en profondeur au cas où un chemin applicatif y accéderait un jour par erreur.
service_role côté Supabase. Le point n’est pas d’éviter BYPASSRLS, c’est de ne jamais l’accorder à un rôle atteignable depuis un client, et de le confiner à un projet unique.Ce rôle serveur s’inscrit dans une posture plus large — rôles prédéfinis, RBAC custom, audit logs — détaillée sur la page Sécurité & RBAC.
Sur un cluster partagé, la base ne fait pas tout le travail seule
Sur le palier SharedClusterDedicated, plusieurs projets d’une même organisation cohabitent sur un cluster CNPG unique. La base physique sépare déjà les projets entre eux, mais les rôles PostgreSQL — eux — sont des objets globaux au cluster, pas à la base. Aurabase ajoute donc une couche : un login PostgreSQL distinct par projet.
Chaque projet se connecte avec son propre login, membre uniquement de ses propres rôles tenant_<uuid> / tenant_<uuid>_admin — jamais de ceux d’un autre projet du même cluster. La base isole déjà les données ; ce login par projet isole en plus l’identité qui s’y connecte, pour qu’un incident sur un projet ne donne à son login aucune appartenance à hériter vers un autre.
RLS seule ou base dédiée : comment trancher pour votre propre SaaS
Le choix d’Aurabase n’est pas une règle universelle — c’est un compromis pour un cas précis : isoler des clients entre eux, potentiellement avec des exigences de conformité différentes, sur une plateforme qu’ils ne contrôlent pas. Si vous construisez votre propre SaaS, la même question se pose à vous, à une échelle différente.
- RLS avec
tenant_iddans une base partagée — pertinent quand vos tenants sont nombreux, individuellement de faible enjeu, et que le coût d’une base par tenant serait disproportionné. Testez chaque policy avecpg_prove, sur chaque table, sans exception. - Base ou schéma dédié — pertinent dès qu’un tenant a un enjeu de conformité propre (santé, RH, secteur public), un volume qui justifie l’isolation des performances, ou que le coût d’une fuite entre deux clients spécifiques serait disproportionné face au coût d’infrastructure supplémentaire.
Le palier tarifaire d’Aurabase applique ce même arbitrage à ses propres clients : base partagée par organisation par défaut, cluster dédié quand l’enjeu du projet le justifie. Pour les patterns RLS à l’intérieur de votre propre base — ownership, multi-tenant par organisation, hiérarchie de rôles — le guide RLS en production détaille les trois cas avec des tests pgTAP. Et si l’API auto-générée sur votre schéma vous intéresse au-delà de REST, le comparatif sur pg_graphql face à Hasura et PostGraphile couvre l’autre moitié de la surface Postgres exposée par Aurabase.