amplify-cli icon indicating copy to clipboard operation
amplify-cli copied to clipboard

allowing Lambda function IAM read privileges for AppSync also requires create privileges

Open gspeicher opened this issue 2 years ago • 24 comments

It appears as though GraphQL/AppSync resource access permissions for Lambda functions were broken at some point so that read permissions no longer work unless create permissions are also added to the function. This violates the Principal of Least Privilege; read permission should be allowed without requiring create permission.

This line of code only generates access to the API resource identifier arn:aws:appsync:region:accountId:apis/GraphQLAPIIdOutput/* when permissions include create privileges. Note that the nearly-identical resource identifier without the trailing * is still generated but the version with the trailing star is no longer included without create.

In our Amplify project, we have a custom resolver function (aptly named Resolver), that has permission to read from the GraphQL API, but not permission to create. Running auth update function and stepping through resource access permissions for that function, re-choosing the existing settings without making any changes whatsoever, results in the following diff:

--- a/src/mobile/amplify/backend/function/Resolver/Resolver-cloudformation-template.json
+++ b/src/mobile/amplify/backend/function/Resolver/Resolver-cloudformation-template.json
@@ -583,33 +583,12 @@
             {
               "Effect": "Allow",
               "Action": [
-                "appsync:GraphQL",
                 "appsync:Get*",
                 "appsync:List*",
                 "appsync:Update*",
                 "appsync:Delete*"
               ],
               "Resource": [
-                {
-                  "Fn::Join": [
-                    "",
-                    [
-                      "arn:aws:appsync:",
-                      {
-                        "Ref": "AWS::Region"
-                      },
-                      ":",
-                      {
-                        "Ref": "AWS::AccountId"
-                      },
-                      ":apis/",
-                      {
-                        "Ref": "apinameGraphQLAPIIdOutput"
-                      },
-                      "/*"
-                    ]
-                  ]
-                },
                 {
                   "Fn::Join": [
                     "",

Then if we redeploy Resolver and access a custom API endpoint, which in turn calls the GraphQL GetProfile API operation, we get the following error:

{ 
   "errorMessage" : "GraphQL Errors[GetProfile]: [{\"errorType\":\"UnauthorizedException\",\"message\":\"Permission denied\"}]",
   "errorType" : "Error", 
   "stack" : [
      "Error: GraphQL Errors[GetProfile]: [{\"errorType\":\"UnauthorizedException\",\"message\":\"Permission denied\"}]",
      " at GraphQLGateway.runQuery (/opt/nodejs/node_modules/@crft/appsync-gateway/dist/gateway.js:40:19)",
      " at processTicksAndRejections (internal/process/task_queues.js:95:5)",
      " at async query (/var/task/index.js:45:20)",
      " at async customEndpoint (/var/task/index.js:111:21)"
   ]
}

Restoring the original Resolver CloudFormation template and redeploying fixes the resolver so that the call to GetProfile works again. Stepping through the CLI function update and adding create privileges also fixes the problem, but violates the Principal of Least Privilege.

gspeicher avatar Apr 05 '22 15:04 gspeicher

Hi @gspeicher I think this may be a duplicate of https://github.com/aws-amplify/amplify-cli/issues/10141 Can you confirm that you have the generateGraphQLPermissions feature flag enabled? After enabling that feature flag, running amplify update function should generate a correct policy

edwardfoyle avatar Apr 06 '22 21:04 edwardfoyle

@gspeicher could you confirm how you made the following change?

Restoring the original Resolver CloudFormation template and redeploying fixes the resolver so that the call to GetProfile works again.

I'm facing similar authentication issues.

Lorenzohidalgo avatar Apr 07 '22 07:04 Lorenzohidalgo

@edwardfoyle I agree, #10141 looks like the same underlying issue, although I could argue that this one is the original so that one is the duplicate. ;-) I did try setting the appSync.generateGraphQLPermissions feature flag in cli.json but it has no impact whatsoever on the generated code, i.e. git diff reports no changes after stepping through amplify function update again. So perhaps as a separate but related issue it would appear that the feature flag is not being correctly ingested from that file, but I would also argue that if you are using the legacy setting, it should continue to behave as before rather than breaking existing projects by stripping the ability to read from the API unless create privileges are also granted.

@Lorenzohidalgo I had a working version of the template in source control, so I reverted to that. You may want to try granting create privileges for the function to access the API resource as a temporary workaround until this issue gets sorted out.

gspeicher avatar Apr 07 '22 10:04 gspeicher

Thanks, @gspeicher, would you be able to share an extract of your cloud formation template where you grant access to the API? (or is it the one referenced in the issue description?)

I've granted all CRUD permissions to my lambda function (since they are needed) and I'm still getting the authentication error.

I've also compared the cloud formation template with a previously created lambda function (that is working correctly) and could not find some relevant differences.

Lorenzohidalgo avatar Apr 07 '22 10:04 Lorenzohidalgo

@Lorenzohidalgo that's basically it shown above, except the second resource is cut off at the bottom where you see Fn::Join in the template. That resource is a duplicate of the first one except the last component of the join ("/*") is omitted. So the two resources more succinctly are arn:aws:appsync:region:accountId:apis/GraphQLAPIIdOutput/* and arn:aws:appsync:region:accountId:apis/GraphQLAPIIdOutput.

gspeicher avatar Apr 07 '22 11:04 gspeicher

Hey @gspeicher and @Lorenzohidalgo would y'all mind sharing the snippet from your Lambda that's making the call to AppSync?

josefaidt avatar Apr 15 '22 19:04 josefaidt

Hi @josefaidt, I've shared the snipped you are asking for and further details on #10141

Lorenzohidalgo avatar Apr 16 '22 16:04 Lorenzohidalgo

Hey @gspeicher and @Lorenzohidalgo please take a look at the sample posted in #10141 to see if this shares some additional insight https://github.com/aws-amplify/amplify-cli/issues/10141#issuecomment-1112435364

josefaidt avatar Apr 29 '22 21:04 josefaidt

@josefaidt, my results are different than what you describe in that comment, and are still unchanged from my original issue submission. I just upgraded to the latest Amplify CLI and stepped through the process to update resource access permissions on the function and remove create access to the API. This results in the exact same diff that I posted above.

To be clear, the only remaining policy statement related to App Sync is as follows:

            {
              "Effect": "Allow",
              "Action": [
                "appsync:Get*",
                "appsync:List*",
                "appsync:Update*",
                "appsync:Delete*"
              ],
              "Resource": [
                {
                  "Fn::Join": [
                    "",
                    [
                      "arn:aws:appsync:",
                      {
                        "Ref": "AWS::Region"
                      },
                      ":",
                      {
                        "Ref": "AWS::AccountId"
                      },
                      ":apis/",
                      {
                        "Ref": "apiartmatcherGraphQLAPIIdOutput"
                      }
                    ]
                  ]
                }
              ]
            },

It seems that there are two things going on here: (1) permissions are incorrect when the generateGraphQLPermissions feature flag is set to false and API access does not include create permissions; and (2) setting generateGraphQLPermissions to true has no effect in my project.

The contents of my cli.json is as follows. The generated policy above is unchanged regardless of the value or presence/absence of the generateGraphQLPermissions flag.

{
  "features": {
    "appSync": {
      "generateGraphQLPermissions": true,
    },
    "graphQLTransformer": {
      "addMissingOwnerFields": true,
      "validateTypeNameReservedWords": true,
      "useExperimentalPipelinedTransformer": false,
      "enableIterativeGSIUpdates": true
    },
    "frontend-ios": {
      "enableXcodeIntegration": true
    },
    "auth": {
      "enableCaseInsensitivity": true,
      "forceAliasAttributes": true,
      "useInclusiveTerminology": true,
      "breakCircularDependency": true
    },
    "codegen": {
      "useAppSyncModelgenPlugin": true
    }
  }
}

gspeicher avatar May 02 '22 13:05 gspeicher

Hey @gspeicher :wave: can you walk me through the process of updating your function resource with the appropriate permissions? I'm seeing:

  1. with generategraphqlpermissions set to false, run amplify update function
  2. select function and modify the resource access permissions
  3. observe we are prompted for API CRUD operations
    > amplify update function
    ? Select which capability you want to update: Lambda function (serverless function)
    ? Select the Lambda function you want to update listusers
    General information
    - Name: listusers
    - Runtime: nodejs
    
    Resource access permission
    - 9966 (Mutation)
    
    Scheduled recurring invocation
    - Not configured
    
    Lambda layers
    - Not configured
    
    Environment variables:
    - Not configured
    
    Secrets configuration
    - Not configured
    
    ? Which setting do you want to update? Resource access permissions
    ? Select the categories you want this function to have access to. api
    ? Api has 2 resources in this project. Select the one you would like your Lambda to 
    access 9966
    ? Select the operations you want to permit on 9966 (Press <space> to select, <a> to 
    toggle all, <i> to invert selection)
    ◯ create
    ◯ read
    ◯ update
    ❯◯ delete
    
  4. ctrl+c/cancel the workflow
  5. set generategraphqlpermissions to true
  6. run amplify update function, select function and modify the resource access permissions
  7. observe we are prompted for GraphQL operations
    > amplify update function
    ? Select which capability you want to update: Lambda function (serverless function)
    ? Select the Lambda function you want to update listusers
    General information
    - Name: listusers
    - Runtime: nodejs
    
    Resource access permission
    - 9966 (Mutation)
    
    Scheduled recurring invocation
    - Not configured
    
    Lambda layers
    - Not configured
    
    Environment variables:
    - Not configured
    
    Secrets configuration
    - Not configured
    
    ? Which setting do you want to update? Resource access permissions
    ? Select the categories you want this function to have access to. api
    ? Api has 2 resources in this project. Select the one you would like your Lambda to 
    access 9966
    ? Select the operations you want to permit on 9966 (Press <space> to select, <a> to 
    toggle all, <i> to invert selection)
    ◯ Query
    ❯◉ Mutation
    ◯ Subscription
    

After enabling the feature flag we are able to step through the function's update which should modify the function's CloudFormation template to use the appsync:GraphQL permissions:

"AmplifyResourcesPolicy": {
  "DependsOn": [
    "LambdaExecutionRole"
  ],
  "Type": "AWS::IAM::Policy",
  "Properties": {
    "PolicyName": "amplify-lambda-execution-policy",
    "Roles": [
      {
        "Ref": "LambdaExecutionRole"
      }
    ],
    "PolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "appsync:GraphQL"
          ],
          "Resource": [
            {
              "Fn::Join": [
                "",
                [
                  "arn:aws:appsync:",
                  {
                    "Ref": "AWS::Region"
                  },
                  ":",
                  {
                    "Ref": "AWS::AccountId"
                  },
                  ":apis/",
                  {
                    "Ref": "api9966GraphQLAPIIdOutput"
                  },
                  "/types/Mutation/*"
                ]
              ]
            }
          ]
        }
      ]
    }
  }
}

josefaidt avatar May 04 '22 16:05 josefaidt

@josefaidt, when I run amplify update function it makes no difference whether generategraphqlpermissions is set to true or false (or omitted) in cli.json. In the last step of your terminal output, it prompts me for create/read/update/delete rather than Query/Mutation/Subscription either way.

gspeicher avatar May 05 '22 19:05 gspeicher

Hey @gspeicher thank you for clarifying! Are you in our Discord by chance? I'd like to hop on a quick call to take a further look at the issue if that's okay

josefaidt avatar May 05 '22 21:05 josefaidt

@josefaidt, I just joined Discord with username gspeicher

gspeicher avatar May 09 '22 12:05 gspeicher

Hey @gspeicher please join our community server at https://discord.gg/invite/amplify and from we can set up a DM

josefaidt avatar May 09 '22 15:05 josefaidt

Did that too. Is there something else I need to do before you can find me there?

gspeicher avatar May 09 '22 16:05 gspeicher

@gspeicher I found you! reached out 🙂

josefaidt avatar May 09 '22 16:05 josefaidt

Hey @gspeicher thanks again for hopping on a call with me! It was really helpful seeing the issue first-hand, and from our call we found a few issues:

  • generategraphqlpermissions does in fact not have an effect on the prompts in amplify update function
  • cli.json was several versions behind what was found in #current-cloud-backend
    • several feature flags did not match what we had locally, namely generategraphqlpermissions and useappsyncmodelgenplugin, which was assumed to be the culprit
    • after updating both copies of cli.json and reuploading the current-cloud-backend zip, we are still experiencing issues with updating function permissions to generate appsync:GraphQL permissions and the CLI continues to prompt for CRUD
    • pushing with amplify push --force did not update the cli.json file in our deployment bucket
    • pulling with amplify pull in a locally initialized project does not update the cli.json file with what is stored in our deployment bucket
  • lastly "failed to create models in the cloud: Modelgen creation failed", which led us to UnrecognizedClientException: The security token included in the request is invalid at Object

josefaidt avatar May 10 '22 17:05 josefaidt

Hey @gspeicher :wave: I wanted to follow up on this one as I have not been able to successfully reproduce this issue. Have you continuously experienced this?

josefaidt avatar Jun 22 '22 18:06 josefaidt

The issue is still unchanged for me, @josefaidt. I just ran amplify update function using Amplify CLI 9.0.0 and updated the resolver function to remove create permission from the API resource. The resulting diff is similar to the original one I posted above. I pushed that update to a non-production backend and the result is the same: the resolver function can no longer query the API after I've removed create permissions. Cloudwatch logs show the error:

"Error: GraphQL Errors[GetProfile]: [{\"errorType\":\"UnauthorizedException\",\"message\":\"Permission denied\"}]",

So the resolver is configured to allow read, update, and delete but the reads are still failing without create. Again, I believe the reason here is that the generated CF template is incorrect; it is missing the resource arn:aws:appsync:region:accountId:apis/GraphQLAPIIdOutput/* (it only contains the one without the trailing *).

gspeicher avatar Jun 24 '22 14:06 gspeicher

Hey @gspeicher does amplify env checkout <current-env-name> resolve the permissions/feature flag issue where amplify update function will prompt us for Query, Mutation, Subscription? For the modelgen issue would you mind filing here? https://github.com/aws-amplify/amplify-adminui

josefaidt avatar Jun 24 '22 17:06 josefaidt

Negative @josefaidt, doing a fresh (re)checkout doesn't change the situation at all.

gspeicher avatar Jun 28 '22 14:06 gspeicher

Hey @gspeicher apologies for the delay here!! Are you still experiencing this with the latest version of the CLI (9.1.0)? I have not been able to successfully reproduce this issue.

josefaidt avatar Jul 15 '22 19:07 josefaidt

@josefaidt just confirmed no change with CLI 9.2.1

gspeicher avatar Aug 15 '22 17:08 gspeicher

Not sure if this is related at all, but I now get the following prompt on every single push. I mention this because at one point it appeared there might be a problem with amplify CLI reading cli.json settings correctly, and I would kind of expect that this new prompt would be silenced via cli.json after responding in the affirmative followed by a successful deployment.

✔ This version of Amplify CLI introduces additional security enhancements for your GraphQL API. The changes are applied automatically with this deployment. This change won't impact your client code. Continue? (Y/n)

gspeicher avatar Aug 16 '22 16:08 gspeicher

Hey @gspeicher apologies for the delay here, I'm wondering if there may be a conflict in the file or an invalid combination of flags causing this issue. Below is a copy of a cli.json file from a fresh project, can you copy this over to your project, and attempt updating your functions with amplify update function to regenerate permissions in the template? Does this remove the CRUD permissions in favor of the GraphQL operations?

{
  "features": {
    "graphqltransformer": {
      "addmissingownerfields": true,
      "improvepluralization": false,
      "validatetypenamereservedwords": true,
      "useexperimentalpipelinedtransformer": true,
      "enableiterativegsiupdates": true,
      "secondarykeyasgsi": true,
      "skipoverridemutationinputtypes": true,
      "transformerversion": 2,
      "suppressschemamigrationprompt": true,
      "securityenhancementnotification": false,
      "showfieldauthnotification": false,
      "usesubusernamefordefaultidentityclaim": true,
      "usefieldnameforprimarykeyconnectionfield": false,
      "enableautoindexquerynames": false,
      "respectprimarykeyattributesonconnectionfield": false,
      "shoulddeepmergedirectiveconfigdefaults": false,
      "populateownerfieldforstaticgroupauth": false
    },
    "frontend-ios": {
      "enablexcodeintegration": true
    },
    "auth": {
      "enablecaseinsensitivity": true,
      "useinclusiveterminology": true,
      "breakcirculardependency": true,
      "forcealiasattributes": false,
      "useenabledmfas": true
    },
    "codegen": {
      "useappsyncmodelgenplugin": true,
      "usedocsgeneratorplugin": true,
      "usetypesgeneratorplugin": true,
      "cleangeneratedmodelsdirectory": true,
      "retaincasestyle": true,
      "addtimestampfields": true,
      "handlelistnullabilitytransparently": true,
      "emitauthprovider": true,
      "generateindexrules": true,
      "enabledartnullsafety": true
    },
    "appsync": {
      "generategraphqlpermissions": true
    },
    "latestregionsupport": {
      "pinpoint": 1,
      "translate": 1,
      "transcribe": 1,
      "rekognition": 1,
      "textract": 1,
      "comprehend": 1
    },
    "project": {
      "overrides": true
    }
  },
  "debug": {
    "shareProjectConfig": true
  }
}

josefaidt avatar Sep 22 '22 20:09 josefaidt

@josefaidt unfortunately this results in no change in behavior of the CLI or the contents of the generated files at all.

Could it possibly be related to the fact that we have multiple auth providers configured for our API? We are using userPools as the primary provider for users logged into our mobile app, and iam as a secondary provider for connections from Lambda functions.

gspeicher avatar Sep 23 '22 11:09 gspeicher

Hey @gspeicher I don't believe the auth modes would impact this, and to confirm I've added multiple auth mechanisms to my GraphQL API to no avail. I'm only able to reproduce the CRUD prompt by setting that flag to false or by removing it entirely. I'm going to mark this as "not-reproducible" for now, however I am interested in getting you unblocked. As a few follow-up questions:

  • do you experience this issue in a separate project?
  • are there any oddities associated with the file's ownership or permissions? Are you able to delete and recreate this file with any change to the CRUD prompt?
  • if you pull this same project into a separate directory does the CLI still fail to read the cli.json file and continue to prompt for CRUD?

josefaidt avatar Sep 30 '22 20:09 josefaidt

The file mode for cli.json is 0644 and owned by me so I don't think it is a permissions issue. I have a second checkout of this same project and get the exact same behavior regardless of which folder I run it in.

Hmm, I am running on a case-sensitive filesystem on MacOS, rather than the default case-insensitive volume. Could amplify-cli possibly be loading this file using something other than all lowercase on MacOS?

gspeicher avatar Sep 30 '22 21:09 gspeicher

Hmm, I am running on a case-sensitive filesystem on MacOS, rather than the default case-insensitive volume. Could amplify-cli possibly be loading this file using something other than all lowercase on MacOS?

That is a great callout, I'm not too sure how this would impact the CLI's functionality but worth investigating. If you load this project into an ec2 instance, initialize the project, run amplify update function, select a function, and make an empty update does it change the contents of the policy?

In another issue we noticed there was a duplication of a feature flag, do you also see a duplicated feature flag?

josefaidt avatar Oct 05 '22 21:10 josefaidt

I just cloned the project into a standard MacOS case-insensitive apfs filesystem and the behavior is the same. One thing I did notice (not sure how this eluded me until now) is that I have two copies of cli.json: one in my application root folder and another in the amplify folder. The one in the amplify folder is much more complete but also has conflicting settings with the one in the app root. Which location is correct?

gspeicher avatar Oct 12 '22 13:10 gspeicher