aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
89 stars 77 forks source link

`amplify mock api` does not work with IAM auth rules, always Unauthorized #140

Open skryshi opened 2 years ago

skryshi commented 2 years ago

Before opening, please confirm:

How did you install the Amplify CLI?

npm

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

v16.6.1

Amplify CLI Version

7.5.2

What operating system are you using?

Mac

Amplify Categories

api

Amplify Commands

Not applicable

Describe the bug

Queries and mutations with IAM @auth are failing with Not Authorized to access exception.

I created a fresh project to test out the new Amplify release 7.5.2. Used this graphQL model:

type Test
  @model
  @auth(rules: [
    { provider: iam, allow: private },
    { provider: userPools, allow: private, operations: [read] }
  ])
{
  id: ID!

  writeable: String
    @auth(rules: [
      { provider: iam, allow: private },
      { provider: userPools, allow: private, operations: [read, update] }
    ])

  readOnly: String

  hidden: String
    @auth(rules: [
      { provider: iam, allow: private },
    ])
}

Using AWS AppSync console, run this query:

query MyQuery {
  listTests {
    items {
      id
    }
  }
  getTest(id: "1") {
    id
  }
}

When I run this query as a UserPool user, I get back some data. When I run this query as IAM, I get this exception:

{
  "data": {
    "listTests": null,
    "getTest": null
  },
  "errors": [
    {
      "path": [
        "getTest"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 7,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access getTest on type Test"
    },
    {
      "path": [
        "listTests"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access listTests on type ModelTestConnection"
    }
  ]
}

As a sanity check, I have verified that IAM should have full access to queries:

Screen Shot 2021-11-26 at 8 23 15 PM

The above unauthorized exception also happens for IAM createTest() mutation. (And might also happen for other mutations and subscriptions, I haven't tested everything).

Expected behavior

IAM queries and mutations succeed, returning either some date or null if there is no data.

Reproduction steps

  1. amplify init
  2. amplify add auth
  3. amplify add api
    1. Set UserPool as primary auth and IAM as secondary auth.
  4. use graphQL Test model as above
  5. amplify push
  6. create a test UserPool user in Cognito
  7. open AWS AppSync console and run the sample query as above

GraphQL schema(s)

```graphql # Put schemas below this line ```

Log output

``` # Put your logs below this line ```

Additional information

No response

renebrandel commented 2 years ago

hi @skryshi - thanks for the issue! The IAM-based "@auth" rules are scoped down to only match the auth/unauth roles that Amplify generate. This exact-match allows us to perform field-level authorization based on IAM.

The AppSync console will use the IAM user that your AWS Account is signed in as and therefore won't match the auth/unauth role. To work around this, we provide a mechanism called "AdminRoles" but these have special access privileges that are scoped based on their IAM policy, not @auth rule. I just added some more documentation to make this clearer https://github.com/aws-amplify/docs/pull/3828/files

localsecurity-emily commented 2 years ago

@renebrandel I am having this issue also.

I have a schema like:

type IncidentNote
  @model(timestamps: { createdAt: "createdAt" })
  @key(
    name: "notesByIncident"
    fields: ["incidentId", "createdAt"]
    queryField: "notesByIncident"
  )
  @auth(
    rules: [
      {
        allow: owner
        ownerField: "monitoringCenterId"
        operations: [read, create]
        identityClaim: "custom:monitoringCenterId"
      }
      { allow: private, provider: iam, operations: [create] }
    ]
  ) {
  id: ID!
  incidentId: ID!
  note: String!
  monitoringCenterId: ID!
  modifiedBy: String!
  writtenBy: String
  createdAt: AWSDateTime!
  shareExternal: Boolean
}

Prior to upgrading to 7.5.2 I was able to create entities using the AWSAppSyncClient with auth type AWS_IAM, no I am not.

renebrandel commented 2 years ago

@localsecurity-emily which IAM role are you using to sign the requests?

localsecurity-emily commented 2 years ago

How would I check that? I should have mentioned it's only an issue when I am mocking amplify locally. Prior to the upgrade I didn't have any issue, now I can't make requests using the IAM credentials, I just end up with:

Error: GraphQL error: undefined
InvalidClientTokenId: The security token included in the request is invalid.

The client is initialized as:

graphqlClient = new appsync.AWSAppSyncClient({
    url: process.env.API_LOCALSECURITYGRAPHQL_GRAPHQLAPIENDPOINTOUTPUT,
    region: process.env.AWS_REGION,
    auth: {
      type: "AWS_IAM",
      credentials: {
        accessKeyId: "fake",
        secretAccessKey: "fake",
      },
    },
    disableOffline: true,
  });

process.env.API_LOCALSECURITYGRAPHQL_GRAPHQLAPIENDPOINTOUTPUT = http://192.168.0.244:20002/graphql process.env.AWS_REGION = us-fake-1

skryshi commented 2 years ago

@renebrandel Thank you, that worked.

renebrandel commented 2 years ago

@localsecurity-emily - I see. I've renamed the task to make it clearer for the engineering team to pick it up.

lbrinker-1 commented 2 years ago

+1 on this one, I've got the same issue - not being able to use IAM authorization when mocking locally, both lambda and the local graphiQL interface since upgrading Amplify CLI version. I back-tracked, and it seems to stop working for me from CLI version 6.4.0 (did a big upgrade from an earlier version).

For local graphiQL, I'm getting the following back

{
  "data": {
    "getUser": null
  },
  "errors": [
    {}
  ]
}

Hosted AppSync GraphiQL interface didn't work for me until I added the console role to the 'custom-roles.json' file like @renebrandel mentioned (not an ideal solution imo, but it worked). Could it be that this is also related to an issue with the role-names which are now used for authorization? I.e. no local roles defined during mocking?

yuth commented 2 years ago

The mock server now supports IAM, and when the request is signed with AccessKey ASIAVJKIAM-AuthRole, the request gets authrole and any other AccessKey is treated as user with UnAuth role. This change was released in Amplify CLI v7.6.3

lbrinker-1 commented 2 years ago

Thanks @yuth!

Can confirm that it works for me now by changing the following:

Lambda

const AWS = require('aws-sdk')
const AWSAppSyncClient = require('aws-appsync').default;
require('cross-fetch/polyfill');

const mockCredentials = { 
    "accessKeyId": "ASIAVJKIAM-AuthRole", 
    "secretAccessKey": "fake"
}

const credentials = AWS.config.credentials || mockCredentials

exports.appsync = new AWSAppSyncClient({
    url: process.env.API_<APINAME>_GRAPHQLAPIENDPOINTOUTPUT,
    region: process.env.REGION,
    auth: {
        type: 'AWS_IAM',
        credentials: credentials,
    },
    disableOffline: true,
});

And local graphiQL by setting role to Auth in Update auth: image

eettaa commented 2 years ago

@yuth @renebrandel thank you so much for your work on this bug :) I have two related questions, I hope it's appropriate to add them here:


The mock server now supports IAM, and when the request is signed with AccessKey ASIAVJKIAM-AuthRole, the request gets authrole and any other AccessKey is treated as user with UnAuth role.

AWS documentation now discusses special 'adminRoles' (thanks @renebrandel for adding that documentation!). Is there any way to mock these roles? I have a setup where a lambda function calls my api and is granted access via adminRole logic. I can get this to run locally in a very hacky way: 1) I mock the signer credentials with the ASIAVJKIAM-AuthRole key and 2) I have to update my schema with an @auth([{allow:private, provider:iam}]) annotation. I need 2) because my mock environment is using a non-adminRole to grant this permission. But 2) is super undesirable because if it is accidentally submitted it opens my entire schema to my users. Is there any way to mock this lamda as running as an adminRole and have the mock api recognize it? Even a hack like "keys like ASIAVJKIAM-mock-\<roleName> are treated as associated with that roleName" would be helpful.


The example above uses appsync.AWSAppSyncClient with configured credentials... does that work for issuing actual data get/create/etc requests to GraphQL, or is that client just meant for configuring AppSync? I ask because I could not find a "send graphql query" function in that module's documentation, and the amplify docs use AWS.Signers.v4 directly. It would be great if I'm just missing something in the docs and this client can be used to correctly sign everything :)

yuth commented 2 years ago

@eettaa what auth do you have in your schema for non mock version where you're using a non-admin role

eettaa commented 2 years ago

@yuth I use cognito pools-based auth for most of my schema. Something like

type TestInt @model @auth(rules: [{allow: owner}]) {
  id: ID!
  num: Int!
  owner: String!
}

The case I'm discussing above is that I also have a lambda function that I want to be able to update TestInt. So I run amplify update function > [lambda name] > (add api access)... This then results in adminRoles being added to the auth checks in the compiled VTL resolvers. But while running mock it appears impossible for the lambda function to actually impersonate its prod auth role.

rafaelfaria commented 2 years ago

+1 having the same problem. Used to work on previous transformer, but on v2 it stopped working, and I'm now unable to access my graphql apis through my lambda on my mock server. Literally having to code in the dark and push it to test in the cloud, which makes development really really slow.

if there was a way to sign the request with the auth role so we could "simulate" the cloud, that would be a step forward. Keep to know how you "hack it" so you could get it running, because that would at least help me to test it locally.

lbrinker-1 commented 2 years ago

The example above uses appsync.AWSAppSyncClient with configured credentials... does that work for issuing actual data get/create/etc requests to GraphQL, or is that client just meant for configuring AppSync? I ask because I could not find a "send graphql query" function in that module's documentation, and the amplify docs use AWS.Signers.v4 directly. It would be great if I'm just missing something in the docs and this client can be used to correctly sign everything :)

@eettaa Yes, this is used to do mutations / queries to GraphQL. The client will automatically sign the request. To give an example of how I used it:

const appsync = require('./graphql-client').appsync;
const gql = require('graphql-tag');

const getUserQuery = gql`
    query GetEmployee($id: ID!) {
        getEmployee(id: $id) {
            id
            email
            managerId
            createdAt
            updatedAt
            owner
            manager {
                id
                email
            }
        }
    }
`;

exports.getEmployeeById = async (id) => {
    return appsync
        .query({
            query: getUserQuery,
            fetchPolicy: 'no-cache',
            variables: {
                id: id,
            },
        })
        .then((res) => res)
        .catch((err) => err);
};

And then just run await getEmployeeById(dataSubjectId); in your lambda function

You can do the same with .mutation

@rafaelfaria have you tried my example above? I fixed this by using a specific set of mock credentials, which signs the request made by the graphQL client (in mock) as if it was done by an authenticated role.

const mockCredentials = { 
    "accessKeyId": "ASIAVJKIAM-AuthRole", 
    "secretAccessKey": "fake"
}
rafaelfaria commented 2 years ago

@lbrinker-1 I tried this and it worked 🎉


  const mockCredentials = {
    "accessKeyId": "ASIAVJKIAM-AuthRole",
    "secretAccessKey": "fake"
  }

  const credentials = endpoint === "localhost" ? mockCredentials : AWS.config.credentials

  const signer = new AWS.Signers.V4(req, 'appsync', true);
  signer.addAuthorization(credentials, AWS.util.date.getDate());
eettaa commented 2 years ago

Thanks @lbrinker-1 for discussion on the AppSync client, I'll try it out. From above:

AWS documentation now discusses special 'adminRoles' (thanks @renebrandel for adding that documentation!). Is there any way to mock these roles?

@yuth @renebrandel as I believe they are the Amplify folks on the thread- any guidance on that kind of IAM mocking?

rafaelfaria commented 2 years ago

So, I end up back to this post :).

From one side with the help of this discussion, I managed to fix the issue I was having trying to access Queries and Mutations from my lambda.

However, I have recently updated to the latest version (7.6.21 ) and now, I keep getting Unauthorised, even though my schema worked before and seems to be correct (see below).

In the mock amplify console, I am able to query my data using the IAM - Auth, however, when I use it on my website, I can't test it locally, doesn't matter what I do, still comes back as Unauthorized.

I have tried adding a user with Administrator policy attached to it, to the custom-roles.json and adding this file to the root of the API. Despite being able to see the user I added in the resolvers, I still keep getting Unauthorised. I really don't know what to try anymore.

Any help would be appreciated.


My resolver has the "rafael-iam", the user I created with Admin policy attached to it.

my custom-roles.json looks like:

{
  "adminRoleNames": ["rafael-iam"]
}

and I can see it applying it on the resolvers

#set( $adminRoles = ["us-east-1_PFGnwOWuc_Full-access/CognitoIdentityCredentials","us-east-1_PFGnwOWuc_Manage-only/CognitoIdentityCredentials","lambdaNewsFeed-dev","rafael-iam"] )

This is my schema (used to work before). I have some one-to-many logic going on.

type Event @model
  @auth(
    rules: [
      { allow: private, operations: [read], provider: iam },
      { allow: groups, groups: ["Admin"] }
    ]
  )
  {
  id: ID!
  title: String!
  image: String
  description: String
  tokenAddress: String @index(name: "byEvent")
  token: Token @belongsTo(fields: ["tokenAddress"])
}

on my code, I call the get method as

  const { data } = (await API.graphql({
          query: getEvent,
          variables: { id: eventId },
          authMode: GRAPHQL_AUTH_MODE.AWS_IAM,
      })) as GetEventResult;

Looking forward to see if there is any ideas to try.

lbrinker-1 commented 2 years ago

@rafaelfaria can you try to stop the mock, delete the amplify/backend/api//resolvers folder, and then run mock again (so it re-generates the resolvers).

I've had this a couple of times in the past where I was getting a random unauthorised, and that fixed it for me. Didn't have the time to dive deep into the root cause, but might be worth trying that?

rafaelfaria commented 2 years ago

Hi @lbrinker-1 ,

That has happened to me before, so I have tried that a few times.

I kid you not, I think I have tried everything. My latest attempt is to literally delete amplify and redo everything again, which is not ideal but I am desperate. This used to work and now it just doesn't. I can't test anything that needs IAM.

So I rebuild the whole amplify project using the latest version (7.6.21). Still having problems :/. Then I did with (7.6.13) because this is the last one I remember updating, that would still work. However, no luck there either.

Any reason why custom-roles.json wouldn't work?

I am literally out of ideas :/

LaurensBrinker commented 2 years ago

@rafaelfaria Sorry, I missed your comment. Have you managed to fix your issue, or are you still encountering it?

Enzaik commented 2 years ago

@lbrinker-1 Im facing this issue but actually in AppSync console

LaurensBrinker commented 2 years ago

@Enzaik do you mean the local or remote GraphiQL interface? If it's remote - make sure to follow the steps that Rene mentioned above, where you add the appropriate IAM role/user to custom-roles.json. (documentation: https://docs.amplify.aws/cli/graphql/authorization-rules/#use-iam-authorization-within-the-appsync-console). If it's local, make sure to update the auth in the GraphiQL interface to use the 'Auth' role.

Enzaik commented 2 years ago

@LaurensBrinker in remote GraphiQL playground. And I did create custom-roles.json, with no success. Please let me know if a new issue needs to be open

rafaelfaria commented 2 years ago

@LaurensBrinker still happening. Can't really test my application by mocking it, I have to develop and push it every single time. Works fine when it's in my dev environment.

I haven't updated to the latest version (8) yet. Not sure if there is any fix there, but will try next.

jleskovar commented 2 years ago

Hi there - just stumbled across this issue. My backend contains a few different lambdas that access AppSync directly by using the "admin roles" functionality mentioned above. This all works well when deployed. However, I get unauthorised when trying to hit the amplify mock APIs locally. After a bit of digging I'm pretty sure it is because my models intentionally do not contain { allow: private, provider: iam }, as doing so would allow authenticated users access to my API models, which I definitely do not want. I can get things to work by using the ASIAVJKIAM-AuthRole access key but only if I also add { allow: private, provider: iam } to my models, which is undesirable.

The ideal solution would be to be able to specify a custom user ARN for accessing the mock APIs locally, which would enable me to fake my local requests as one of my allowed admin user ARNs

Any help / assistance appreciated

LaurensBrinker commented 2 years ago

After a bit of digging I'm pretty sure it is because my models intentionally do not contain { allow: private, provider: iam }, as doing so would allow authenticated users access to my API models, which I definitely do not want.

@jleskovar "Authenticated Users" in what sense? I'm using the same { allow: private, provider: iam } to give full access to my Lambda function, but I use {allow: owner}, which is setup to use Cognito as authentication type, to use only allow Cognito users access to their own items. Or e.g. {allow: private, provider: userPools, operations: [read]} to allow authenticated users Read access.

LaurensBrinker commented 2 years ago

@Enzaik / @jleskovar I'm not quite sure tbh, I think it might be best to create a separate issue, link this one, and dive deeper into the steps required to replicate. And then someone from the Amplify team can have a deeper look.

charlesfayal commented 2 years ago

This is an issue for us as well. Tried the mock credentials. mock API_KEY works but our schema authorization bars us from using it.

abstractalchemist commented 1 year ago

I'm also trying to get the local ( mocked ) version of the API to work. using

type Team @model @auth(rules : [{ allow: private, provider: iam}]) {
  id: ID!
  name: String!
  description: String
}

Not sure how to inject the mock auth based on the discussion above, at least through any of the exposed Amplify APIs.

jvaczcorporation commented 1 year ago

@rafaelfaria and @abstractalchemist I was able to test my lambda locally by creating an accessKey in IAM for my authorized user in custom-rules.json. With that in my request I put:

 credentials = {
                accessKeyId: "USER_ACCESS_KEY_ID",
                secretAccessKey: "USER_SECRET_ACCESS_KEY,
}
Itai-deepdub commented 1 year ago

Was the custom-role mock issue solved somehow - I think this will be a much better solution.

Hi there - just stumbled across this issue. My backend contains a few different lambdas that access AppSync directly by using the "admin roles" functionality mentioned above. This all works well when deployed. However, I get unauthorised when trying to hit the amplify mock APIs locally. After a bit of digging I'm pretty sure it is because my models intentionally do not contain { allow: private, provider: iam }, as doing so would allow authenticated users access to my API models, which I definitely do not want. I can get things to work by using the ASIAVJKIAM-AuthRole access key but only if I also add { allow: private, provider: iam } to my models, which is undesirable.

The ideal solution would be to be able to specify a custom user ARN for accessing the mock APIs locally, which would enable me to fake my local requests as one of my allowed admin user ARNs

Any help / assistance appreciated