aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.82k stars 820 forks source link

IAM auth failing to query GraphQL in lambda created through amplify-cli #10535

Closed 0afcode closed 2 years ago

0afcode commented 2 years ago

Before opening, please confirm:

How did you install the Amplify CLI?

npm install -g @aws-amplify/cli

If applicable, what version of Node.js are you using?

14.x

Amplify CLI Version

8.4.0

What operating system are you using?

ubuntu

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

no

Amplify Categories

api

Amplify Commands

Not applicable

Describe the bug

Lambda function created through the amplify-cli errors out when trying to query a graphql api that is part of the project. Roles have been confirmed to show access to the api. similar functions that were created in the same way before CLI version 8.4.0 operate normally without issue.

Expected behavior

Amplify function has the permission to perform queries on the graphql api.

Reproduction steps

  1. on v8.4.0 of cli
  2. init project with api backed by dynamoDb and auth.
  3. set up IAM as auth type for the configuration.
  4. create schema model that have a private IAM auth role
  5. create lambda function with access to CRUD the api
  6. in the lambda, use code to make a query to the graphql api

GraphQL schema(s)

```graphql # Put schemas below this line type SourceSync @model (subscriptions: { level: off }) @auth(rules: [ {allow: private, provider: iam}]) { id: ID! @primaryKey name: String abbr: String modelName: String url: String } ```

Log output

``` ERROR Invoke Error { "errorType": "Error", "errorMessage": "GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection", "graphQLErrors": [ { "path": [ "listSourceSyncs" ], "data": null, "errorType": "Unauthorized", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection" } ], "networkError": null, "message": "GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection", "stack": [ "Error: GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection", " at new ApolloError (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:85:32)", " at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1039:45", " at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1411:21", " at Array.forEach ()", " at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1410:22", " at Map.forEach ()", " at QueryManager.broadcastQueries (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:1405:26)", " at Object.next (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:1462:35)", " at notifySubscription (/opt/nodejs/node_modules/zen-observable/lib/Observable.js:135:18)", " at onNotify (/opt/nodejs/node_modules/zen-observable/lib/Observable.js:179:3)" ] } ```

Additional information

I created a lambda function through the "amplify add function" command. I gave it all the permissions to CRUD on my existing api just as any other function I've created in the past. I verified that the function, once the backend got pushed and built on the cloud, had the correct role set up as all my other previous lambda functions performing similar actions.

image

I used the same test code I always do to confirm the function will work, and on this instance it keeps failing with an unauthorized error. The only thing I can think of is that my other functions were created pre cli version 8.3:

ERROR   Invoke Error    {
    "errorType": "Error",
    "errorMessage": "GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection",
    "graphQLErrors": [
        {
            "path": [
                "listSourceSyncs"
            ],
            "data": null,
            "errorType": "Unauthorized",
            "errorInfo": null,
            "locations": [
                {
                    "line": 2,
                    "column": 3,
                    "sourceName": null
                }
            ],
            "message": "Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection"
        }
    ],
    "networkError": null,
    "message": "GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection",
    "stack": [
        "Error: GraphQL error: Not Authorized to access listSourceSyncs on type ModelSourceSyncConnection",
        "    at new ApolloError (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:85:32)",
        "    at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1039:45",
        "    at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1411:21",
        "    at Array.forEach (<anonymous>)",
        "    at /opt/nodejs/node_modules/apollo-client/bundle.umd.js:1410:22",
        "    at Map.forEach (<anonymous>)",
        "    at QueryManager.broadcastQueries (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:1405:26)",
        "    at Object.next (/opt/nodejs/node_modules/apollo-client/bundle.umd.js:1462:35)",
        "    at notifySubscription (/opt/nodejs/node_modules/zen-observable/lib/Observable.js:135:18)",
        "    at onNotify (/opt/nodejs/node_modules/zen-observable/lib/Observable.js:179:3)"
    ]
}

My schema for SourceSync model:

type SourceSync @model (subscriptions: { level: off }) @auth(rules: [
    {allow: private, provider: iam}]) {
    id: ID! @primaryKey
    name: String
    abbr: String
    modelName: String
    url: String
}

And the code in the lambda function in question. This lambda has a layer were the packages are being imported from. I've confirmed the layer is operational, because I have the older lambda functions in this project that are using this particular layer and they still work.

/* Amplify Params - DO NOT EDIT
    API_MYAPI_GRAPHQLAPIENDPOINTOUTPUT
    API_MYAPI_GRAPHQLAPIIDOUTPUT
    ENV
    REGION
Amplify Params - DO NOT EDIT */

"use strict";
const awsAppSync = require("aws-appsync");
const gql = require("graphql-tag");
require("cross-fetch/polyfill");
const aws = require("aws-sdk");

aws.config.update({
    region: process.env.REGION
});
const appSync = new aws.AppSync();

exports.handler = async (event) => {
    console.log('starting...');
    const qs = gql`query myquery {
        listSourceSyncs {
            items {
                id,
                _version
            }
        }
    }`;
    const res = await graphqlClient.query({query: qs, fetchPolicy: 'no-cache'});
    console.log(res);
};

const graphqlClient = new awsAppSync.AWSAppSyncClient({
    url: process.env.API_MYAPI_GRAPHQLAPIENDPOINTOUTPUT,
    region: process.env.REGION,
    auth: {
        type: "AWS_IAM",
        credentials: aws.config.credentials,
    },
    disableOffline: true
});
0afcode commented 2 years ago

Additional comment: I have also confirmed that if I go and add a custom-roles.json with my function's role's name in the admin property, now the connection is successfully established. However this is a workaround, since per the article here ( https://docs.amplify.aws/cli/graphql/authorization-rules/#grant-lambda-function-access-to-graphql-api ), the function created through the CLI as part of an amplify project, should not have to go through this extra step to have it able to access graphql through IAM credentials.

josefaidt commented 2 years ago

Hey @0afcode :wave: thanks for raising this! This appears similar to https://github.com/aws-amplify/amplify-category-api/issues/679. If this is an existing GraphQL API that we are granting access to, can you try adding a small comment or space to your schema to trigger an UPDATE to the API? In the linked issue the problem lies with the VTL resolvers not being updated after granting access

0afcode commented 2 years ago

Hey @0afcode 👋 thanks for raising this! This appears similar to aws-amplify/amplify-category-api#679. If this is an existing GraphQL API that we are granting access to, can you try adding a small comment or space to your schema to trigger an UPDATE to the API? In the linked issue the problem lies with the VTL resolvers not being updated after granting access

Hey @josefaidt that appears to be the bug. I created another function to test it with, added CRUD permissions for the api. Did an amplify push command just to see what changed. only the new function is listed as "create" at this point. Then I added a new line to the schema.graphql file in the api to trigger a modify and ran the amplify push command. Now the api is listed as 'update' while the new function listed as 'create'. Continued with the build, and tried out the same function code listed above, and this time it worked.

OskarD commented 2 years ago

Additional comment: I have also confirmed that if I go and add a custom-roles.json with my function's role's name in the admin property, now the connection is successfully established. However this is a workaround, since per the article here ( https://docs.amplify.aws/cli/graphql/authorization-rules/#grant-lambda-function-access-to-graphql-api ), the function created through the CLI as part of an amplify project, should not have to go through this extra step to have it able to access graphql through IAM credentials.

Where did you put this file, and can you show me the structure?

I tried putting it in my api's directory like this, but it didn't help my case:

{
  "adminRoleNames": ["arn:aws:sts::<My App ID>:assumed-role"]
}
0afcode commented 2 years ago

Additional comment: I have also confirmed that if I go and add a custom-roles.json with my function's role's name in the admin property, now the connection is successfully established. However this is a workaround, since per the article here ( https://docs.amplify.aws/cli/graphql/authorization-rules/#grant-lambda-function-access-to-graphql-api ), the function created through the CLI as part of an amplify project, should not have to go through this extra step to have it able to access graphql through IAM credentials.

Where did you put this file, and can you show me the structure?

I tried putting it in my api's directory like this, but it didn't help my case:

{
  "adminRoleNames": ["arn:aws:sts::<My App ID>:assumed-role"]
}

Your parameter's value is incorrect. You need to provide the role's name (excluding the env) instead of the ARN.

The file is in < project >/amplify/backend/api/< ApiName >/custom-roles.json example: { "adminRoleNames": ["devLambdaRole10ae332f"] }

josefaidt commented 2 years ago

Hey @0afcode I don't believe adding these function roles is required for this to work, however I'm curious if the generated VTL resolvers include the role in the auth resolvers? This should be handled for us by the CLI

josefaidt commented 2 years ago

Hey @0afcode :wave: just wanted to follow up on this issue and see if the VTL resolvers included the Lambda role?

josefaidt commented 2 years ago

Closing due to inactivity

0afcode commented 2 years ago

@josefaidt this was caused by the api stack not triggering a rebuild/update after the graphql resources were allowed access to a function with amplify update function

so in other words, if we want to allow a lambda function created through amplify to have access to a graphql api also created from the same amplify stack, we need to trigger a change on the schema.graphql file in order for the amplify push script to detect an update with the api as well, and trigger a rebuild of the schema/resolvers. This solution was discussed in another thread, but I cannot seem to find it to link here anymore.