top of page

Solution SaaS : comment isoler les « Tenants » avec des IAM « policies » générées dynamiquement

Dernière mise à jour : 28 mai

Icône de bouclier bleu avec serrure, entourée de motifs hexagonaux et points lumineux sur fond sombre, évoquant la cybersécurité.

En tant qu’entreprise créatrice de solutions SaaS (« Software as a Service ») sur AWS, vous utilisez certainement AWS Identity and Access Management (IAM) comme colonne vertébrale de votre stratégie d’isolation de vos ressources et datas (« tenants ») pour vos clients.


IAM vous permet de définir une série de permissions en créant des « policies » et en les attachant à des identités IAM (utilisateurs, groupes d’utilisateurs ou rôles) ou des ressources AWS. Une « policie » est un objet dans AWS qui, lorsqu’il est associé à une identité ou à une ressource, définit les autorisations de ces dernières.


AWS évalue ces « policies » lorsqu’un principal IAM (utilisateur ou rôle) envoie une demande. Les permissions déterminent si la demande est autorisée ou refusée. AWS prend en charge six types de “policies” :

  • basées sur une identité,

  • basées sur les ressources,

  • les limites d’autorisations,

  • les politiques de contrôle de service Organizations,

  • les listes ACL

  • et les politiques de session.


En utilisant ce service, vous êtes nombreux à créer des « policies » distinctes pour chaque client. Mais très vite, cela peut engendrer une explosion voire un “plat de spaghettis” et rendre la situation impossible à maintenir.


Pour éviter cette situation, la solution est de mettre en place une génération dynamique. Cette méthode offre une expérience d’isolation plus évolutive et davantage administrable. Voyons comment la déployer.


TABLE DES MATIÈRES




RBAC, ABAC, et IAM « policies » générées dynamiquement : quelles différences entre ces méthodes d’isolation ?


Avant toute chose, voici un rappel des trois principales méthodes d’isolation possibles avec IAM.


  1. RBAC – Authentification des utilisateurs avec le contrôle d’accès basé sur les rôles : vous attribuez à chaque client un rôle IAM dédié ou un groupe de rôles IAM statiques qu’il utilise pour accéder aux ressources. RBAC est efficace lorsque vous avez un petit nombre de Tenants et des « policies » relativement statiques.

  2. ABAC – Contrôle d’accès basé sur les attributs : cette technique convient à un ensemble d’applications SaaS qui connaissent une croissance rapide, sauf si vous devez prendre en charge fréquemment des modifications ou ajouts de rôles. Dans ce cas, les IAM « policies » générées dynamiquement s’imposent.

  3. IAM « policies » générées dynamiquement : cette technique crée dynamiquement une IAM « policie » pour un client conformément à l’identité de l’utilisateur. Choisissez cette technique dans des applications hautement dynamiques avec des définitions de rôles changeantes ou fréquemment ajoutées (par exemple, scénario de collaboration de Tenants).



Les fondamentaux de l’isolation avec IAM


Voici comment vous pouvez isoler des Tenants avec IAM à l’aide du Security Token Service (STS).

Diagramme de flux AWS montrant l'authentification utilisateur via Amazon Cognito, appel API, obtention d'identifiants temporaires et accès aux données.
  1. Les requêtes entrantes incluent une en-tête d’authentification avec un JSON Web Token (JWT) (1) qui comprend des données identifiant le Tenant actuel. Ce jeton est signé par un fournisseur d’identité, garantissant que le JWT ne peut pas être modifié et que l’identité du Tenant peut être approuvée.

  2. Nous récupérons (2) la « policie » spécifique au Tenant à partir d’IAM et demandons à STS de renvoyer un identifiant (3) défini dans notre gestion des permissions.

  3. Lorsque nous tentons d’accéder à (4) Amazon DynamoDB, notre autorisation (5) d’effectuer l’appel SDK getItem est vérifiée par IAM, autorisant ou refusant l’accès en fonction de la « policie » spécifique au Tenant que nous avons récupérée.


Ce modèle est simple et limite l’accès à des données Amazon DynamoDB spécifiques pour notre Tenant. Cependant, ce modèle nécessite de créer une permission personnalisée pour chaque Tenant du système :

Texte JSON sur fond blanc affichant une politique IAM, indiquant l'autorisation d'accès à une table DynamoDB pour "Tenant1".

En ligne 11, nous voyons que l’identifiant du Tenant est codé en dur dans notre permission. Cela présente plusieurs problèmes :

  • Si le nombre de Tenants utilisant notre système augmente très rapidement, le nombre de permissions va devenir ingérable.

  • Lorsque vous publierez de nouvelles fonctionnalités pour votre service, vous devrez modifier vos ressources IAM existantes et mettre à jour les processus d’intégration. Cela créera un couplage étroit entre vos services et votre infrastructure de sécurité, ce qui peut augmenter la complexité de votre processus de déploiement.

  • Cela va limiter également la capacité de votre équipe à se concentrer sur la création de nouvelles fonctionnalités. Au fur et à mesure que votre l’isolation et la sécurité des Tenants va devenir de + en + difficile à maintenir et à tester, vous augmenterez également la possibilité d’introduire une erreur qui pourrait exposer les données des Tenants.



Comment mettre en place des IAM « policies » générées dynamiquement ?


Reprenons l’exemple précédent (tentative de restriction d’accès d’une ressource Amazon DynamoDB à un utilisateur). Cette fois, nous ne stockons pas notre « policie » dans IAM. Nous décidons de transformer notre « policie » en un modèle où les références de Tenants statiques sont remplacées par des espaces réservés de modèle.


Les espaces réservés de table et de Tenant dans le modèle suivant peuvent désormais être hydratés avec les valeurs appropriées lors de l’exécution.


Code JSON pour permissions AWS DynamoDB, incluant des actions autorisées, ressources, et conditions de clés égales. Fond blanc.
  • En ligne 4, vous remarquerez que l’action de ce modèle a une portée large. Cela nous donne la plus grande flexibilité pour appliquer cette autorisation à une variété de cas d’utilisation.

  • Dans cette stratégie, les ressources, en ligne 7, ne sont pas spécifiques au Tenant, mais notez que certaines « policies » imposent l’isolation des Tenants au niveau des ressources.

  • L’opérateur de condition (en ligne 9) limite notre Tenant à ne voir que les lignes avec une clé qui commence par une valeur d’identifiant de Tenant spécifique (ligne 11).



Assumer un rôle


Dans cet exemple, le rôle doit contenir une permission autorisant l’accès à une ressource Amazon DynamoDB. Elle permet à quiconque d’accéder aux ressources DynamoDB sans aucune limitation spécifique au Tenant.


Code JSON affichant une politique AWS IAM pour DynamoDB, avec actions comme GetItem et Query, sur fond blanc, texte en noir.

Maintenant, examinez la permission générée dynamiquement à l’aide du Security Token Service (STS).


Code JSON de politique IAM AWS affichant des actions DynamoDB, ressources et conditions avec texte noir sur fond blanc.

Du point de vue du code, assumer un rôle avec STS est simple. Voici une version abrégée du code qui assume un nouveau rôle.


Code affiché sur fond blanc. Instructions de configuration de credientials AWS en utilisant AssumeRole et DynamoDbClient en Java.

Modèles d’autorisation


Le cœur du distributeur automatique de jetons est un ensemble de fichiers de modèles d’autorisation. Voyons maintenant comment gérer ces fichiers modèles, leur permettant d’évoluer indépendamment de notre code.


Exemple : comment créer une permission Amazon S3 pour restreindre l’accès au niveau d’un dossier ?


Voici comment accéder à la ListBuckets action (4) pour les Tenants dont le préfixe correspond à l’identifiant du Tenant. Cela limite la capacité d’un Tenant à interagir avec des objets dans des dossiers appartenant à d’autres Tenants.


1 {

2 "Effect": "Allow",

3 "Action": [

4 "s3:ListBucket"

5 ],

6 "Resource": [

7 "arn:aws:s3:::{{bucket}}"

8 ],

9 "Condition": {

10 "StringLike": {

11 "s3:prefix": [

12 "{{tenant}}",

13 "{{tenant}}/",

14 "{{tenant}}/*"

15 ]

16 }

17 }

18 },

19 {

20 "Effect": "Allow",

21 "Action": [

22 "s3:GetObject",

23 "s3:PutObject",

24 "s3:DeleteObject"

25 ],

26 "Resource": [

27 "arn:aws:s3:::{{bucket}}/{{tenant}}/*"

28 ]

29 }



Les modèles sont conservés séparément de votre code, et sont déployés et versionnés indépendamment.


Étant donné que les modèles ne sont que des fichiers JSON, il peut être préférable de les considérer comme faisant partie de votre infrastructure, comme un processus de déploiement de code. Leur déploiement sur un système de fichiers tel qu’Amazon Elastic File System (Amazon EFS) ou Amazon S3, tous deux accessibles sur l’ensemble de votre architecture, s’intégrerait bien dans une architecture de microservices.



Génération d’une permission à partir de modèles


Maintenant que nos permission sont définies en dehors d’IAM, nous pouvons introduire du code qui charge nos modèles d’autorisation dans des instructions et les ajoute à des « policies » à portée dynamique lors de l’exécution. Voyons comment nous hydratons les modèles d’autorisation pour créer des permission.


Voici une classe Java simple appelée PolicyGenerator, responsable de la création d’une permission :


1 String scopedPolicy = PolicyGenerator.generator()

2 .s3FolderPerTenant(bucket)

3 .dynamoLeadingKey(tableName)

4 .tenant(tenantIdentifier)

5 .generatePolicy();



L’objectif est de permettre au développeur d’ajouter aussi facilement que possible des autorisations de sécurité correctement formées et valides. Ce processus nécessite l’accès à l’identifiant du Tenant qui a été extrait du JWT.


Chaque méthode d’autorisation que nous ajoutons prend également comme paramètres les valeurs requises nécessaires pour hydrater ce modèle spécifique. Le distributeur automatique de jetons dans notre exemple est configuré pour ajouter les autorisations dont le microservice a besoin et localiser les valeurs dont il a besoin à partir de variables environnementales.



Conclusion


La génération de « policies » dynamiques peut aider les fournisseurs SaaS à mettre en œuvre l’isolation des Tenants. L’exemple d’implémentation d’un distributeur automatique de jetons fournit un mécanisme administrable pour implémenter cette génération dynamique.


Lorsque vous implémentez votre propre distributeur automatique de jetons, gardez à l’esprit quelques points :

Les modèles d’autorisation doivent fournir une séparation de vos permissions d’isolement, leur permettant d’évoluer indépendamment de vos applications.

Les « policies » doivent permettre le principe du moindre privilège, limitant l’accès de vos Tenants aux services et aux données aussi étroitement que possible.

L’identité du Tenant doit être résolue de manière cohérente et vérifiable dans votre solution.

Votre solution doit être encapsulée et réutilisable dans tous vos services SaaS, en renvoyant des informations d’identification à l’échelle du Tenant.


Pour plus d’informations sur les exemples de cet article, consultez le référentiel AWS SaaS Factory GitHub : https://github.com/aws-samples/aws-saas-factory-dynamic-policy-generation


Il contient un exemple d’application et des ressources AWS CloudFormation pour provisionner automatiquement l’infrastructure nécessaire dans votre compte AWS.




bottom of page