Aller au contenu

Sécurité — posture, preuves, conformité

Vous êtes ici : Comprendre — comment la suite se défend, et comment elle le prouve. Comment GS-Apps se défend — et comment il le prouve. Lecture 10 min. Audience : auditeurs sécurité, devs, direction. Date : 2026-07-07 | Version : 1.0 — remplace le stub (matière : docs/security, PUBLIC_VS_PRIVATE, ADR-026/032/033/069)

Question Réponse Détail
Des mots de passe SQL ? Non — Managed Identity par application, moindre privilège Posture
Une compagnie peut-elle voir les données d’une autre ? Non — RLS fail-closed sur les tables tenant, prouvée par test comportemental en CI Preuve RLS
Qu’est-ce qui est public ? Une seule surface : la page de scan QR, sans aucune PII Frontière public/privé
Qui a le droit de faire quoi ? Rôles en BD (source unique), délégation bornée, god-view par octroi audité Modèle d’accès
Qui touche l’infra Azure ? Une poignée d’humains — MFA, PIM, Conditional Access, break-glass Accès privilégiés
Conformité ? Loi 25 · CNESST (démontré 2026-04-28) · ISO 27001 A.9.4 · OWASP Par référentiel

Toute la posture tient sur trois principes qui se renforcent : passwordless (rien à voler), fail-closed (refuser par défaut), moindre privilège (n’accorder que le nécessaire).

Les trois piliers de la posture de sécuritéLa posture de sécurité GS-Apps repose sur trois piliers d’égale importance. Premier pilier, passwordless : une identité managée par application, aucun mot de passe stocké — rien à voler. Deuxième pilier, fail-closed : sans contexte ni rôle valide, le système refuse et la base ne rend aucune ligne. Troisième pilier, moindre privilège : chaque identité n’a que les droits de son travail, versionnés comme du code et vérifiés en continu, jusqu’au compte d’audit lui-même sans privilège.Posture de sécurité — prouvée, pas déclaréePasswordlessune identité managéepar applicationrien à volerFail-closedsans contexte valide,zéro ligne · accès refusérefuser par défautMoindre privilègedroits versionnés,vérifiés en continujusqu’au compte d’audittrois principes d’égale importance — ils se renforcent, ils ne se remplacent pas
Les trois piliers portent la posture ensemble. Retirer l’un — un mot de passe qui traîne, une porte ouverte par défaut, un privilège de trop — et l’édifice penche.

Chaque backend se connecte à Azure SQL par sa Managed Identity propre (azure-active-directory-default) — aucun mot de passe SQL dans la configuration, aucune rotation manuelle, traçabilité par application dans les journaux SQL (ADR-033). Les permissions de ces identités sont versionnées comme du code (migrations SQL, moindre privilège par schéma — ADR-060) : l’autorisation de chaque app se lit dans le dépôt, pas dans une tête.

Le serveur MCP (outils IA) suit la même exigence : authentification JWT Azure AD OAuth 2.1 avec découverte .well-known, validation de signature, d’audience et d’issuer — pas de jeton statique partagé (ADR-026).

Le système refuse quand il ne sait pas : tenant inconnu → refus ; aucun rôle → accès vide ; aucun contexte tenant → zéro ligne retournée par la base. Le détail des « trois portes » d’une connexion est dans le Modèle d’accès.

Moindre privilège — partout, y compris pour l’audit

Section intitulée « Moindre privilège — partout, y compris pour l’audit »

Les droits de chaque identité sont versionnés comme du code : un manifeste canonique décrit qui touche quel schéma, un contrôle de dérive en CI compare l’état réel de la base au manifeste, et les exceptions sont documentées et justifiées. La cible est le moindre privilège par schéma — la bascule fine est en cours (Managed Identity §4). Et le principe s’applique jusqu’à l’audit lui-même : le compte que le CI utilise pour vérifier la sécurité est volontairement sans privilège (lecture seule) — un compte privilégié contournerait la RLS au moteur et rendrait la preuve aveugle.

2. Isolation des données — prouvée, pas déclarée

Section intitulée « 2. Isolation des données — prouvée, pas déclarée »

Le multi-tenant se défend en profondeur : au-delà du filtrage applicatif, les tables tenant portent une politique RLS (Row-Level Security) dont le prédicat borne chaque requête à la compagnie courante (ADR-032, périmètre détaillé dans Architecture de données §3).

Défense en profondeur — de la requête aux donnéesUne requête utilisateur traverse trois couches successives : la couche identité (Entra — authentification et assignation à l’application), la couche application (validation complète du JWT, allowlist des tenants, rôles résolus en base, fail-closed), puis la couche base de données où la RLS borne chaque requête à la compagnie par son prédicat — zéro ligne sans contexte. Cette dernière couche est prouvée en CI par un test comportemental. Au bout : uniquement les données de la compagnie de l’utilisateur.Une requête utilisateurCouche 1 — Identité (Entra)authentification · assignation à l’applicationCouche 2 — ApplicationJWT validé · allowlist tenants · rôles en BD · fail-closedCouche 3 — Base de données (RLS)prédicat company_id · zéro ligne sans contexte← prouvée en CI,à chaque db-initLes données de ta compagnierien d’autre — même si une requête se trompe
Défense en profondeur : même une requête applicative fautive reste bornée par la base. La couche RLS n’est pas déclarée — elle est rejouée en CI.

GS-Apps a deux zones, et une seule est publique (PUBLIC_VS_PRIVATE.md) :

La frontière public/privéÀ gauche, la zone publique : la page de scan QR du portail, sans authentification, qui n’expose que des données non personnelles comme la plaque et le type de véhicule. À droite, la zone privée : toutes les autres applications, derrière Azure AD obligatoire, avec rôles en base et RLS. Entre les deux, la frontière : toute PII vit côté privé.ZONE PUBLIQUEaucune authentificationPortail QR — page /p/:qrCodeSSR mobile · lecture seuleplaque · type de véhicule · n° interneaucune donnée personnelleZONE PRIVÉEAzure AD obligatoirehub · admin · ug · inv · mcp+ routes admin du portaildonnées métier complètes, selon rôlesrôles en BD · RLS · audittoute PII vit côté privé
La zone publique répond à l’obligation CNESST (droit de refus accessible depuis n’importe quel téléphone, sans compte). Tout le reste est derrière Azure AD.

La page publique de scan expose uniquement des informations déjà observables sur la voie publique (plaque, type de véhicule) ou non personnelles (numéro interne). Conducteur attribué, historique, coûts : côté privé, derrière authentification et rôles.

Référentiel Exigence Réponse GS-Apps Preuve
Loi 25 (Québec), art. 5 Accès aux renseignements personnels limité aux fonctions qui le justifient Frontière public/privé (zéro PII publique) · une identité par app · rôles par module en BD · RLS PUBLIC_VS_PRIVATE · Modèle d’accès
CNESST (droit de refus) Information SST accessible à tout signaleur, depuis n’importe quel téléphone Portail QR public mobile-first, sans compte Démonstration réussie 2026-04-28
ISO 27001 A.9.4 Gestion des accès privilégiés Fin du compte SQL administrateur partagé (migration MI 2026-04-27) · moindre privilège versionné · break-glass documenté ADR-033
OWASP Top 10 Conception sûre (A04), contrôle d’accès (A01) OAuth PKCE · JWT validés (signature, audience, issuer) · fail-closed · RLS en profondeur ADR-026 · Stratégie d’authentification

Les droits applicatifs (qui voit quoi dans les apps) et les droits d’infrastructure (qui touche Azure) sont deux plans séparés — avoir un rôle applicatif ne donne aucun droit infra, et inversement (ADR-069) :

  • Plan applicatif : la BD ug est la source unique des rôles ; le god-view vient d’un octroi explicite, nommé, révocable, audité — jamais d’une appartenance. Détail : Modèle d’accès.
  • Plan Azure-tenant : une poignée d’humains, durcis selon la règle de l’art 2026 — MFA partout, PIM (privilèges juste-à-temps plutôt que permanents), Conditional Access, moins de 5 Global Admins, compte break-glass hors-ligne documenté.

La posture actuelle vient aussi de leçons internes, traitées à la racine plutôt que rustinées :

  • Le compte partagé : un audit interne (avril 2026) a révélé que les applications partageaient un compte SQL à privilèges élevés — commodité de premier déploiement. La migration Managed Identity a remplacé ce partage par une identité par application à moindre privilège, et l’ancien compte a été retiré du chemin de production (conservé uniquement comme accès d’urgence hors-ligne, avec politique de rotation).
  • Le couplage des charges : un traitement lourd logé dans une application web a entraîné des applications voisines dans une panne — l’isolation du batch dans un worker dédié supprime cette classe de panne. Récit complet : Compute & exécution.