# Atelier : les mêmes droits SQL, des lignes différentes

Cette expérience locale montre ce que la sécurité par ligne de PostgreSQL, appelée RLS, change pour deux rôles. Les quatre commandes sont fictives. Les équipes disposent toutes deux de `SELECT`, `INSERT` et `UPDATE`, mais leur politique associe le rôle SQL actif à un territoire.

| Identifiant | Territoire |
| --- | --- |
| 101, 102 | FR |
| 201, 202 | DE |

La même requête `SELECT id FROM atelier.commandes ORDER BY id;`, sans filtre de territoire, doit produire `101, 102` sous `equipe_fr` et `201, 202` sous `equipe_de`.

## Lancer l'expérience

Prérequis : un terminal compatible `sh`, Docker installé et démarré, l'image `postgres:17-alpine` déjà présente localement. Aucun client PostgreSQL à installer sur l'ordinateur. La commande suivante vérifie la présence de l'image sans la télécharger :

```sh
docker image inspect postgres:17-alpine
```

Depuis le dossier de cet atelier, lancer :

```sh
sh ./run-local.sh
```

Le script crée un dossier temporaire pour les journaux et affiche son chemin. Pour choisir un dossier vide où les conserver :

```sh
sh ./run-local.sh ./journaux-rls
```

Le conteneur porte le nom fixe `nymphar-rls-20261031`. Si ce nom existe déjà, le script s'arrête sans le modifier. Il utilise `--pull=never` et `--network none`, ne publie aucun port et ne monte aucun dossier de l'ordinateur. Le mot de passe visible dans le script est fictif et réservé à cette démonstration. Les commandes `psql` passent par `docker exec` dans ce seul conteneur.

À la fin, le script supprime le conteneur qu'il a créé et ses volumes anonymes. Les journaux restent sur l'ordinateur. `code-sortie.txt` doit contenir `0`, et `verifications.log` doit contenir 29 lignes `NOTICE: RESULT`, chacune avec `"status" : "pass"`. Une assertion ratée arrête le script avec un code non nul. Les refus SQL attendus sont capturés et vérifiés, ils ne font pas échouer l'atelier.

Un arrêt forcé de Docker ou de la machine peut empêcher le nettoyage. Pour identifier un éventuel reste de **cet atelier**, examiner `conteneur-id.txt` et le nom `nymphar-rls-20261031` avant toute suppression.

## Lire les fichiers et les résultats

`schema.sql` crée le propriétaire, les deux rôles `NOLOGIN`, la table, les droits et la politique. `USING` choisit les lignes existantes accessibles. `WITH CHECK` contrôle l'état proposé lors d'un ajout ou d'une modification. Les deux expressions sont écrites explicitement pour rendre leurs fonctions visibles.

`verifications.sql` exerce les opérations autorisées et refusées, puis annule ses modifications de données et de politiques. Ses fonctions de vérification sont `SECURITY INVOKER` : elles conservent les droits du rôle testé. Elles sont des outils du banc d'essai, supprimés à la fin.

| Vérification | Résultat attendu |
| --- | --- |
| RLS désactivée, équipe FR ou DE | `[101, 102, 201, 202]` |
| RLS activée, sans politique applicable, équipe FR | `[]` |
| Lecture FR, sans filtre SQL | `[101, 102]` |
| Lecture DE, sans filtre SQL | `[201, 202]` |
| FR demande explicitement la commande 201 | `[]` |
| DE demande explicitement la commande 101 | `[]` |
| FR insère 103 en FR | `[103]` retourné, puis `[101, 102, 103]` visibles |
| FR insère 203 en DE | SQLSTATE `42501`, aucune ligne 203 créée |
| FR transforme 101 de FR en DE | SQLSTATE `42501`, 101 reste en FR |
| FR modifie la ligne cachée 201 | `[]` retourné, libellé allemand inchangé |
| FR modifie le libellé de 101 | `[101]` retourné, libellé modifié |
| `SELECT` révoqué, même politique | SQLSTATE `42501`, droit SQL absent |
| Ajout d'une politique permissive autorisant 201 pour FR | `[101, 102, 201]`, les règles s'ajoutent avec `OR` |
| Ajout d'une politique restrictive excluant 102 | `[101, 201]`, cette règle réduit avec `AND` |
| Propriétaire, sans `FORCE ROW LEVEL SECURITY` | `[101, 102, 201, 202]` |
| Même propriétaire, avec `FORCE` | `[]`, car aucune politique ne s'applique à ce rôle |
| Superutilisateur ou rôle `BYPASSRLS`, même avec `FORCE` | `[101, 102, 201, 202]` |
| Données finales après annulation des essais | `[101, 102, 201, 202]` |

Le contrôle compare les identifiants exacts, les états des lignes et les codes SQLSTATE. Compter seulement les lignes laisserait passer une erreur qui échange une commande française et une commande allemande.

## Ce que cette expérience permet de conclure

Le banc d'essai utilise la connexion d'administration locale pour installer le schéma puis choisir le rôle à examiner avec `SET ROLE`. Les opérations sur la table s'exécutent bien comme `equipe_fr` ou `equipe_de`, qui sont non propriétaires, sans `SUPERUSER` et sans `BYPASSRLS`. Il ne démontre pas l'authentification d'utilisateurs finaux : les rôles sont `NOLOGIN` et la connexion d'administration peut sélectionner les deux.

Dans une application réelle, la connexion et les appartenances de rôles doivent empêcher un utilisateur de choisir une autre équipe. Il faut aussi séparer le rôle applicatif du propriétaire et des rôles privilégiés. `FORCE` soumet le propriétaire aux politiques lorsqu'il consulte la table, mais ce propriétaire conserve le pouvoir de modifier la configuration RLS.

Le paramètre libre `app.territoire` est volontairement positionné à `DE` sous `equipe_fr`. Il est modifiable ; notre politique fondée sur `current_user` continue de renvoyer `[101, 102]`. Une politique qui ferait confiance à un tel paramètre aurait besoin d'une couche applicative digne de confiance pour établir le contexte. Un `SET LOCAL` limite sa durée à la transaction, il n'authentifie pas sa valeur et ne constitue pas à lui seul une défense contre une injection SQL.

Les politiques sur les vues, les fonctions `SECURITY DEFINER`, les pools de connexions, les clés étrangères, les sauvegardes et la performance ne sont pas évalués ici. Pour une vue, la documentation PostgreSQL 17 distingue notamment le comportement par défaut et `security_invoker = true`. Une politique RLS n'est pas une preuve globale de sécurité d'un SaaS.

## Version effectivement exécutée

Le 31 octobre 2026 : PostgreSQL **17.11**, `server_version_num = 170011`, architecture `aarch64`, Docker Engine **29.7.2**. L'image locale portait le tag `postgres:17-alpine` et l'identifiant :

```text
sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24
```

Le tag peut changer. Le script conserve l'identifiant de l'image réellement utilisée dans `image.json`. Pour réutiliser exactement cette image si elle est présente sur votre machine :

```sh
DEMO_IMAGE=sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24 sh ./run-local.sh
```

Les 29 résultats de cette exécution sont reproduits dans `resultats-reference.json`. Ils décrivent ce jeu de données et ces commandes, sans mesurer la sécurité ni la performance d'une application réelle.

Documentation primaire consultée le 31 octobre 2026 : [politiques RLS](https://www.postgresql.org/docs/17/ddl-rowsecurity.html), [CREATE POLICY](https://www.postgresql.org/docs/17/sql-createpolicy.html), [GRANT](https://www.postgresql.org/docs/17/sql-grant.html), [SET ROLE](https://www.postgresql.org/docs/17/sql-set-role.html), [options personnalisées](https://www.postgresql.org/docs/17/runtime-config-custom.html), [SET](https://www.postgresql.org/docs/17/sql-set.html), [CREATE VIEW](https://www.postgresql.org/docs/17/sql-createview.html).
