{"id":9161,"date":"2026-10-21T09:00:00","date_gmt":"2026-10-21T07:00:00","guid":{"rendered":"https:\/\/myrmidons.net\/?p=9161"},"modified":"2026-10-18T12:14:31","modified_gmt":"2026-10-18T10:14:31","slug":"sortir-lautorisation-du-code-applicatif","status":"publish","type":"post","link":"https:\/\/myrmidons.net\/fr\/sortir-lautorisation-du-code-applicatif\/","title":{"rendered":"Sortir l\u2019autorisation du code applicatif"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nos applications d\u00e9cident en permanence qui a le droit de faire quoi, et presque toutes le font avec une logique <em>ad hoc<\/em> dispers\u00e9e dans le code m\u00e9tier. Open Policy Agent propose l\u2019inverse&nbsp;: un moteur de d\u00e9cision externe, dont les r\u00e8gles sont du code versionn\u00e9 et testable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Probl\u00e9matique<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019autorisation se d\u00e9grade toujours de la m\u00eame fa\u00e7on. Elle commence par un <code>user.isAdmin()<\/code>, puis vient une table de r\u00f4les, puis des exceptions pour certains clients, puis une contrainte de conformit\u00e9 qui impose un contr\u00f4le par r\u00e9gion. Au bout de deux ans, la r\u00e8gle est r\u00e9partie entre le contr\u00f4leur, le service et la clause <code>WHERE<\/code>, et dupliqu\u00e9e dans chaque application qui touche la m\u00eame donn\u00e9e. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cette dispersion cause trois probl\u00e8mes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Personne n&rsquo;a de vue claire sur l&rsquo;existant.<\/strong> \u00ab Qui peut lire la table des transactions ? \u00bb exige de relire le code, de l&rsquo;interpr\u00e9ter, et d&rsquo;esp\u00e9rer n&rsquo;avoir rien manqu\u00e9. La r\u00e9ponse n&rsquo;existe nulle part sous une forme lisible. Et quand l\u2019audit r\u00e9clame la trace des d\u00e9cisions prises, la r\u00e9ponse est simple&nbsp;: il n\u2019y en a pas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Changer une r\u00e8gle est un d\u00e9ploiement.<\/strong> Relever un seuil d&rsquo;habilitation ou ajouter une r\u00e9gion autoris\u00e9e passe par une <em>pull request<\/em>, une revue, un pipeline et une fen\u00eatre de mise en production. Le d\u00e9lai n&rsquo;a aucun rapport avec la taille du changement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>La m\u00eame r\u00e8gle existe en plusieurs exemplaires.<\/strong> D\u00e8s qu&rsquo;une deuxi\u00e8me application touche la m\u00eame donn\u00e9e, la r\u00e8gle est r\u00e9impl\u00e9ment\u00e9e, dans un autre langage, par quelqu&rsquo;un d&rsquo;autre, \u00e0 partir d&rsquo;une compr\u00e9hension voisine mais pas identique. Les deux copies divergent, et personne ne sait laquelle est juste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le probl\u00e8me vient du couplage entre le code et les permissions. La politique d&rsquo;acc\u00e8s vit dans le code qui l&rsquo;applique, et h\u00e9rite donc de son cycle de vie, de son langage et de son d\u00e9coupage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">D\u00e9cider et appliquer sont deux m\u00e9tiers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/mgoeminne.com\/?page_id=1290\">Open Policy Agent<\/a> part d&rsquo;une s\u00e9paration emprunt\u00e9e au vocabulaire du contr\u00f4le d&rsquo;acc\u00e8s : le <strong>point d&rsquo;application<\/strong> (PEP) et le <strong>point de d\u00e9cision<\/strong> (PDP).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Votre application reste le point d&rsquo;application. Elle intercepte une demande, elle s&rsquo;arrange pour la satisfaire et en assume les cons\u00e9quences. Elle ne raisonne plus en termes de permissions. OPA est le point de d\u00e9cision : il re\u00e7oit une question relative aux permissions, y r\u00e9pond, et n&rsquo;ex\u00e9cute rien.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le contrat entre les deux est volontairement pauvre : un document JSON entre, un autre sort. OPA ne conna\u00eet ni votre m\u00e9tier, ni Kubernetes, ni Trino, ni le sch\u00e9ma de votre base de donn\u00e9es. Il \u00e9value des r\u00e8gles sur le contexte qu&rsquo;on lui passe, et rien d&rsquo;autre. Cette indigence assum\u00e9e est ce qui lui permet de servir l&rsquo;administration de pods aussi bien que le contr\u00f4le d&rsquo;acc\u00e8s d&rsquo;un moteur SQL: il n&rsquo;y a aucun mod\u00e8le de domaine pr\u00e9con\u00e7u.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les r\u00e8gles s&rsquo;\u00e9crivent en <a href=\"https:\/\/www.openpolicyagent.org\/docs\/policy-language\">Rego<\/a>, un langage d\u00e9claratif d\u00e9riv\u00e9 de <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Datalog\">Datalog<\/a>. On n&rsquo;y d\u00e9crit pas une proc\u00e9dure de v\u00e9rification mais un ensemble de faits et d&rsquo;implications, que le moteur \u00e9value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Un exemple : le self-service BI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prenons une API interne qui expose le catalogue de donn\u00e9es aux analystes. Trois r\u00e8gles s&rsquo;appliquent, et leur int\u00e9r\u00eat est qu&rsquo;elles viennent de trois endroits diff\u00e9rents de l&rsquo;organisation :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Un analyste n\u2019acc\u00e8de qu\u2019aux donn\u00e9es de sa r\u00e9gion.<\/li>\n\n\n\n<li>Son habilitation doit couvrir la classification de la table.<\/li>\n\n\n\n<li>Les colonnes identifiantes sont masqu\u00e9es, pas refus\u00e9es&nbsp;: on peut exprimer des requ\u00eates dessus, mais la valeur retourn\u00e9e est caviard\u00e9e.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019API n\u2019impl\u00e9mente aucune de ces r\u00e8gles. Lorsqu\u2019un analyste la contacte, elle demande \u00e0 OPA de valider sa requ\u00eate&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"action\": \"query\",\n  \"user\": {\n    \"id\": \"a.dupont\",\n    \"groups\": &#091;\"analysts\", \"emea\"],\n    \"clearance\": 2\n  },\n  \"resource\": {\n    \"schema\": \"finance\",\n    \"table\": \"transactions\",\n    \"columns\": &#091;\"txn_id\", \"amount\",\n                \"customer_iban\"],\n    \"classification\": \"confidential\",\n    \"region\": \"emea\"\n  }\n}\n\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deux choses m\u00e9ritent d&rsquo;\u00eatre not\u00e9es sur ce document.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il ne contient <strong>aucune donn\u00e9e m\u00e9tier<\/strong>. OPA ne verra jamais une transaction, un montant ou un IBAN r\u00e9el : seulement de quoi d\u00e9cider si une requ\u00eate doit \u00eatre accord\u00e9e. Cela facilite son int\u00e9gration dans un environnement r\u00e9gul\u00e9.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il porte <strong>les attributs, pas la conclusion<\/strong>. L&rsquo;application ne dit pas \u00ab cet utilisateur est analyste EMEA de niveau 2, donc\u2026 \u00bb ; elle d\u00e9pose les faits. Toute la logique d&rsquo;interpr\u00e9tation est d\u00e9finie dans OPA.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La politique<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On d\u00e9crit en Rego la politique qui valide une telle demande.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package bi.authz\n\ndefault allow := false\n\nallow if {\n  input.action == \"query\"\n  \"analysts\" in input.user.groups\n  input.resource.region in input.user.groups\n  input.user.clearance &gt;= min_level\n}\n\nmin_level := levels&#091;input.resource.classification]\n\nlevels := {\n  \"public\": 0, \"internal\": 1,\n  \"confidential\": 2, \"restricted\": 3\n}\n\npii := {\n\"customer_iban\", \n\"customer_email\"\n}\n\nmasked contains col if {\n  some col in input.resource.columns\n  col in pii\n  input.user.clearance &lt; 3\n}\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trois points de ce fichier m\u00e9ritent un arr\u00eat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Les conditions sont une conjonction implicite<\/strong>. Dans le corps de <code>allow<\/code>, les quatre lignes sont des <strong>et<\/strong> logiques. Il n&rsquo;y a pas de <code>&amp;&amp;<\/code>, pas d&rsquo;imbrication, pas d&rsquo;ordre d&rsquo;\u00e9valuation \u00e0 raisonner. On \u00e9num\u00e8re ce qui doit \u00eatre vrai. Une r\u00e8gle de dix conditions reste plate et lisible, l\u00e0 o\u00f9 l&rsquo;\u00e9quivalent imp\u00e9ratif devient un escalier de <code>if<\/code> imbriqu\u00e9s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>L&rsquo;ind\u00e9fini n&rsquo;est pas le faux<\/strong>. <code>default allow := false<\/code> pose le refus comme r\u00e9ponse par d\u00e9faut. C&rsquo;est plus fort qu&rsquo;une simple valeur initiale, parce qu&rsquo;en Rego une r\u00e8gle dont le corps \u00e9choue n&rsquo;est pas <em>fausse<\/em> : elle est <strong>ind\u00e9finie<\/strong>. La valeur par d\u00e9faut que nous ajoutons rend l&rsquo;\u00e9valuation de la permission toujours d\u00e9finie. La cons\u00e9quence est importante en pratique. Si la classification de la table est absente du catalogue, ou vaut une cha\u00eene inconnue, <code>levels[...]<\/code> ne trouve rien, <code>min_level<\/code> est ind\u00e9fini, la comparaison \u00e9choue, <code>allow<\/code> est ind\u00e9fini, et la r\u00e9ponse est <code>false<\/code>. <strong>Un d\u00e9faut de m\u00e9tadonn\u00e9e produit un refus et ne constitue pas une ouverture vers une \u00e9ventuelle acceptation.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Une d\u00e9cision n&rsquo;est pas forc\u00e9ment un bool\u00e9en<\/strong>. <code>masked contains col if { ... }<\/code> ne d\u00e9finit pas un bool\u00e9en mais un <strong>ensemble<\/strong> : la liste des colonnes \u00e0 masquer, construite par it\u00e9ration sur les colonnes demand\u00e9es. Une d\u00e9cision OPA peut \u00eatre un ensemble de colonnes, une clause SQL, un motif de refus lisible, ou un objet structur\u00e9. C&rsquo;est pr\u00e9cis\u00e9ment ce qui le rend utile dans de nombreux contextes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La r\u00e9ponse<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019application interroge OPA avec une requ\u00eate POST et re\u00e7oit une r\u00e9ponse en JSON.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\"result\": {\n  \"allow\": true,\n  \"masked\": &#091;\"customer_iban\"]\n}}\n\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il revient \u00e0 l\u2019application d\u2019ex\u00e9cuter la requ\u00eate et de masquer les r\u00e9sultats de mani\u00e8re appropri\u00e9e.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On peut aller plus loin. Par exemple, Trino peut lui d\u00e9l\u00e9guer son contr\u00f4le d\u2019acc\u00e8s, et sait obtenir de lui des filtres de lignes et des masques de colonnes. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>access-control.name=opa\nopa.policy.uri=https:\/\/opa.internal\/v1\/data\/trino\/allow\nopa.policy.row-filters-uri=https:\/\/opa.internal\/v1\/data\/trino\/rowFilters\nopa.policy.column-masking-uri=https:\/\/opa.internal\/v1\/data\/trino\/columnMask<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trino envoie l&rsquo;identit\u00e9 de l&rsquo;appelant et l&rsquo;action tent\u00e9e ; la politique r\u00e9pond un bool\u00e9en pour l&rsquo;autorisation, ou une <strong>expression SQL<\/strong> que le moteur injecte dans le plan de requ\u00eate. Un filtre de lignes retourn\u00e9 par OPA devient une clause <code>WHERE<\/code> que l&rsquo;utilisateur ne peut pas contourner, parce qu&rsquo;elle est ajout\u00e9e apr\u00e8s l&rsquo;analyse de sa requ\u00eate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C&rsquo;est l\u00e0 que la s\u00e9paration prend sa vraie valeur. La r\u00e8gle \u00ab un analyste ne voit que sa r\u00e9gion \u00bb peut d\u00e9sormais \u00eatre \u00e9crite <strong>une fois<\/strong> et appliqu\u00e9e \u00e0 la fois par l&rsquo;API de catalogue, par Trino quand l&rsquo;analyste passe en SQL direct, et par le prochain outil qui touchera la m\u00eame donn\u00e9e. Les trois parlent le m\u00eame protocole ; seule la forme de la r\u00e9ponse diff\u00e8re. La duplication qui faisait diverger les r\u00e8gles dispara\u00eet, par construction.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La m\u00eame m\u00e9canique existe chez Kubernetes (admission de ressources), Envoy (autorisation de requ\u00eates au niveau du maillage), Kafka, Terraform et une bonne partie de l&rsquo;\u00e9cosyst\u00e8me <a href=\"https:\/\/www.cncf.io\/\">CNCF<\/a>. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Le testing, la botte secr\u00e8te d&rsquo;OPA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sortir la politique du code ne servirait pas \u00e0 grand-chose si elle devenait un fichier de configuration opaque. L&rsquo;int\u00e9r\u00eat est qu&rsquo;elle redevient un artefact de g\u00e9nie logiciel \u00e0 part enti\u00e8re.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Les politiques sont testables<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>opa test<\/code> ex\u00e9cute des tests unitaires \u00e9crits en Rego. Une r\u00e8gle d&rsquo;autorisation devient v\u00e9rifiable en CI sans d\u00e9marrer l&rsquo;application, sans base de donn\u00e9es et sans jeu de donn\u00e9es. Ce point, \u00e0 lui seul, justifie qu&rsquo;on s&#8217;embarrasse d&rsquo;un langage de plus.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package bi.authz_test\n\nimport data.bi.authz\n\nanalyst := {\n\t\"action\": \"query\",\n\t\"user\": {\n\t\t\"id\": \"a.dupont\",\n\t\t\"groups\": &#091;\"analysts\", \"emea\"],\n\t\t\"clearance\": 2,\n\t},\n\t\"resource\": {\n\t\t\"schema\": \"finance\",\n\t\t\"table\": \"transactions\",\n\t\t\"columns\": &#091;\"txn_id\", \"amount\", \"customer_iban\"],\n\t\t\"classification\": \"confidential\",\n\t\t\"region\": \"emea\",\n\t},\n}\n\n# le cas nominal\ntest_analyste_accede_a_sa_region if {\n\tauthz.allow with input as analyst\n}\n\n# hors de sa r\u00e9gion : refus\ntest_refus_hors_region if {\n\tnot authz.allow with input as json.patch(analyst, &#091;\n\t\t{\"op\": \"replace\", \"path\": \"\/resource\/region\", \"value\": \"apac\"},\n\t])\n}\n\n# habilitation insuffisante pour une table restricted\ntest_refus_clearance_insuffisante if {\n\tnot authz.allow with input as json.patch(analyst, &#091;\n\t\t{\"op\": \"replace\", \"path\": \"\/resource\/classification\", \"value\": \"restricted\"},\n\t])\n}\n\n# m\u00e9tadonn\u00e9e manquante ou inconnue =&gt; refus\ntest_classification_inconnue_refuse if {\n\tnot authz.allow with input as json.patch(analyst, &#091;\n\t\t{\"op\": \"replace\", \"path\": \"\/resource\/classification\", \"value\": \"nouvelle_valeur\"},\n\t])\n}\n\n# le masquage, en dessous du niveau 3\ntest_iban_masque if {\n\tauthz.masked == {\"customer_iban\"} with input as analyst\n}\n\n# au niveau 3, plus de masquage\ntest_pas_de_masquage_niveau_3 if {\n\tcount(authz.masked) == 0 with input as json.patch(analyst, &#091;\n\t\t{\"op\": \"replace\", \"path\": \"\/user\/clearance\", \"value\": 3},\n\t])\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il ne reste plus qu&rsquo;\u00e0 ex\u00e9cuter ces tests dans la CI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>opa test . --verbose\nopa test . --coverage --format=json | jq '.coverage'<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Les politiques se d\u00e9ploient sans red\u00e9ployer l&rsquo;application<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Les politiques se distribuent en <strong>bundles<\/strong> : une archive, signable, qu&rsquo;OPA t\u00e9l\u00e9charge p\u00e9riodiquement depuis un serveur HTTP, un bucket S3 ou un registre <a href=\"https:\/\/opencontainers.org\/\">OCI<\/a>. Changer un seuil d&rsquo;habilitation devient une publication de bundle. Aucun red\u00e9marrage, aucune fen\u00eatre de mise en production, et une signature v\u00e9rifiable sur ce qui tourne r\u00e9ellement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Les d\u00e9cisions sont tra\u00e7ables par d\u00e9faut<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Les <strong>decision logs<\/strong> exportent chaque d\u00e9cision (l&rsquo;entr\u00e9e, la sortie, la version de la politique, l&rsquo;horodatage) vers un collecteur HTTP ou la sortie standard. C&rsquo;est tr\u00e8s pratique pour les audits et \u00e7a ne n\u00e9cessite aucune instrumentation du code applicatif. Dans un environnement r\u00e9gul\u00e9, ce point peut \u00eatre d\u00e9terminant.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tout n&rsquo;est pas rose pour autant, et plusieurs points m\u00e9ritent d&rsquo;\u00eatre consid\u00e9r\u00e9s pour \u00e9viter toute d\u00e9convenue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Rego ne ressemble \u00e0 rien de connu.<\/strong> L&rsquo;ind\u00e9fini n&rsquo;est pas le faux, l&rsquo;it\u00e9ration est implicite, une variable ne se r\u00e9affecte pas, et une r\u00e8gle dont aucun corps n&rsquo;est vrai dispara\u00eet silencieusement au lieu de lever une erreur. Un d\u00e9veloppeur exp\u00e9riment\u00e9 \u00e9crira du Rego qui marche en une journ\u00e9e et du Rego correct en une semaine. Entre les deux, il \u00e9crira surtout des r\u00e8gles qui ne se d\u00e9clenchent jamais sans qu&rsquo;il s&rsquo;en aper\u00e7oive. Heureusement, <code><a href=\"https:\/\/www.openpolicyagent.org\/ecosystem\/entry\/regal\">regal<\/a><\/code> sauve la mise et met en \u00e9vidence la plupart des probl\u00e8mes typiques. En compl\u00e9ment, pratiquez le <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Test_driven_development\">TDD<\/a> et \u00e9crivez vos tests de politique avant d&rsquo;\u00e9crire la politique.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>OPA ne d\u00e9cide que sur ce qu&rsquo;il a en m\u00e9moire.<\/strong> Ce qui est propre \u00e0 la requ\u00eate va dans l&rsquo;<code>input<\/code>. Ce qui est partag\u00e9 et petit va dans <code>data<\/code>, charg\u00e9 avec le bundle. Synchroniser un r\u00e9f\u00e9rentiel de plusieurs centaines de milliers d&rsquo;entr\u00e9es ne tient pas : la m\u00e9moire et la fra\u00eecheur deviennent probl\u00e9matique. Dans ce cas, c&rsquo;est \u00e0 l&rsquo;application d&rsquo;enrichir l&rsquo;<code>input<\/code> avec ce que la d\u00e9cision exige, ce qui suppose de savoir \u00e0 l&rsquo;avance ce dont la politique a besoin. Cela r\u00e9introduit un couplage faible mais r\u00e9el.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ce n&rsquo;est pas un remplacement universel.<\/strong> Pour du masquage de colonnes sur un seul entrep\u00f4t, le catalogue natif ou Ranger reste souvent plus simple, mieux int\u00e9gr\u00e9 et plus facile \u00e0 justifier devant une \u00e9quipe d&rsquo;exploitation. OPA gagne quand la m\u00eame r\u00e8gle doit valoir dans plusieurs technologies, ou quand la politique change plus vite que le code qui l&rsquo;applique.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">OPA ne simplifie pas vos r\u00e8gles d&rsquo;autorisation. Celles-ci restent aussi compliqu\u00e9es que votre organisation. Par contre, il modifie l&rsquo;endroit o\u00f9 elles vivent, et ce d\u00e9placement peut \u00eatre payant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une r\u00e8gle sortie du code devient un objet qu&rsquo;on peut lire sans ouvrir l&rsquo;application, tester sans la d\u00e9marrer, d\u00e9ployer sans la red\u00e9ployer, auditer sans l&rsquo;instrumenter, et r\u00e9utiliser dans le moteur d&rsquo;\u00e0 c\u00f4t\u00e9. Aucune de ces cinq propri\u00e9t\u00e9s n&rsquo;est spectaculaire isol\u00e9ment. Ensemble, elles changent votre projet: la politique cesse d&rsquo;\u00eatre une dette qui s&rsquo;accumule dans les recoins du code pour devenir un livrable avec un propri\u00e9taire, des tests et un historique.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le co\u00fbt d&rsquo;entr\u00e9e est r\u00e9el. Il est presque enti\u00e8rement dans Rego. Comptez une semaine par personne pour \u00eatre \u00e0 l&rsquo;aise, et acceptez que les premi\u00e8res politiques doivent \u00eatre r\u00e9\u00e9crites.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si vous voulez l&rsquo;\u00e9valuer, la mani\u00e8re la moins co\u00fbteuse de proc\u00e9der tient en trois \u00e9tapes.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Reproduisez l&rsquo;exemple en local.<\/strong> Les deux fichiers de cet article, plus un <code>input.json<\/code>, suffisent : <code>opa eval -d policy.rego -i input.json data.bi.authz<\/code>. Ou passez par le <a href=\"https:\/\/play.openpolicyagent.org\/\">Rego Playground<\/a>, sans rien installer.<\/li>\n\n\n\n<li><strong>Prenez une r\u00e8gle existante.<\/strong> Celle qui a chang\u00e9 trois fois en six mois, ou celle que deux applications impl\u00e9mentent diff\u00e9remment. R\u00e9\u00e9crivez-la en Rego avec ses tests, et comparez le r\u00e9sultat au code qu&rsquo;elle remplacerait.<\/li>\n\n\n\n<li><strong>Ne branchez rien en production au premier essai.<\/strong> Faites tourner OPA en <strong>mode observation<\/strong> : l&rsquo;application journalise l&rsquo;\u00e9cart entre la d\u00e9cision d&rsquo;OPA et la sienne, et continue d&rsquo;appliquer la sienne. Au bout de quelques semaines, les journaux logs disent si la politique est juste.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Si la r\u00e8gle choisie ne vit que dans une application et ne change jamais, l&rsquo;exercice conclura qu&rsquo;OPA n&rsquo;apporte rien. C&rsquo;est un r\u00e9sultat utile, et il co\u00fbte une apr\u00e8s-midi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Article initialement publi\u00e9 sur <a href=\"https:\/\/mgoeminne.com\/2026\/10\/20\/sortir-lautorisation-du-code-applicatif\/\" data-type=\"link\" data-id=\"https:\/\/mgoeminne.com\/2026\/10\/20\/sortir-lautorisation-du-code-applicatif\/\">Pointe s\u00e8che<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Nos applications d\u00e9cident en permanence qui a le droit de faire quoi, et presque toutes le font avec une logique ad hoc dispers\u00e9e dans le code m\u00e9tier. Open Policy Agent propose l\u2019inverse&nbsp;: un moteur de d\u00e9cision externe, dont les r\u00e8gles sont du code versionn\u00e9 et testable. Probl\u00e9matique L\u2019autorisation se d\u00e9grade toujours de la m\u00eame [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":9162,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[108,141,143],"tags":[109,146,145],"class_list":["post-9161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-connaissances-techniques","category-developpement","category-publique","tag-connaissances","tag-gouvernance","tag-securite"],"_links":{"self":[{"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/posts\/9161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/comments?post=9161"}],"version-history":[{"count":4,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/posts\/9161\/revisions"}],"predecessor-version":[{"id":9169,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/posts\/9161\/revisions\/9169"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/media\/9162"}],"wp:attachment":[{"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/media?parent=9161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/categories?post=9161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/myrmidons.net\/fr\/wp-json\/wp\/v2\/tags?post=9161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}