Sécurité - AWS AppSync

Sécurité

Cette section décrit les options dont vous disposez pour configurer la sécurité et la protection des données pour vos applications.

Il existe quatre méthodes permettant d'autoriser vos applications à interagir avec votre API AWS AppSync GraphQL. Vous indiquez le type d'autorisation que vous utilisez en spécifiant l'une des valeurs de type d'autorisation suivantes dans votre API AWS AppSync ou dans un appel via l'interface de ligne de commande :

  • API_KEY

    Pour utiliser des clés API.

  • AWS_IAM

    Pour utiliser les autorisations AWS Identity and Access Management (IAM).

  • OPENID_CONNECT

    Pour utiliser votre fournisseur OpenID Connect.

  • AMAZON_COGNITO_USER_POOLS

    Pour utiliser un groupe d'utilisateurs Amazon Cognito.

Ces types d'autorisation de base fonctionnent pour la plupart des développeurs. Pour les cas d'utilisation plus avancés, vous pouvez ajouter des modes d'autorisation supplémentaires via la console, l'interface de ligne de commande et AWS CloudFormation. Pour les modes d'autorisation supplémentaires, AppSync fournit un type d'autorisation qui prend les valeurs répertoriées ci-dessus (c'est-à-dire API_KEY, AWS_IAM, OPENID_CONNECT, AMAZON_COGNITO_USER_POOLS).

Lorsque vous spécifiez API_KEY ou AWS_IAM comme type d'autorisation principal ou par défaut, vous ne pouvez pas les spécifier à nouveau comme l'un des modes d'autorisation supplémentaires. De même, vous ne pouvez pas dupliquer API_KEY ni AWS_IAM à l'intérieur des modes d'autorisation supplémentaires. Vous pouvez utiliser plusieurs groupes d'utilisateurs Amazon Cognito et fournisseurs OpenID Connect. Cependant, vous ne pouvez pas utiliser des groupes d'utilisateurs Amazon Cognito ou des fournisseurs OpenID Connect dupliqués entre le mode d'autorisation par défaut et l'un des modes d'autorisation supplémentaires. Vous pouvez spécifier différents clients pour votre groupe d'utilisateurs Amazon Cognito ou fournisseur OpenID Connect à l'aide de l'expression regex de configuration correspondante.

Autorisation API_KEY

Les API non authentifiées nécessitent des limitations plus strictes que les API authentifiées. Pour contrôler les limitations des points de terminaison GraphQL non authentifiés, vous pouvez utiliser des clés API. Une clé API est une valeur codée en dur dans votre application, qui est générée par le service AWS AppSync quand vous créez un point de terminaison GraphQL non authentifié. Vous pouvez effectuer la rotation des clés API à partir de la console, de l'interface de ligne de commande ou de la Référence de l'API AWS AppSync.

Les clés API sont configurables pour une durée maximale de 365 jours et vous pouvez prolonger une date d'expiration existante de 365 jours maximum à partir de cette date. Les clés API sont recommandées pour le développement ou pour les cas d'utilisation où il est possible d'exposer une API publique en toute sécurité.

Sur le client, la clé API est spécifiée par l'en-tête x-api-key.

Par exemple, si votre API_KEY a pour valeur 'ABC123', vous pouvez envoyer une requête GraphQL via curl comme suit :

$ curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:ABC123" -d '{ "query": "query { movies { id } }" }' https://YOURAPPSYNCENDPOINT/graphql

Autorisation AWS_IAM

Ce type d'autorisation applique le Processus de signature AWS Signature Version 4 sur l'API GraphQL. Vous pouvez associer les stratégies d'accès Identity and Access Management (IAM) à ce type d'autorisation. Votre application peut mettre à profit cette association en utilisant une clé d'accès (qui se compose d'un ID de clé d'accès et d'une clé d'accès secrète) ou en utilisant des informations d'identification temporaires à courte durée fournies par les identités fédérées Amazon Cognito.

Si vous souhaitez un rôle pouvant effectuer toutes les opérations de données :

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/*" ] } ] }

Vous trouverez l'élément YourGraphQLApiId sur la page principale de liste des API de la console AppSync, directement sous le nom de votre API. Vous pouvez aussi récupérer cet élément via l'interface de ligne de commande : aws appsync list-graphql-apis

Si vous souhaitez limiter l'accès à un nombre limité d'opérations GraphQL, vous pouvez le faire pour les champs racine Query, Mutation et Subscription.

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-2>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Mutation/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Subscription/fields/<Field-1>" ] } ] }

Par exemple, supposons que vous ayez le schéma suivant et que vous souhaitiez limiter l'accès à l'obtention de tous les billets de blog :

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! }

La stratégie IAM correspondante pour un rôle (que vous pouvez lier à un groupe d'identités Amazon Cognito, par exemple) se présente comme suit :

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/posts" ] } ] }

Autorisation OPENID_CONNECT

Ce type d'autorisation applique les jetons OpenID Connect (OIDC) fournis par un service compatible avec OIDC. Votre application peut tirer parti des utilisateurs et des privilèges définis par votre fournisseur OIDC pour le contrôle des accès.

La seule valeur de configuration obligatoire que vous fournissez à AWS AppSync est une URL d'auteur (par exemple, https://auth.example.com). Cette URL doit être adressable via HTTPS. AWS AppSync ajoute /.well-known/openid-configuration à l'URL d'auteur et localise la configuration OpenID à l'adresse https://auth.example.com/.well-known/openid-configuration conformément à la spécification OpenID Connect Discovery. Vous devez normalement récupérer un document JSON compatible avec la norme RFC5785 au niveau de cette URL. Ce document JSON doit contenir une clé jwks_uri qui pointe vers le document JSON Web Key Set (JWKS) avec les clés de signature.

AWS AppSync nécessite le JWKS pour contenir les champs JSON de alg, kty et kid.

AWS AppSync prend en charge RS256 RS384 RS512 comme algorithmes de signature. Les jetons émis par le fournisseur doivent inclure l'heure d'émission du jeton (iat) et peuvent inclure son heure d'authentification (auth_time). Vous pouvez fournir des valeurs de durée de vie (TTL) pour l'heure d'émission (iatTTL) et l'heure d'authentification (authTTL) dans votre configuration OpenID Connect afin de renforcer la validation. Si votre fournisseur autorise plusieurs applications, vous pouvez également fournir une expression régulière (clientId) qui est utilisée pour les autorisations par ID client.

Pour valider plusieurs ID de clients, utilisez l'opérateur de pipeline (« | ») qui est un « ou » dans regex. Par exemple, si votre application OIDC a quatre clients avec des ID clients tels que 0A1S2D, 1F4G9H, 6GS5MG, 1J6L4B, pour valider uniquement les trois premiers ID clients, vous devez placer 1F4G9H|1J6L4B|6GS5MG dans le champ ID client.

Autorisation AMAZON_COGNITO_USER_POOLS

Ce type d'autorisation applique les jetons OIDC fournis par les groupes d'utilisateurs Amazon Cognito. Votre application peut mettre à profit les utilisateurs et les groupes dans vos groupes d'utilisateurs et les associer à des champs GraphQL pour le contrôle d'accès.

Lorsque vous utilisez des groupes d'utilisateurs Amazon Cognito, vous pouvez créer les groupes auxquels les utilisateurs appartiennent. Ces informations sont codées dans un jeton JWT que votre application envoie à AWS AppSync dans un en-tête d'autorisation lors de l'envoi des opérations GraphQL. Vous pouvez utiliser les directives GraphQL sur le schéma pour contrôler les groupes pouvant appeler des résolveurs sur un champ, ainsi que les résolveurs pouvant être appelés, ce qui permet à vos clients de bénéficier d'un accès plus contrôlé.

Supposons, par exemple, que vous ayez le schéma GraphQL suivant :

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! } ...

Si vous avez deux groupes dans les groupes d'utilisateurs Amazon Cognito (blogueurs et lecteurs) et que vous souhaitez limiter les lecteurs afin qu'ils ne puissent pas ajouter de nouvelles entrées, votre schéma doit se présenter comme suit :

schema { query: Query mutation: Mutation }
type Query { posts:[Post!]! @aws_auth(cognito_groups: ["Bloggers", "Readers"]) } type Mutation { addPost(id:ID!, title:String!):Post! @aws_auth(cognito_groups: ["Bloggers"]) } ...

Notez que vous pouvez omettre la directive @aws_auth si vous souhaitez utiliser par défaut une stratégie d'octroi ou de refus spécifique pour l'accès. Vous pouvez spécifier cette stratégie d'octroi ou de refus dans la configuration du groupe d'utilisateurs lors de la création de votre API GraphQL via la console ou via la commande suivante de l'interface de ligne de commande :

$ aws appsync --region us-west-2 create-graphql-api --authentication-type AMAZON_COGNITO_USER_POOLS --name userpoolstest --user-pool-config '{ "userPoolId":"test", "defaultEffect":"ALLOW", "awsRegion":"us-west-2"}'

Utilisation de modes d'autorisation supplémentaires

Lorsque vous ajoutez des modes d'autorisation supplémentaires, vous pouvez configurer directement le paramètre d'autorisation au niveau de l'API GraphQL AWS AppSync (c'est-à-dire, le champ authenticationType que vous pouvez configurer directement sur l'objet GraphqlApi) et il agit en tant que valeur par défaut sur le schéma. Cela signifie que tout type n'ayant pas de directive spécifique doit transmettre le paramètre d'autorisation au niveau de l'API.

Au niveau du schéma, vous pouvez spécifier des modes d'autorisation supplémentaires à l'aide de directives sur le schéma. Vous pouvez spécifier des modes d'autorisation sur des champs spécifiques du schéma. Par exemple, pour l'autorisation API_KEY, vous devez utiliser @aws_api_key sur les définitions/champs de type d'objet de schéma. Les directives suivantes sont prises en charge sur les champs de schéma et les définitions de types d'objet :

  • @aws_api_key : permet de spécifier que le champ a l'autorisation API_KEY.

  • @aws_iam : permet de spécifier que le champ a l'autorisation AWS_IAM.

  • @aws_oidc : permet de spécifier que le champ a l'autorisation OPENID_CONNECT.

  • @aws_cognito_user_pools : permet de spécifier que le champ a l'autorisation AMAZON_COGNITO_USER_POOLS.

Vous ne pouvez pas utiliser la directive @aws_auth avec des modes d'autorisation supplémentaires. @aws_auth fonctionne uniquement dans le contexte de l'autorisation AMAZON_COGNITO_USER_POOLS sans mode d'autorisation supplémentaire. Cependant, vous pouvez utiliser la directive @aws_cognito_user_pools à la place de la directive @aws_auth, en utilisant les mêmes arguments. La principale différence entre les deux est que vous pouvez spécifier @aws_cognito_user_pools sur n'importe quelle définition de champ et de type d'objet.

Pour comprendre comment fonctionnent les modes d'autorisation supplémentaires et comment ils peuvent être spécifiés sur un schéma, examinons le schéma suivant :

schema { query: Query mutation: Mutation } type Query { getPost(id: ID): Post getAllPosts(): [Post] @aws_api_key } type Mutation { addPost( id: ID! author: String! title: String! content: String! url: String! ): Post! } type Post @aws_api_key @aws_iam { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! } ...

Pour ce schéma, supposons que AWS_IAM est le type d'autorisation par défaut sur l'API GraphQL AWS AppSync. Cela signifie que les champs qui n'ont pas de directive sont protégés à l'aide de AWS_IAM. Par exemple, c'est le cas pour le champ getPost sur le type Query. Les directives de schéma vous permettent d'utiliser plusieurs modes d'autorisation. Par exemple, vous pouvez avoir configuré API_KEY comme mode d'autorisation supplémentaire sur l'API GraphQL AWS AppSync et vous pouvez marquer un champ à l'aide de la directive @aws_api_key (par exemple, getAllPosts dans cet exemple). Les directives fonctionnent au niveau du champ. Vous devez donc également accorder à API_KEY l'accès au type Post. Vous pouvez le faire en marquant chaque champ du type Post avec une directive ou en marquant le type Post avec la directive @aws_api_key.

Pour limiter davantage l'accès aux champs du type Post, vous pouvez utiliser des directives sur les champs individuels du type Post, comme illustré ci-après.

Par exemple, vous pouvez ajouter un champ restrictedContent au type Post et limiter l'accès à celui-ci à l'aide de la directive @aws_iam. Les demandes AWS_IAM authentifiées pourront accéder à restrictedContent, mais les demandes API_KEY ne pourront pas y accéder.

type Post @aws_api_key @aws_iam{ id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! restrictedContent: String! @aws_iam } ...

Contrôle précis des accès

Les informations précédentes montrent comment limiter ou accorder l'accès à certains champs GraphQL. Si vous voulez définir des contrôles d'accès sur les données en fonction de certaines conditions (par exemple, en fonction de l'utilisateur effectuant l'appel et du fait qu'il est ou non propriétaire des données), vous pouvez utiliser des modèles de mappage dans vos résolveurs. Vous pouvez également utiliser une logique métier plus complexe, dont vous trouverez la description dans Filtrage des informations.

Cette section explique comment définir les contrôles d'accès sur vos données à l'aide d'un modèle de mappage de résolveur DynamoDB.

Avant d'aller plus loin, si vous n'êtes pas familiarisé avec les modèles de mappage dans AWS AppSync, vous pouvez consulter les documents Référence du modèle de mappage des résolveurs et Référence du modèle de mappage des résolveurs pour DynamoDB.

Dans l'exemple suivant qui utilise DynamoDB, supposons que vous utilisiez le schéma de billet de blog précédent et que seuls les utilisateurs ayant créé un billet soient autorisés à le modifier. Le processus d'évaluation devrait consister pour l'utilisateur à obtenir des informations d'identification dans son application, à l'aide de groupes d'utilisateurs Amazon Cognito, par exemple, puis à transmettre ces informations d'identification dans le cadre d'une opération GraphQL. Le modèle de mappage remplace alors une valeur des informations d'identification (comme le nom utilisateur) dans une instruction conditionnelle qui sera ensuite comparée à une valeur dans votre base de données.

Pour ajouter cette fonctionnalité, ajoutez un champ GraphQL editPost comme suit :

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { editPost(id:ID!, title:String, content:String):Post addPost(id:ID!, title:String!):Post! } ...

Le modèle de mappage de résolveur pour editPost (illustré dans un exemple à la fin de cette section) doit effectuer une vérification logique par rapport à votre magasin de données afin que seul l'utilisateur ayant créé un billet puisse le modifier. Dans la mesure où il s'agit d'une modification, elle correspond à une opération UpdateItem dans DynamoDB. Vous pouvez effectuer une vérification conditionnelle avant d'exécuter cette action, en utilisant le contexte transmis pour la validation de l'identité de l'utilisateur. Ce dernier se trouve dans un objet Identity qui a les valeurs suivantes :

{ "accountId" : "12321434323", "cognitoIdentityPoolId" : "", "cognitoIdentityId" : "", "sourceIP" : "", "caller" : "ThisistheprincipalARN", "username" : "username", "userArn" : "Sameasabove" }

Pour utiliser cet objet dans un appel DynamoDBUpdateItem, vous devez stocker les informations d'identité d'utilisateur dans la table pour la comparaison. Tout d'abord, votre mutation addPost doit contenir le créateur. Ensuite, votre mutation editPost doit effectuer la vérification conditionnelle avant la mise à jour.

Voici un exemple de modèle de mappage de demande pour une mutation addPost qui contient l'identité d'utilisateur sous la forme d'une colonne Author :

{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "postId" : $util.dynamodb.toDynamoDBJson($context.arguments.id) }, "attributeValues" : { "Author" : $util.dynamodb.toDynamoDBJson($context.identity.username) #foreach( $entry in $context.arguments.entrySet() ) #if( $entry.key != "id" ) ,"${entry.key}" : $util.dynamodb.toDynamoDBJson($entry.value) #end #end }, "condition" : { "expression" : "attribute_not_exists(postId)" } }

Notez que l'attribut Author est renseigné à partir de l'objet Identity, qui provient de l'application.

Enfin, voici un exemple de modèle de mappage de demande pour une mutation editPost, qui met à jour le contenu du billet de blog uniquement si la demande provient de l'utilisateur auteur du billet :

{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id), }, "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args), "condition" : { "expression" : "contains(#author,:expectedOwner)", "expressionNames" : { "#author" : "Author" }, "expressionValues" : { ":expectedOwner" : $util.dynamodb.toDynamoDBJson($context.identity.username) } } }

Cet exemple utilise un PutItem qui écrase toutes les valeurs plutôt qu'un UpdateItem, ce qui serait un peu plus détaillé dans un exemple, mais le même concept s'applique au bloc d'instruction condition.

Filtrage des informations

Il peut arriver que vous ne puissiez pas contrôler la réponse de votre source de données, mais que vous ne souhaitiez pas envoyer des informations inutiles aux clients sur la réussite d'une opération de lecture ou d'écriture sur la source de données. Dans ce cas, vous pouvez filtrer les informations à l'aide d'un modèle de mappage de réponse.

Supposons, par exemple, que vous n'ayez pas d'index approprié sur votre table DynamoDB de billet de blog (par exemple, un index sur Author). Vous pouvez exécuter une requête GetItem avec le modèle de mappage suivant :

{ "version" : "2017-02-28", "operation" : "GetItem", "key" : { "postId" : $util.dynamodb.toDynamoDBJson($ctx.args.id) } }

Cette requête renvoie toutes les réponses, même si l'appelant n'est pas l'auteur du billet. Pour éviter cela, vous pouvez effectuer le contrôle d'accès sur le modèle de mappage de réponse comme suit :

{ #if($context.result["Author"] == "$context.identity.username") $utils.toJson($context.result); #end }

Si l'appelant ne correspond pas à ce contrôle, seule une réponse null est renvoyée.

Accès à une source de données

AWS AppSync communique avec les sources de données en utilisant les rôles et les stratégies d'accès de gestion des identités et des accès (IAM). Si vous utilisez un rôle existant, une stratégie d'approbation doit être ajoutée pour qu'AWS AppSync assume le rôle. La relation d'approbation doit ressembler à ce qui suit :

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }

Il est important de définir la stratégie d'accès en fonction du rôle, afin de n'avoir que les autorisations nécessaires pour agir sur l'ensemble minimal de ressources nécessaires. Lorsque vous utilisez la console AppSync pour créer une source de données et un rôle, cette opération est effectuée automatiquement pour vous. Cependant, lorsque vous utilisez un modèle d'exemple intégré de la console IAM pour créer un rôle en dehors de la console AWS AppSync, les permissions ne seront pas automatiquement limitées à une ressource et vous devez effectuer cette action avant de déplacer votre application en production.