Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

En finir avec les permissions complexes : une bouffée d'air frais pour votre code

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

By Dima KramskoyAug 5, 202612 min read
Dima Kramskoy

About Dima Kramskoy

Solutions architect who takes ideas from zero to proven. With over 20 years of software engineering, I build PoCs and MVPs on AWS that prove the path before you commit — spanning GenAI, DevOps, and FinOps. Off the clock: live-fire cooking, fishing, the outdoors, and applying Extreme Ownership to everything.

My personal page

PerfectScale™ for Kubernetes

Ready to optimize?

Get your free Kubernetes savings analysis

Vue d'ensemble

Cet article présente Amazon Verified Permissions (AVP) comme une solution concrète pour gérer les autorisations complexes d'une application, avec une mise en pratique détaillée basée sur l'exemple PetStore d'AWS.

Le problème

Vous êtes-vous déjà retrouvé face à une complexité considérable en essayant de gérer tous les rôles, utilisateurs et acteurs possibles des autorisations de votre propre produit SaaS ? Vous connaissez la galère des exigences métier qui changent, de la quête des bons principals de sécurité tout en jonglant entre revues de code et tests A/B liés aux changements que vous essayez d'implémenter ? Eh oui, votre SaaS tourne sur le cloud AWS.

Il est sans doute temps de respirer un grand coup et de prendre du recul !! Réfléchissez à votre solution, puis repensez-la encore, car je pourrais bien vous en montrer une relativement simple ! Une solution qui vous aide à maîtriser la complexité et à consacrer vos ressources et vos efforts à ce qui compte vraiment : votre activité et ses besoins !!

Cette solution, c'est Amazon Verified Permissions.


Comment vous gérez habituellement les autorisations dans votre application web

Décortiquons le système d'autorisation d'une application web. Le besoin d'un tel système apparaît avec l'évolution de l'application. Vous pouvez le planifier en amont (ce qui est préférable, dès la conception de votre système), mais vous devrez tout de même faire face à des changements dictés par l'évolution des exigences métier. Vous devrez modifier différentes parties de votre système après sa mise en production.

Pourquoi un tel système est-il nécessaire ? Pour de nombreuses raisons :

  • Vous voulez autoriser certains rôles à voir certaines parties de votre application
  • Vous devez restreindre certaines actions pour des rôles/utilisateurs spécifiques de votre application
  • Vous devez renforcer les contrôles de sécurité et empêcher les accès non autorisés
  • Votre logique de permissions finit éparpillée dans tout votre code, transformant la maintenance en cauchemar

Votre système de permissions typique : un vrai sac de nœuds

La plupart des systèmes d'autorisation ressemblent aujourd'hui à ceci : des blocs if/else disséminés dans tout votre code :

// The typical nightmare in your Lambda or Node.js service:
boolean userAuthorizedForOrder(Order order, User user) {
if (user.storeId == order.storeId) {
if (user.roles.contains("store-owner")) {
return true; // store owners can access their own store's orders
} else if (user.roles.contains("customer")) {
if (user.id == order.customerId) {
return true; // customers can only see their own orders
}
}
}
return false;
}

Et ensuite ? Vous ajoutez un rôle. Puis un autre. Puis un cas particulier pour les managers régionaux. Puis une règle sur qui peut annuler des commandes après 24 heures. Puis une nouvelle exigence métier concernant l'accès le week-end. Soudain, vous vous noyez dans des conditions imbriquées, vos revues de code deviennent un enfer, chaque changement d'exigence métier implique un déploiement, et la moitié de votre équipe débat de cas limites au lieu de développer des fonctionnalités.

Là où ça fait vraiment mal

Explosion des rôles : vos 2 rôles deviennent 5, puis 10, puis vous vous retrouvez avec un manager-régional-avec-permissions-d'employé-de-magasin-mais-uniquement-le-week-end, et plus personne ne sait qui doit avoir accès à quoi.

Des revues de code cauchemardesques : chaque changement de permission exige une modification du code. Les développeurs passent des heures à débattre de la logique des politiques dans les PR au lieu de livrer des fonctionnalités.

Tester les autorisations : comment tester toutes les combinaisons ? Matrice utilisateurs × rôles × ressources × actions = chaos. Vous ratez des cas limites, et ça casse en production.

Piste d'audit : bon courage pour expliquer à la conformité pourquoi tel utilisateur a pu accéder à telle commande. C'est enfoui dans l'historique git, réparti sur 5 fichiers différents.

Zéro flexibilité : envie de changer des permissions un vendredi ? Dommage, c'est un déploiement. Besoin de revenir rapidement en arrière sur une politique ? Redéployez.


Comment gérer tout cela avec Amazon Verified Permissions

Et si vous aviez l'opportunité de vous alléger de ce fardeau ? Et si je vous disais qu'il existe désormais un service capable de faire l'essentiel du travail de gestion des permissions de votre application web à votre place ?

Au lieu d'incruster les autorisations dans votre code, et si vous les sépariez complètement ? Et si les permissions étaient déclaratives, versionnées, centralisées, et que vous pouviez les modifier sans redéployer votre application ?

C'est la promesse d'Amazon Verified Permissions (AVP).

L'architecture

Voici comment cela fonctionne (dans les grandes lignes) :

  1. L'utilisateur s'authentifie via AWS Cognito (ou tout autre IdP)
  2. Votre API reçoit la requête avec un token JWT
  3. Le Lambda Authorizer intercepte la requête
  4. Le Lambda construit une requête d'autorisation avec : principal (qui), action (ce qu'il fait), ressource (sur quoi il agit), contexte (attributs supplémentaires)
  5. Le Lambda appelle AVP avec cette requête
  6. AVP évalue les politiques Cedar (un langage de politiques conçu pour l'autorisation)
  7. AVP renvoie ALLOW ou DENY
  8. API Gateway applique la décision : la requête passe, ou reçoit un 403

Le point clé : toute la logique de permissions vit dans les politiques Cedar au sein d'AVP. Votre code se contente de demander "est-ce autorisé ?" et fait confiance à la réponse.

media

L'exemple concret : PetStore

J'ai utilisé le dépôt PetStore d'AWS comme application d'exemple, avec la plupart des fonctionnalités décrites, mais avec quelques légères modifications, car le schéma fourni ne fonctionnait pas — probablement à cause de mises à jour du service effectuées par AWS. Gardez à l'esprit qu'il s'agit d'un service relativement récent et je suppose qu'il est encore, sur bien des aspects, en cours de maturation.

Il vous suffit de suivre les étapes fournies et de commencer par déployer l'application Amplify. Il se peut qu'on vous demande de vous identifier avec votre compte GitHub afin de récupérer le dépôt et de le déployer dans votre écosystème AWS. Une fois le déploiement effectué, en regardant le diagramme d'architecture ci-dessus, on peut identifier les composants et la manière dont ils communiquent entre eux.


Mise en place : le guide pas à pas

Étape 1 : utilisateurs et groupes Cognito

Pour le fournisseur d'identité, nous utilisons AWS Cognito. Après le déploiement, rendez-vous dans votre dashboard Amazon Cognito : vous devriez y trouver le User pool dont le nom commence par "petstoresample". C'est votre pool, et c'est là que vous devez créer les utilisateurs et les groupes pour cette application.

Créez trois utilisateurs :

Nous devons créer trois utilisateurs pour ce PoC. L'un doit impérativement s'appeler "abhi", car ce nom est codé en dur dans la fonction Lambda. Les deux autres sont plus flexibles — je les ai appelés "owner" et "visitor". Lors de leur création :

  • N'envoyez pas d'e-mails de vérification
  • Marquez-les comme "adresse e-mail vérifiée"
  • Utilisez des mots de passe simples (vous ne voudrez pas les réinitialiser après la configuration)

Une fois terminé, vous devriez obtenir quelque chose comme ceci :

media

Ajoutez des attributs personnalisés à l'utilisateur owner :

N'oubliez pas de mettre à jour l'utilisateur "owner" avec l'attribut personnalisé employmentStoreCode. Il sera utilisé plus tard dans les politiques d'Amazon Verified Permissions. Cliquez simplement sur "Edit" et mettez à jour pour que les attributs de votre utilisateur ressemblent à ceci :

media

Créez les groupes et assignez les utilisateurs :

Vous devez créer 2 groupes et y ajouter les utilisateurs. Ces noms de groupes ("Store-Owner-Role" et "Customer") font partie des tokens émis par Cognito et seront utilisés pour le contrôle d'accès basé sur les rôles (RBAC).

media

L'utilisateur "abhi" doit être ajouté au groupe "Customer", avec l'autre utilisateur "visitor" :

media

Et évidemment, le "owner", avec le tag supplémentaire que nous avons ajouté, ira dans le groupe "Store-Owner-Role" :

media

Étape 2 : configuration d'Amazon Verified Permissions

Cette partie est terminée ; passons maintenant à la revue et à la configuration d'Amazon Verified Permissions. Dans cette partie, nous allons également passer en revue le Lambda Authorizer et l'API Gateway — ils ont été déployés via le même déploiement Amplify, en tant que composants de l'application.

Rendez-vous simplement dans Amazon Verified Permissions en le saisissant dans le champ de recherche, et commençons par créer un nouveau Policy store. Dans le dépôt, il est recommandé de commencer par le schéma, mais l'interface a changé : il faut d'abord créer le Policy store. Vous pouvez improviser si vous le souhaitez, car nous utiliserons au final le schéma que je fournis. Pour vous simplifier la vie, spécifiez simplement un Policy Store vide et donnez-lui le nom "PetStore".

Étape 3 : définissez votre schéma

C'est une étape cruciale. Le schéma indique à AVP quels principals, ressources, actions et relations existent dans votre application. Utilisez ce schéma :

{
"MyApplication": {
"actions": {
"GetOrder": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Order"]
}
},
"GetStoreInventory": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"ListOrders": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"PlaceOrder": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"SearchPets": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
}
},
"entityTypes": {
"Application": {
"memberOfTypes": [],
"shape": {
"attributes": {
"storeId": {"type": "String"}
},
"type": "Record"
}
},
"Order": {
"memberOfTypes": [],
"shape": {
"attributes": {
"owner": {
"name": "User",
"type": "Entity"
},
"storeId": {
"type": "String"
}
},
"type": "Record"
}
},
"Pet": {
"memberOfTypes": [],
"shape": {
"attributes": {
"owner": {
"name": "User",
"type": "Entity"
},
"storeId": {
"type": "String"
}
},
"type": "Record"
}
},
"Group": {
"memberOfTypes": [],
"shape": {
"attributes": {},
"type": "Record"
}
},
"User": {
"memberOfTypes": ["Group"],
"shape": {
"attributes": {
"employmentStoreCode": {"type": "String"}
},
"type": "Record"
}
}
}
}
}

Le schéma du projet d'origine comporte un petit problème : copiez donc plutôt celui-ci dans le schéma de votre Policy Store.

Étape 4 : créez les politiques Cedar

Cliquez sur le Policy Store ; dans le panneau de gauche, vous verrez le lien "Policies". Cliquez dessus, puis sur "Create Policy > Create static policy". L'assistant de création de politique s'appuie sur le schéma que nous avons ajouté. Nous utilisons des politiques permissives : le périmètre doit être défini sur le groupe de participants, le périmètre de ressource doit être la ressource spécifique de votre choix, et le type d'action doit être sélectionné comme un ensemble spécifique d'actions.

Petite astuce : créez la politique entièrement permissive en sélectionnant "All principals", "All resources" et "All actions", enregistrez-la, puis modifiez-la et collez simplement votre politique en langage Cedar. C'est un contournement, mais il faut connaître la déclaration de votre politique.

Voici les politiques dont vous aurez besoin pour ce PoC :

Exemple RBAC — les clients peuvent rechercher des animaux et passer des commandes :

permit(
principal in MyApplication::Group::"Customer",
action in [MyApplication::Action::"SearchPets", MyApplication::Action::"PlaceOrder"],
resource == MyApplication::Application::"main"
);

Exemple ABAC — les propriétaires de magasin ne peuvent accéder qu'à l'inventaire de leur propre magasin :

permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"GetStoreInventory",
resource == MyApplication::Application::"main"
)
when {
principal.employmentStoreCode == resource.storeId
};

Exemple ABAC — les utilisateurs ne peuvent consulter que leurs propres commandes :

permit(
principal in MyApplication::Group::"Customer",
action == MyApplication::Action::"GetOrder",
resource is MyApplication::Order
)
when {
principal == resource.owner
};

Cloud bill shouldn't be a mystery

One platform for AI and Cloud optimization.

L'intégration du Lambda Authorizer

Votre fonction Lambda reçoit la requête API et doit :

  1. Vérifier le token JWT (extraire les informations utilisateur)
  2. Construire une requête d'autorisation pour AVP
  3. Appeler l'API isAuthorized d'AVP
  4. Renvoyer ALLOW/DENY

Voici à quoi cela ressemble (extrait du véritable dépôt PetStore) :

const AWS = require('aws-sdk');
const avp = new AWS.Service({
apiConfig: require('./verifiedpermissions.json')
});
const { CognitoJwtVerifier } = require("aws-jwt-verify");
const jwtVerifier = CognitoJwtVerifier.create({
userPoolId: process.env.USERPOOLID,
tokenUse: "id",
clientId: process.env.APPCLIENTID
});
const policyStoreId = process.env.POLICYSTOREID;
exports.handler = async (event) => {
// Extract and verify the JWT token
const token = event.headers["Authorization"];
let payload;
try {
payload = await jwtVerifier.verify(token);
var groups = payload["cognito:groups"] || [];
// Build the principal entity (the user)
var userEntity = {
"identifier": {
"entityType": "MyApplication::User",
"entityId": payload["cognito:username"]
},
"attributes": {
"employmentStoreCode": {
"string": payload["custom:employmentStoreCode"] == null ? "" : payload["custom:employmentStoreCode"]
}
},
"parents": []
};
// Add groups as parent entities (for group membership)
groups.forEach((group) => {
userEntity.parents.push({
"entityType": "MyApplication::Group",
"entityId": group
});
});
// Build the authorization query
let entities = {
entityList: [userEntity]
};
// Add resource entities based on the action
addResourceEntities(entities, actionMap[event.httpMethod + event.resource], event.pathParameters);
// Call AVP
let authQuery = {
policyStoreId,
principal: { "entityType": "MyApplication::User", "entityId": payload["cognito:username"] },
action: { "actionType": "MyApplication::Action", "actionId": actionMap[event.httpMethod + event.resource] },
resource: buildResource(actionMap[event.httpMethod + event.resource], event.pathParameters),
entities
};
const authResult = await avp.isAuthorized(authQuery).promise();
if (authResult.decision == 'ALLOW') {
return buildResponse(200, 'Authorized', authResult);
} else {
return buildResponse(403, "You are not authorized to perform this action.", authResult);
}
} catch(err) {
console.log(err);
return buildResponse(403, "Error: " + err);
}
};

À retenir : votre code ne contient aucune logique de permissions. Il se contente de transmettre le contexte à AVP et de faire confiance à la réponse. C'est tout l'intérêt.


Le vrai bénéfice : des changements sans redéploiement

C'est ici que la magie opère.

Scénario : le métier vous annonce : "À partir du mois prochain, les utilisateurs Store-Owner-Role pourront aussi annuler des commandes, mais uniquement celles passées il y a plus de 24 heures."

Avec des permissions traditionnelles codées en dur :

  • Vous modifiez le code du Lambda
  • Vous ajoutez une fonction getOrderAge()
  • Vous écrivez des tests pour la nouvelle logique
  • Vous commitez, créez une PR, attendez la revue de code
  • Quelqu'un remet en question votre logique, vous itérez
  • Vous mergez, déployez en staging, testez
  • Vous déployez en production
  • S'il y a un bug, vous redéployez pendant que l'entreprise perd de l'argent

Avec Amazon Verified Permissions :

  • Vous vous connectez à la console AVP
  • Vous ajoutez une nouvelle politique Cedar (2 minutes chrono) :
permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"CancelOrder",
resource is MyApplication::Order
)
when {
principal.employmentStoreCode == resource.storeId &&
resource.age > 1440 // minutes
};
  • Vous enregistrez
  • C'est en ligne. Immédiatement. Sans déploiement. Sans changement de code. Sans redéploiement du Lambda.

C'est tout. Votre code n'a pas changé. Votre Lambda n'a pas été redéployé. La règle d'autorisation a été mise à jour en temps réel.


RBAC + ABAC : le meilleur des deux mondes

Cedar vous permet de combiner les deux approches :

RBAC seul : "L'utilisateur fait-il partie du groupe Admin ?" (binaire, ne tient pas compte des attributs de la ressource)

ABAC seul : "L'attribut department de l'utilisateur correspond-il à l'attribut department de la ressource ?" (granulaire, mais exige beaucoup d'attributs)

RBAC + ABAC (la force de Cedar) : "L'utilisateur fait-il partie du groupe Store-Owner-Role ET son employmentStoreCode correspond-il au storeId de la ressource ?"

permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"ManageInventory",
resource is MyApplication::Application
)
when {
principal.employmentStoreCode == resource.storeId
};

Vous obtenez ainsi le meilleur des deux mondes : le regroupement par rôles + la granularité par attributs.


Tests et déploiement

Une fois vos politiques en place, testez-les :

  1. En tant qu'utilisateur Customer : peut-il exécuter SearchPets ? ✅ Oui. GetStoreInventory ? ❌ Non.
  2. En tant qu'utilisateur Store-Owner-Role : peut-il exécuter GetStoreInventory pour son magasin ? ✅ Oui. Pour un autre magasin ? ❌ Non.
  3. En tant qu'owner avec employmentStoreCode=petstore-london : peut-il accéder aux commandes de petstore-london ? ✅ Oui.

Si une politique ne fonctionne pas, vérifiez :

  • Vos entités sont-elles correctement structurées dans le Lambda ?
  • Le JWT de l'utilisateur contient-il les bons cognito:groups ?
  • L'attribut personnalisé figure-t-il bien dans le profil utilisateur ?
  • Les types d'entités de votre requête d'autorisation correspondent-ils à votre schéma ?

Conclusion : de la plomberie à l'essentiel

Je n'avais pas prévu de présenter Amazon Verified Permissions comme la solution miracle pour votre application, mais si vous êtes englué dans un processus sans fin d'ajustement du code de votre application web basée sur les rôles parce que les exigences métier changent trop souvent, essayez de vous arrêter, de prendre du recul et de repenser ce que vous faites.

Ce service peut peut-être vous faire gagner du temps et des ressources, et vous libérer pour vous concentrer sur votre activité plutôt que sur la plomberie. Au fond, tout cela ressemble à de la plomberie : elle soutient les objectifs métier de votre application, mais ne contribue pas vraiment à en accroître la valeur.

Mais au moins, avec Amazon Verified Permissions, vous ne réparez pas la plomberie chaque semaine.