ENbext.dev
DocumentationFonctionnalités

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Politique d'authentification

La fonctionnalité AuthzPolicy permet à un plugin de demander « étant donné cette demande, l'action est-elle autorisée ? en utilisant un langage de stratégie externalisé : AWS Cedar, le CEL de Google, OPA/Rego ou un moteur personnalisé. Il exécute une couche au-dessus de Auth : l'authentification établit qui est l'appelant ; authz-policy décide de ce que ils peuvent faire avec cette identité.

Il s'agit de la forme E2 pour les sites qui nécessitent plus que des vérifications de chaîne de portée. Lorsque « l'administrateur peut tout faire, l'utilisateur peut faire ses propres choses » se transforme en « l'utilisateur du plan pro peut consulter les factures de son locataire à moins que le locataire n'ait des retards de paiement, et les auditeurs peuvent tout voir en lecture seule », la bonne réponse n'est pas plus un middleware - il s'agit d'un ensemble de règles externalisées sur lesquelles l'auteur de la politique peut itérer sans toucher au code du gestionnaire.

Le trait#

rust
pub trait AuthzPolicyPlugin: Send + Sync {
    fn name(&self) -> &str;

    fn evaluate(&self, ctx: &PolicyContext) -> PolicyDecision;
    fn reload_policies(&self) -> Result<(), String>;

    fn cleanup(&self) -> Result<(), String> { Ok(()) }
}

Trois choses méritent d’être soulignées concernant la forme.

evaluate renvoie directement PolicyDecision, et non Result<PolicyDecision, String>. Il s'agit de la seule fonctionnalité de bext-plugin-api où une énumération plate est porteuse. Les appelants se dirigent vers trois résultats normaux véritablement distincts – Autoriser, Refuser, Muter – et l'hôte ne doit pas confondre « la politique a dit non » avec « le moteur a explosé ». Le regroupement des erreurs de moteur dans Err(..) forcerait chaque site d'appel à entrer dans une arborescence de correspondance où la branche Err doit de toute façon devenir une Deny. L'insertion de fail-closed dans le contrat du plugin supprime ce passe-partout et rend l'autorisation en cas d'erreur impossible par construction.

PolicyDecision a une variante Mutate. Les moteurs de style Cedar peuvent associer des obligations côté réponse : injecter un en-tête Content-Security-Policy, tamponner un filigrane, rédiger des champs à partir d'un corps JSON. La variante Mutate contient une liste de paires d'en-têtes et un corps de remplacement facultatif, tous deux appliqués par l'hôte après l'exécution de par le gestionnaire. Les moteurs qui utilisent uniquement des portes (CEL, OPA) n'émettent jamais de Mutate - ils utilisent exclusivement Allow et Deny - et c'est très bien ; la variante est opt-in.

reload_policies existe afin que les stratégies puissent être rechargées à chaud sans redémarrer le serveur. Les moteurs sauvegardés sur disque relisent le fichier ; les moteurs distants établissent un nouvel ensemble de règles. Les implémentations effectuent le rechargement de manière atomique — soit le nouvel ensemble est actif, soit l'ancien reste, jamais un état partiel — et l'hôte déclenche l'appel à partir des événements de l'observateur de fichiers ou du tableau de bord de développement.

L'énumération de décision#

rust
pub enum PolicyDecision {
    Allow,
    Deny {
        reason: String,
    },
    Mutate {
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    },
}

Allow et Deny sont les cas courants. Le Deny.reason est contrôlé par l'auteur de la politique et l'hôte DOIT le transmettre à des journaux structurés et DEVRAIT le présenter au client - les backends de référence Cedar et CEL produisent des raisons qui sont sûres pour les corps 403, et les moteurs personnalisés documentent leurs propres garanties.

Mutate est le chemin de réécriture. L'hôte applique headers en les ajoutant à la réponse sortante (les doublons de même clé sont autorisés et ajoutés dans l'ordre) et, si body est Some, remplace en gros le corps du gestionnaire avant que la réponse n'atteigne le câble. Un Mutate sans en-tête ni corps est légal — les moteurs peuvent en émettre un lors de la composition des règles — et l'hôte le traite comme Allow.

L'assistant is_allowed renvoie true pour Autoriser et Muter, false pour Refuser, pour le middleware qui ne se soucie que de la décision de porte et gère la mutation ailleurs.

Fermeture en cas d'échec, avec observabilité des opérations#

Le contrat de trait interdit l'autorisation en cas d'échec. Les moteurs qui ne peuvent pas évaluer — fichier de stratégie mal formé, déroulement de panique interne, attribut manquant qu'une stratégie a déclaré requis — DOIVENT renvoyer PolicyDecision::Deny avec la chaîne d'erreur comme raison, et non Allow. Allow-on-failure est un anti-modèle d'autorisation bien connu et le plugin ABI refuse de le représenter.

Pour que l'observabilité des opérations reste distincte, l'erreur du moteur de préfixe du backend de référence refuse avec engine_error:  :

  • Refus face à l'utilisateur (un rejet de politique normal) : "denied by policy policy1 (action=post:read, resource=posts/123)"
  • Refus d'erreur moteur (repli en cas de fermeture en cas de panne) : "engine_error: failed to parse cedar policy: unexpected token at line 3"

Les opérateurs câblent une métrique de journal sur le préfixe engine_error: et une page sur des pics de niveau 500 sans avoir besoin d'un canal d'erreur distinct. Les moteurs personnalisés sont encouragés à suivre la même convention : elle est documentée dans la documentation au niveau du module de bext-plugin-api::authz_policy et dans les deux plugins de référence.

Les deux backends de référence#

Deux plugins de référence sont livrés avec le trait. Choisissez celui qui correspond à la complexité de votre politique.

@bext/policy-cedar#

Cèdre AWS. Cedar a été conçu exactement autour de la forme Autoriser/Refuser/obligations exposée par cette fonctionnalité, et c'est le premier choix naturel pour les sites qui ont besoin de modèles d'entités riches, d'une réécriture côté réponse ou d'une validation de schéma de politique. Le plugin lit un seul fichier de stratégie .cedar lors de la construction et à chaque appel reload_policies.

Cartographie des champs :

  • PolicyContext.principal → User::"<id>". None devient User::"anonymous" afin que les stratégies puissent toujours correspondre (règle typique : refuser sauf si le mandataire n'est pas anonyme).
  • PolicyContext.action → Action::"<action_string>". Le namespace:verb complet est le nom de l'action ; Cedar traite les deux points comme faisant partie du nom, donc les règles font référence littéralement à Action::"post:read".
  • PolicyContext.resource → Resource::"<resource_string>". Les barres obliques ne sont pas analysées dans la hiérarchie : les stratégies utilisent like pour la correspondance des préfixes.
  • PolicyContext.attributes → Enregistrement de chaînes Cedar Context, accessible sous context.tenant, context.plan, etc.

Le PolicySet analysé se trouve derrière un parking_lot::RwLock, donc evaluate lit en parallèle à partir de chaque thread de requête sans conflit ; reload_policies prend le verrou en écriture uniquement pour l'échange de pointeur final, et un rechargement qui ne parvient pas à analyser laisse l'ancien ensemble actif.

@bext/policy-cel#

Langage d'expression commun de Google. CEL est plus simple que Cedar - une politique est une expression booléenne unique sur un sac de variables plat, pas un moteur de règles - et elle convient aux vérifications de style "est le principal dans la liste autorisée ET le locataire correspond" sans la surcharge du schéma Cedar. Les webhooks d'admission Kubernetes, les filtres Envoy RBAC et une longue traîne d'autorisations cloud natives l'utilisent, de sorte que de nombreuses politiques sont portables entre ces systèmes et un site bext.

Variables disponibles pour l'expression :

  • action — chaîne, l'action namespace:verb.
  • resource — chaîne, l'identifiant de ressource opaque.
  • principal — chaîne, le sujet authentifié. None devient la chaîne vide, donc principal == "" est la vérification anonyme.
  • attributes — map<string, string>, accessible sous attributes.tenant, attributes.plan, etc.

Exemple:

code
principal != "" && (action == "post:read" || attributes.plan == "pro")

Le plugin compile l'expression avec impatience afin que des erreurs de syntaxe apparaissent au démarrage. Le Program compilé vit derrière un parking_lot::RwLock ; reload_policies se recompile à partir du disque et échange de manière atomique, préservant l'ancien programme en cas d'échec de compilation.

CEL est un langage de décision, pas un langage de transformation, donc ce plugin n'émet jamais de PolicyDecision::Mutate — uniquement Autoriser et Refuser. Les sites qui ont besoin de réécrire les réponses doivent choisir le backend Cedar.

Cèdre vs CEL : lequel dois-je choisir ?#

Les deux moteurs ne sont pas interchangeables. Choisissez selon la forme de vos politiques.

Cueillez le cèdre lorsque :

  • Votre modèle d'autorisation comporte des entités avec des relations (les utilisateurs appartiennent à des groupes, les ressources appartiennent à des locataires, les groupes accordent des rôles sur les ressources) et vous souhaitez que le moteur les suive.
  • Vous avez besoin d’obligations côté réponse : injection d’en-tête, rédaction de champs, filigrane.
  • Vous vous attendez à ce que l’ensemble de politiques s’étende au-delà de quelques dizaines de règles et souhaitez bénéficier d’une analyse statique accompagnée d’un langage de stratégie validé par un schéma.
  • Votre équipe écrit déjà Cedar ailleurs (AWS Verified Permissions, par exemple) et vous souhaitez des politiques portables.

Choisissez CEL lorsque :

  • Vos politiques sont "ce booléen est-il vrai pour la demande" : vérifications d'adhésion, comparaisons d'attributs, correspondance de locataires.
  • Vous souhaitez la portabilité avec l'admission Kubernetes, Envoy RBAC ou les conditions IAM de Google Cloud, qui utilisent tous le CEL.
  • Vous préférez une expression unique à un langage à plusieurs règles et vous n'avez pas besoin de Mutate.
  • Vous effectuez une itération rapide et ne souhaitez pas conserver un fichier de schéma Cedar parallèlement aux politiques.

Choisissez ni — écrivez un AuthzPolicyPlugin personnalisé — lorsque vous disposez d'un moteur de politique existant (Rego, une table de règles développée en interne, un magasin de règles basé sur une base de données) que les plugins de référence ne peuvent pas express. Le trait est délibérément étroit, de sorte que l'ABI ne constitue pas un goulot d'étranglement dans ce cas.

AuthzPolicy vs Auth – à qui appartient quoi#

Cela mérite d’être précisé car ce sera l’erreur la plus courante commise par les auteurs de plugins.

AuthPlugin transforme un jeton Bearer ou un cookie de session en AuthUser (identifiant, portées, attributs). Il s'exécute dans le middleware d'authentification, une fois par requête, et il ne sait rien de et de ce que l'utilisateur essaie de faire - il ne peut pas, car il s'exécute avant le routage. Les chaînes de portée sur l'utilisateur constituent une forme légère d'autorisation qui s'arrête à « ce jeton est autorisé à appeler cette classe de point de terminaison », et pour la plupart des sites, cela suffit.

AuthzPolicyPlugin exécute après que le routage ait identifié action et resource et évalue une stratégie par demande par rapport au principal déjà authentifié. C'est par point de décision, pas par demande : un seul gestionnaire peut évaluer plusieurs politiques s'il touche plusieurs ressources.

Un site peut fonctionner avec uniquement Auth (les chaînes de portée suffisent), uniquement AuthzPolicy (chaque demande est anonyme mais soumise à une politique - une API publique avec limitation de débit, par exemple), ou les deux empilés (Auth remplit principal, AuthzPolicy évalue action ).

Configuration#

Les deux plugins de référence prennent un chemin de source de politique dans leur bloc de configuration de plugin :

toml
[plugins.authz_policy]
type = "cedar"
policy_file = "./policies/site.cedar"

# or, for CEL:
# type = "cel"
# policy_file = "./policies/site.cel"

Les plugins exposent également un constructeur from_source pour les tests et les politiques intégrées au manifeste ; dans ce mode, reload_policies renvoie une erreur (il n'y a aucun fichier à relire) et les politiques sont immuables pendant toute la durée de vie du plugin.

Écrire votre propre plugin de politique#

Implémentez le trait sur un type Send + Sync, renvoyez PolicyDecision::Allow / Deny / Mutate par évaluation et suivez le contrat fermé en cas d'échec sur les erreurs internes. Faites correspondre la convention de préfixe de raison engine_error: si vous souhaitez que votre plugin tombe dans le même pipeline d'alerte opérationnelle que les plugins de référence.

La définition complète des traits avec les notes de conception au niveau du module se trouve dans bext-plugin-api/src/authz_policy.rs.

Attention

Les moteurs qui échouent en interne doivent renvoyer PolicyDecision::Deny, jamais Allow. Le plugin ABI rend l'autorisation en cas d'erreur impossible par construction, mais les moteurs personnalisés qui encapsulent un catch_unwind et renvoient Allow en cas de panique violent le contrat fermé en cas d'échec. Suivez la convention motif-préfixe engine_error: afin que les alertes opérationnelles se déclenchent en cas de panne de moteur, et pas seulement en cas de refus au niveau de l'utilisateur.

Conseil

CEL n'émet jamais de PolicyDecision::Mutate — si vous avez besoin d'une injection d'en-tête côté réponse ou d'une rédaction de champ, vous devez utiliser le backend Cedar. Voir la section Cedar vs CEL ci-dessus pour une comparaison complète.

En rapport#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.