How to limit access in Amplify Graphql API using IAM auth?

0

My GraphQL API:

type MyEntity @model @auth(rules: [
      { allow: owner, ownerField: "ownerId"}
    ]) {
  id: ID!
  ownerId: String!
  strFld: String!
}

Now, I would like a lambda ("myLambda") to query MyEntity. So I added { allow: private, provider: iam } to MyEntity, which allowed the lambda to query it (following these instructions for IAM auth). However, in addition to allowing myLambda to access MyEntity, it basically bypassed the owner protection, and allowed any client connecting via IAM to edit any other owner's MyEntity.

For example, I didn't do anything in particular to give my react client IAM access, and it normally queries MyEntity via userGroups. But by just changing the graphql query in the react client to use GRAPHQL_AUTH_MODE.AWS_IAM, now it can read and write any MyEntity regardless of owner.

When using IAM auth for an entity, how do I limit it so that only my lambda has access? The docs say things like "IAM can be used to manage access", but they don't explain what role/policy/etc will be used by an external (e.g., react) client that I could edit to remove access.

Travis
asked 9 months ago589 views
2 Answers
0

Hi,

If you use IAM for authorizations (coupled with Cognito if you have very large of authentified users), you can restrict access to only certain of you APIs: see https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html

You can find YourGraphQLApiId from the main API listing page in the AppSync console, directly under the
 name of your API. Alternatively you can retrieve it with the CLI: aws appsync list-graphql-apis

If you want to restrict access to just certain GraphQL operations, you can do this for the root Query, Mutation, 
and Subscription fields.


{
   "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>"
         ]
     }
   ]
}

Have a look at the real and detailed example right after the excerpt above, it should provide good guidance for your use case.

Best,

Didier

profile pictureAWS
EXPERT
answered 9 months ago
0

After a lot of struggling with this, I think I understand.

For a lambda to be able to call an Amplify GraphQL API, you would need to add an auth rule in the API like { allow: private, provider: iam }. You would also do amplify function update and grant your function access to call the API, which would update the execution role for that lambda to have permission to call the API.

However, adding the auth rule in the API has another effect: If you use Cognito authentication, it will also add permissions to call the API to the "authRole" that is used by the identity pool, which means that any user who is logged in can now make IAM queries to access this API.

I did not want this -- I am trying to only grant access to the lambda, not the whole world. In fact, I didn't want my Cognito users to make any IAM queries (they should only use user pool auth), so I created an additional empty role and modified my identity pool to use it as its authRole. Unfortunately this is a huge pain. I had to create a custom resource for the Role, and then overriding the amplify auth involves writing CDK code in typescript, which is very hard to troubleshoot when it doesn't compile the first time (compilation generates very vague compiler errors).

But anyway, it seemed to work. This is what I did:

amplify custom add to add a new resource for my empty role:

  "IdentityPoolAuthRole": {
    "Type": "AWS::IAM::Role",
    "Properties": {
      "RoleName": {
        "Fn::Sub": [
          "(myapp)IdentityPoolAuthRole-${env}",
          {
            "env": {
              "Ref": "env"
            }
          }
        ]
      },
      "AssumeRolePolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "cognito-identity.amazonaws.com:amr": "authenticated"
                }
            }
          }
        ]
      }
    }
  }

Note: ideally this Role would specify the identity pool ID in its AssumeRolePolicyDocument, but this would cause a problem with different identity pools in different environments.

Then, amplify override auth and put this in override.ts:

import { AmplifyAuthCognitoStackTemplate, AmplifyProjectInfo } from '@aws-amplify/cli-extensibility-helper';

export function override(resources: AmplifyAuthCognitoStackTemplate, amplifyProjectInfo: AmplifyProjectInfo) {
  resources.identityPoolRoleMap.roles["authenticated"] = "(arn of the role created above)-" + amplifyProjectInfo.envName;
}

In my opinion it's a problem that it's so difficult to secure an amplify API once you grant permission for lambda to call it.

I've submitted a feedback on the amplify instructions related to this.

Travis
answered 9 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions