Aller au contenu principal
Tous les articles
11 juin 20268 min de lectureÉquipe Reasoning Layer

Le contrôle d'accès qui s'explique, avec ReasoningLayer

contrôle d'accèssécuritéagentscas d'usage

C'est lundi. Alice rejoint l'équipe Engineering.

Personne n'ouvre la table des permissions pour y taper une ligne accordant à Alice l'accès à design.pdf. On l'ajoute à un groupe — Engineering — et on passe à autre chose. Le groupe a déjà le fichier. C'est tout le changement.

Le vendredi, un auditeur pose la question que tout système de contrôle d'accès finit par s'entendre poser : Alice peut-elle modifier design.pdf — et pourquoi exactement ?

Dans la plupart des systèmes, le « pourquoi » relève de l'enquête médico-légale. Vous parcourez ses appartenances à des groupes, déployez les rôles que portent ces groupes, résolvez les permissions qu'impliquent ces rôles, et vérifiez qu'aucun refus ne se cache trois couches plus bas. La réponse est réelle, mais elle est éparpillée entre des jointures et du code applicatif, et la reconstituer à la main est précisément le travail que personne n'a le temps de faire pendant un audit.

Cela devrait être une seule question, avec une seule réponse, et cette réponse devrait arriver accompagnée de sa propre preuve.

C'est ce que nous avons construit.

Voyez-le avant d'en lire le récit#

Demandez « qui peut faire ceci ? » et le moteur répond avec les personnes — et la dérivation qui justifie chacune d'elles.

Chaque autorisation que la démonstration vous montre est dérivée, et non stockée. Le nom d'Alice apparaît sous « peut lire design.pdf » alors qu'aucune ligne de la base de connaissances ne le dit, parce que le moteur l'a déduit de deux faits et de deux règles — et il vous remet la chaîne qu'il a suivie pour y parvenir.

Ne nous croyez pas sur parole pour la dérivation — pilotez-la vous-même. ▶ Ouvrez la démo IAM en direct et demandez-lui qui peut toucher un fichier : chaque réponse est une véritable requête par chaînage arrière — le moteur remonte de votre question vers les faits qui la tranchent — et chaque ligne porte la preuve qui l'a produite.

Des permissions que vous n'avez jamais écrites#

Voici tout ce que la base de connaissances stocke réellement pour notre scénario. Deux relations :

  • Appartenance : Alice est membre du groupe Engineering.
  • Une autorisation : le groupe Engineering peut modifier design.pdf.

C'est tout. Aucune ligne ne dit « Alice peut modifier design.pdf ». Aucune ligne ne dit « Alice peut lire design.pdf ». Ni l'une ni l'autre n'est stockée — toutes deux sont vraies, et le moteur les dérive à la demande à partir de deux règles qui sont elles-mêmes des données — écrites dans le même langage que les faits (elles sont homoïconiques) :

Règle 1 — modifier implique lire. Si un subject peut faire modify_file sur un objet, ce subject peut faire view_file dessus.

Règle 2 — héritage de groupe. Si un subject est membre d'un groupe, et que le groupe détient une permission sur un objet, le membre détient aussi cette permission.

Demandez « Alice peut-elle lire design.pdf ? » et le moteur enchaîne les deux règles. La Règle 2 transforme l'autorisation de modification du groupe en autorisation de modification d'Alice. La Règle 1 transforme l'autorisation de modification d'Alice en autorisation de lecture. Deux sauts, aucune permission stockée, une réponse vraie.

Règle 1 · modifier ⟹ lireRègle 2 · héritage de groupeDÉRIVÉview_file(alice, design.pdf)DÉRIVÉmodify_file(alice, design.pdf)FAIT STOCKÉmember(alice, engineering)FAIT STOCKÉmodify_file(engineering, design.pdf)fait stockédérivé (inféré par règle)
La réponse est sa propre preuve : deux faits stockés (en bas) alimentent deux règles pour dériver une autorisation que personne n’a écrite (en haut).

C'est l'ossature de toute l'approche : la politique est une donnée, et les autorisations sont dérivées. Vos règles d'accès cessent d'être des milliers de lignes de permissions pré-calculées qui se désynchronisent dès qu'un organigramme change, et deviennent un petit ensemble de règles valables sur tout ce qui est vrai à l'instant présent. Déplacez Alice dans un autre groupe et vous n'exécutez aucune migration pour recalculer ses permissions — il n'y a jamais eu de lignes à recalculer. La question suivante reçoit simplement une réponse différente.

Chaque réponse vient avec une preuve#

Une vérification de permission qui renvoie true vous dit quoi. Elle ne vous dit pas pourquoi, et lors d'un audit, d'un incident, ou d'un moment « attendez, comment le stagiaire a-t-il un accès en production ? », pourquoi est la seule chose qui compte.

Parce que les autorisations sont dérivées par des règles plutôt que lues dans une table, la dérivation est la réponse. Quand le moteur confirme qu'Alice peut lire design.pdf, il renvoie l'arbre qu'il a parcouru : appartenance au groupe → autorisation de modification du groupe → autorisation de modification d'Alice (Règle 2) → autorisation de lecture d'Alice (Règle 1). Deux applications de règle, et vous pouvez lire chacune d'elles.

Ne le prenez pas pour argent comptant — voici la forme exacte que le moteur renvoie pour « Alice peut-elle lire design.pdf ? » :

{
  "allowed": true,
  "certainty": 1.0,
  "query_time_ms": 1,
  "proof": {
    "goal_display": "iam_permission(action: \"view_file\", object: \"design.pdf\", subject: \"alice\")",
    "rule_label": "iam_permission(action: \"view_file\", object: ?O, subject: ?S)",
    "subproofs": [
      {
        "goal_display": "iam_permission(action: \"modify_file\", object: \"design.pdf\", subject: \"alice\")",
        "rule_label": "iam_permission(action: ?A, object: ?O, subject: ?U)",
        "subproofs": [
          { "goal_display": "iam_membership(group: \"engineering\", member: \"alice\")" },
          { "goal_display": "iam_permission(action: \"modify_file\", object: \"design.pdf\", subject: \"engineering\")" }
        ]
      }
    ]
  }
}

Lisez-le de haut en bas et c'est la dérivation, dans son intégralité. Le goal_display de chaque nœud est le rendu, par le moteur lui-même, de la conclusion qui y est prouvée — au sol, subject: "alice" compris. Chaque rule_label nomme la règle qui l'a produite, et les subproofs d'un nœud sont ce sur quoi cette règle s'est appuyée :

  • La racine prouve qu'Alice peut lire design.pdf, par la Règle 1 (view_file découle de modify_file).
  • Cela repose sur l'autorisation de modification d'Alice — elle-même dérivée, par la Règle 2 (héritage de groupe), à partir de deux choses que la base de connaissances stocke réellement : son iam_membership dans Engineering, et l'autorisation modify_file du groupe Engineering sur design.pdf. Ce sont tous deux des faits ; la récursion s'arrête là.

Deux applications de règle, deux faits stockés, chaque nœud rendu prêt à être lu. Rien dans le front end n'a assemblé quoi que ce soit — le moteur émet l'arbre entier en même temps que le verdict. (Simplifié pour la page : le moteur enveloppe ceci dans un tableau solutions[]allowed n'est que solutions.length > 0 — et chaque nœud porte aussi les identifiants de ses termes et la substitution de variables complète, élagués ici pour garder la chaîne lisible.)

Cela change ce qu'est une décision d'accès. Ce n'est plus un booléen opaque qu'il faut croire puis désosser quand quelqu'un le conteste. C'est une affirmation accompagnée de sa citation — l'arbre ci-dessus. « Oui, et voici la chaîne » — lue directement dans la dérivation propre du moteur par la requête même qui a répondu à la question, et non reconstituée à la main après coup. L'explicabilité n'est pas une fonctionnalité que l'on greffe ; elle découle du fait même de dériver la réponse en premier lieu.

Et le cas négatif est tout aussi net. Demandez « Bob peut-il modifier secrets.txt ? » alors que Bob ne détient qu'une autorisation de lecture, et il n'y a aucune dérivation à trouver — la Règle 1 ne fonctionne que dans un sens, lire n'implique jamais modifier. Aucune preuve, aucun accès. L'absence de chaîne est elle-même l'enregistrement d'audit.

Une seule requête, tous les types de subject#

Les rôles, les groupes, les utilisateurs et les comptes de service sont tous des entités qui peuvent détenir une permission. Dans ReasoningLayer, ils partagent un supertype — subject — avec user et user_group comme sous-sortes en dessous. Cette hiérarchie n'est pas décorative ; elle change ce qu'une seule requête renvoie.

Demandez au moteur tous les subjects et il répond avec les utilisateurs et les groupes d'un seul coup :

MATCH iam_subject(name: ?N);

Vous avez interrogé le supertype une fois ; vous avez récupéré tous les sous-types. Pour le contrôle d'accès, c'est la différence entre « lister tout ce qui pourrait toucher cette ressource » comme une seule question et un UNION sur chaque table où vous avez modélisé des subjects. Ajoutez un nouveau type de subject demain — une équipe, un bot, une application tierce — faites-en une sous-sorte de subject, et la même requête le capte sans aucune réécriture. Le treillis de types fait le travail qu'assurait autrefois l'énumération.

SUPERTYPE INTERROGÉiam_subjectiam_useriam_user_groupservice · bot · appVOTRE PROCHAINE SOUS-SORTEMATCH iam_subject(name: ?N);→ utilisateurs ∪ groupes ∪ …
Interrogez le supertype une fois et chaque sous-sorte revient — ajoutez demain un nouveau type de sujet et la même requête le capte.

Le tout, en code#

Le modèle ci-dessus constitue toute la mise en place. Vous affirmez les faits, ajoutez les deux règles, et interrogez. Voici la forme que cela prend avec le SDK TypeScript(vous ne mettez pas cela en place vous-même ? Sautez le bloc ; la seule ligne à retenir se trouve juste après.)

import { getClient } from './sdk-client';
 
const client = getClient(); // le tenant de votre espace de travail
 
// Les seules choses réellement stockées : une appartenance et une autorisation.
await client.inference.bulkAddFacts({
  facts: [
    { sortName: 'iam_membership', features: { member: 'alice', group: 'engineering' } },
    {
      sortName: 'iam_permission',
      features: { subject: 'engineering', object: 'design.pdf', action: 'modify_file' },
    },
  ],
});
 
// Règle 1 — une permission de modification implique une permission de lecture.
await client.inference.addRule({
  term: {
    sortName: 'iam_permission',
    features: { subject: { name: '?S' }, object: { name: '?O' }, action: 'view_file' },
  },
  antecedents: [
    {
      sortName: 'iam_permission',
      features: { subject: { name: '?S' }, object: { name: '?O' }, action: 'modify_file' },
    },
  ],
  certainty: 1.0,
});
 
// Règle 2 — un membre hérite des permissions du groupe.
await client.inference.addRule({
  term: {
    sortName: 'iam_permission',
    features: { subject: { name: '?U' }, object: { name: '?O' }, action: { name: '?A' } },
  },
  antecedents: [
    { sortName: 'iam_membership', features: { member: { name: '?U' }, group: { name: '?G' } } },
    {
      sortName: 'iam_permission',
      features: { subject: { name: '?G' }, object: { name: '?O' }, action: { name: '?A' } },
    },
  ],
  certainty: 1.0,
});
 
// « Alice peut-elle lire design.pdf ? » — et rapportez la preuve.
const { solutions } = await client.inference.backwardChain({
  goal: {
    sortName: 'iam_permission',
    features: { subject: 'alice', object: 'design.pdf', action: 'view_file' },
  },
  maxSolutions: 5,
  maxDepth: 50,
});
 
const allowed = solutions.length > 0; // → true, avec solutions[0].proof qui en détaille le pourquoi

L'enseignement, si vous avez sauté le bloc : les règles sont des valeurs que vous remettez au moteur, la réponse revient avec sa dérivation, et ajouter une nouvelle politique revient à ajouter une règle — non à modifier le code qui lit les permissions.

Vers quoi cela mène : refus, habilitation et passage à l'échelle#

Le scénario fichier-Engineering est volontairement réduit — il rend la mécanique visible. La même machinerie fait tourner le contrôle d'accès d'entreprise sans changement ; seules les données grossissent. Quelques éléments vers lesquels vous vous tournez dès que le problème devient réel :

Refus en monde clos, avec la Négation par l'échec. « Détient la permission » suffit rarement. Un accès réel n'est accordé que si le subject détient la permission et n'est pas sur liste noire, pas suspendu, pas en fin de contrat. La NAF permet d'exprimer exactement cela : une vérification qui ne réussit que lorsque ces faits de refus ne parviennent pas à être prouvés. Suspendez un compte et chaque autorisation dérivée qu'il détenait s'évapore à la requête suivante — vous révoquez en énonçant un seul fait, et non en traquant chaque permission que la suspension devrait annuler.

L'habilitation comme un treillis, et non une colonne. Modélisez les niveaux d'habilitation comme des sortes imbriquées — Clearance5 ⊂ Clearance4 ⊂ Clearance3 ⊂ Clearance2 ⊂ Clearance1 — et « un détenteur de Clearance-3 satisfait une exigence de Clearance-2 » cesse d'être un if level >= required éparpillé dans votre code. Cela devient un fait sur la hiérarchie de types que chaque requête respecte gratuitement. La même forme modélise le côté ressource : un service qui requiert Clearance-3 requiert implicitement tout ce qui se trouve en dessous.

Clearance5 ⊂ Clearance4 ⊂ … ⊂ Clearance1 · chaque niveau est une sous-sorte du niveau inférieurUN DÉTENTEURClearance5Clearance4Clearance3Clearance2Clearance1Clearance3 ⟹ satisfait aussi Clearance2 et Clearance1
L'habilitation est un treillis, pas une colonne : un détenteur de Clearance-3 satisfait gratuitement une exigence de Clearance-2 ou Clearance-1 — aucun `if level ≥ n` nulle part.

Séparation des tâches. Donnez aux responsables Finance une capacité Approver et aux responsables Engineering une capacité Executor, et le moteur peut faire remonter les subjects qui détiendraient les deux — le conflit que vous êtes contractuellement tenu d'empêcher — sous forme de requête, et non de revue trimestrielle sur tableur.

Les agents comme subjects à part entière. Le type de subject qui croît le plus vite n'est pas une personne — c'est un logiciel agissant pour le compte de quelqu'un : un compte de service, un bot, un agent IA déclenchant des appels d'outils en boucle. Parce que tout peut être une sous-sorte de subject, un agent est autorisé par exactement les mêmes règles qu'un humain, et — c'est ce qui compte quand un agent agit des milliers de fois par heure — chaque action qu'il entreprend revient avec la même preuve lisible. « Cet agent a supprimé cet enregistrement » cesse d'être une ligne de journal qu'il faut croire et devient une dérivation que vous pouvez vérifier : quelle autorisation, héritée de quel rôle, pour le compte de qui, sans aucun refus en travers du chemin. Une décision d'accès que vous pouvez prouver plutôt que simplement journaliser, c'est la différence entre un audit et une supposition.

Rien de tout cela ne change le modèle de l'exemple réduit. Ce sont les mêmes sortes, les mêmes règles homoïconiques, les mêmes preuves par chaînage arrière — portant des centaines d'utilisateurs et des dizaines de services au lieu d'un fichier et d'un groupe. La politique n'est pas devenue plus compliquée à exprimer. Les données ont simplement grossi.

Nous faisons tourner exactement cela dans une seconde démonstration, à l'échelle de l'entreprise : la démo Security Access charge 200 utilisateurs répartis sur 30 services dans la base de connaissances et répond à chaque vérification d'accès par une véritable requête NAF — refus, treillis d'habilitation et conflits de séparation des tâches inclus.

La même machinerie à l'échelle de l'entreprise — 200 utilisateurs, 30 services, avec des refus en monde clos, un treillis d'habilitation et des conflits de séparation des tâches remontés sous forme de requêtes.

Ce que ReasoningLayer fait différemment#

Le contrôle d'accès est un domaine encombré, et les bons outils qu'on y trouve sont bons à ce pour quoi ils ont été conçus. ReasoningLayer est construit autour d'une question différente — non pas seulement « est-ce autorisé ? » mais « qui peut faire ceci, et pourquoi exactement ? » — et cela change ce qui revient.

Tables de permissions et accès basé sur les rôles (RBAC / ACL). Le cheval de trait : des lignes associant qui-a-quoi, élargies par les rôles et les groupes. Rapides à lire, familières à tous — mais les lignes sont pré-calculées, donc elles se désynchronisent dès que l'organigramme bouge, et « pourquoi est-ce autorisé ? » est une jointure médico-légale sur plusieurs tables et une couche de code applicatif. ReasoningLayer stocke les deux faits et dérive le reste : rien à maintenir synchronisé, et le « pourquoi » revient avec la réponse.

Moteurs de politique-comme-code (OPA / Rego, AWS Cedar). Un véritable progrès — la politique devient une donnée : versionnée, testée, revue, au lieu d'instructions if éparpillées entre les services. La différence tient à la forme de la réponse. Ce sont des points de décision : remettez-leur une requête, obtenez allow ou deny (avec des journaux de décision, ou les politiques déterminantes de Cedar, disponibles après coup). La réponse de ReasoningLayer est la dérivation — la preuve accompagne le verdict — et la même règle fonctionne dans les deux sens : « Alice peut-elle lire ceci ? » et « qui peut lire ceci ? » sont une seule requête, le subject étant lié ou laissé ouvert. Énumérer chaque principal pouvant atteindre une ressource n'est pas ce pour quoi un point de décision est conçu.

Moteurs basés sur les relations (Google Zanzibar, OpenFGA, SpiceDB). Le parent le plus proche : des permissions dérivées de relations, avec l'appartenance à un groupe héritée exactement comme la Règle 2 l'hérite — et éprouvé à grande échelle. ReasoningLayer ajoute deux choses qu'ils ne portent pas. La réponse est l'arbre de preuve lui-même, et non un booléen assorti d'un appel séparé d'expansion ou de débogage. Et c'est un seul substrat : le même moteur tient les treillis de sortes et d'habilitation, les refus en NAF, la résiduation, et le versant contrainte-et-optimisation (le moteur derrière notre travail de planification) — de sorte que « un approbateur et un exécutant ne peuvent pas être la même personne, et le tour de garde doit tout de même couvrir chaque vacation » constitue un seul modèle, et non un graphe de relations branché sur un moteur de politique branché sur un solveur.

Une troisième réponse, pour quand « autoriser ou refuser » est la mauvaise question. Chaque système ci-dessus renvoie l'un de deux verdicts. Parfois, la réponse honnête est ni l'un ni l'autre, pas encore : la requête ne peut être tranchée parce qu'un fait manque — l'appareil est-il géré ? le contrat a-t-il été contresigné ? Forcé de choisir, un moteur binaire échoue en mode ouvert (dangereux) ou en mode fermé (bloque un travail légitime). ReasoningLayer peut résiduer — suspendre la décision, restituer exactement ce qu'il attend, et reprendre à l'instant où ce fait arrive. C'est la différence entre un « refusé » brut et un « refusé pour l'instant, en attente d'une vérification d'appareil géré » : la matière première pour une authentification renforcée plutôt qu'une impasse.

En somme : la réponse est une preuve, la même requête fonctionne en avant et en arrière, un seul moteur porte la politique et les contraintes, d'un fichier unique à l'échelle de l'entreprise, et il peut dire « pas encore » au lieu de deviner. Rien de tout cela n'est greffé — cela découle du fait même de dériver la réponse en premier lieu.

Ce que cela change#

Trois choses deviennent concrètement plus simples.

Les audits cessent d'être de l'archéologie. La question « comment le stagiaire a-t-il un accès en production ? » devient une seule requête avec une chaîne attachée — et non une journée à tracer des jointures et des groupes imbriqués. La preuve est la piste d'audit, générée à l'instant où vous posez la question plutôt que reconstituée après coup.

La révocation est un seul fait, et non un balayage. Suspendez un compte et chaque autorisation qu'il avait dérivée s'évapore à la requête suivante, parce que rien n'avait été stocké à traquer — fini le « avons-nous bien attrapé chaque permission que cette suspension était censée annuler ? ».

Les actions des agents deviennent défendables. Quand un compte de service ou un agent IA agit des milliers de fois par heure, « l'agent l'a fait » doit être plus qu'une ligne de journal. Chaque action porte la même preuve que celle d'un humain — quelle autorisation, héritée de quel rôle, pour le compte de qui — de sorte qu'une revue de conformité lit une dérivation au lieu de vous croire sur parole.

Essayez#

Vous n'avez pas à nous attendre. ▶ Ouvrez la démo IAM en direct — connectez-vous au playground, puis demandez qui peut toucher un fichier, regardez les réponses revenir avec leurs preuves, et ajoutez une appartenance pour voir une nouvelle autorisation apparaître sans que personne ne l'écrive. Vous le voulez à l'échelle de l'entreprise ? La ▶ démo Security Access fait tourner la même machinerie sur 200 utilisateurs et 30 services.

Quand vous serez prêt à le confronter à votre politique — vos rôles, vos groupes, vos règles de refus, vos niveaux d'habilitation — 👉 parlez-nous. Apportez la question d'accès la plus difficile à répondre aujourd'hui ; nous vous montrerons la preuve.

Les décisions d'accès ne devraient pas être une boîte noire qu'on vous demande de croire. Elles devraient être une chaîne que vous pouvez lire.

Une dernière chose, sans quoi nous aurions l'impression d'être des imposteurs : la passerelle qui garde ReasoningLayer est elle-même bâtie sur ReasoningLayer. Chaque appel vers la plateforme — « cet appelant a-t-il le droit d'atteindre ce point d'accès ? » — est tranché par le même moteur dérive-et-prouve dont vous venez de lire le récit. Nous mangeons notre propre nourriture pour chien. Il s'avère qu'elle est plutôt bonne.