Amplify-cli: Distinguishing IAM between authenticated users and lambda resolver

Created on 19 Nov 2019  路  12Comments  路  Source: aws-amplify/amplify-cli

Which Category is your question related to?
Auth, GraphQL Transform

Amplify CLI Version
3.17.0

What AWS Services are you utilizing?
Cognito, AppSync, Lambda

Provide additional details e.g. code snippets
When calling GraphQL API from a lambda function resolver as described in the docs:
https://aws-amplify.github.io/docs/cli-toolchain/quickstart#graphql-from-lambda
it is necessary to grant the corresponding permission on GraphQL types to be queried/mutated, for example (as in the docs):

{ allow: private, provider: iam, operations: [read, create] }

My point in using the lambda resolver was to mutate a type I do not want every authenticated user to have update-permission for. However, with an identity pool in use (needed for multi-auth, allowing some unauthenticated queries on different types as well as user image uploads etc.), essentially any authenticated user having a valid JWT token is granted the same level access to the GraphQL type as the lambda function. GraphQL mutation call with authMode: 'AWS_IAM' succeeds on the client-side from any authenticated user.

Is there any way to separate out the private IAM-roles from a lambda resolver and from authenticated users? How could I annotate the GraphQL type to accomplish that?

Most helpful comment

Is order matters in how you put the rules? For example, are these two equivalent?

type YourTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: private, provider: iam, operations: [read, create, update, delete] },    
    { allow: groups, groups: ["users"], operations: [read] }
  ])
{

vs

type YourTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: groups, groups: ["users"], operations: [read] },
    { allow: private, provider: iam, operations: [read, create, update, delete] }
  ])
{

The reason I asked is because I am still trying to understand how rules are applied. What happens if something belongs to the group Users and also has iam role?

Is there a potential security hazard on the work around you propose? Upon creating an account, the user would be automatically granted IAM role. There is a small latency before Lamda triggers and user is added to group. Someone might still be able to takes advantage of this short period of time to perform some actions.

AWS amplify should have an easier way separating IAM role from Lamda and Cognito user pool!

All 12 comments

In this case I would recommend your authenticated users don't use IAM and instead use User Pools as their auth mechanism. You can then manage the IAM policy for the Lambda's execution role independently of this flow (granting it permissions to your AppSync resource as defined here) either by editing the policy in the AWS Console or using the CloudFormation produced by Amplify CLI if using a function in your current project.

@undefobj What would you suggest for allowing users to upload images?

Perhaps an alternative approach to distinguishing between IAM users and Lambda functions would be:

  • Keep IAM auth enabled to allow users to upload images to s3 etc.
  • Decorate any types/fields that you don't want users to access with something like:
    @auth(rules: [{ allow: groups, groups: ["ForbiddenGroup"] }])
    This will act as an explicit deny for any user.

  • Give specific permissions to lambda functions via IAM roles in order to access 'forbidden' types

Do you think this approach would work?

Also not sure but maybe instead of using the graphql transform @auth directive, instead use the appsync @aws_iam directive. I'm not certain but this may potentially prevent allow policies being added to the auth role (for authenticated users).

@multidis Let us know if you work something out!

In this case I would recommend your authenticated users don't use IAM and instead use User Pools as their auth mechanism.

@undefobj My users are using a User Pool as the default authentication mechanism. However to enable public read queries on some types and also to allow S3 image uploads I have to enable the Amplify CLI option

Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)

followed by "Allow unauthenticated logins".

My understanding is with this setup after a user is authenticated by the Cognito User Pool, they get a private IAM role as well. In fact, I noticed if there is e.g. an incorrect password attempt with the User Pool Auth.signIn, a 401 error from the Identity Pool URL follows right after that. In all cases it seems that the Identity Pool attempts to assign an IAM role to every User Pool authenticated user.

The situation probably requires either some distinction between private IAM auth statements for the users and for lambda, or perhaps some GraphQL resolver customization to allow lambda where users are forbidden.

It may also be worthwhile to warn Amplify multi-auth users in the auth transformer docs (and in the lambda example) about private IAM roles accessible to authenticated users.

@Jkap7788 Preventing User Pool based access to a given GraphQL type is not a problem (if I understood your suggestion correctly; @auth statements without the explicit provider specification default to User Pool). When a "usual" Amplify query is performed such as

await API.graphql(graphqlOperation(queryName, {
  input: { ... },
}));

it results in error (as it should) since the user is not allowed to perform the operation per the default Cognito auth-provider.

However, when the same operation is done asking IAM to be the auth provider:

await API.graphql({
  query: queryName,
  variables: { input: { ... } },
  authMode: 'AWS_IAM'
});

the operation is allowed. Thus, in essence, any time I allow lambda to perform an operation on a given type using the allow: private, provider: iam, ... statement, the same operation is allowed for any user with an Identity Pool issued token via the IAM mode.

Looking further in the AWS console at the IAM roles created by the Amplify CLI, I see:

  • ...-unauthRole (cognito-identity)
  • ...-authRole (cognito-identity)
  • ...-authRole-idp (lambda)

The cognito-identity provided roles reflect GraphQL schema @auth-annotations (provider: iam ones), while the last one (lambda) just unites permissions from the first two (or allows to assume one of those roles via iam:UpdateAssumeRolePolicy if I am reading the policy correctly). If I start tweaking the roles manually in the console they are going to be overwritten with the next amplify push.

Since we already grant the AppSync access to the lambda when we select the corresponding options in the amplify add function flow (in my case it is create, read, update), is there a way to actually allow lambda perform those queries without the allow: private, provider: iam instruction on the corresponding types of the GraphQL schema?

This seems to be solved by requiring all users to be added to some Cognito group (e.g. call it "users" group); this is possible using the amplify auth flow prompts of the Amplify CLI.

Once users belong to a group, a separate group IAM role is configured that differs from auth and un-auth ones. That way, authenticated users querying GraphQL with authMode: 'AWS_IAM' no longer have the same role as lambda does, and allow: private, provider: iam ... does not apply to them.

Leaving the issue open for the Amplify Team to decide if they want to include a warning in the lambda example docs: if one just follows the tutorial and has an Identity Pool configured one may not realize they are opening up queries to Any authenticated user with allow: private, provider: iam ....

@multidis We have done a big refactor to our GraphQL transformer docs with more use-case based examples. Please take a look at them and provide us with some feedback. Thank you!

@kaustavghosh06 It seems that an explicit warning about this issue would be helpful for the "GraphQL from lambda" example in the docs. Please see suggested edit in this PR:
https://github.com/aws-amplify/docs/pull/1071

Once users belong to a group, a separate group IAM role is configured that differs from auth and un-auth ones. That way, authenticated users querying GraphQL with authMode: 'AWS_IAM' no longer have the same role as lambda does, and allow: private, provider: iam ... does not apply to them.

@multidis Can you provide example of your schema to allow lamba function access but restrict user group. I'm trying to restrict access where public (unauth-role) users can read, logged in (auth-role) users can read, admin can create\read\update\delete and my lamba function also create\read\update\delete. This is my auth roles look like:

@auth(
        rules: [
            { allow: public, provider: iam, operations: [read] }
            { allow: groups, groups: ["Admin"] }
        ]
    )

@shivangp in my case an example type is:

type MyTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: groups, groups: ["admin"], operations: [read, create, update, delete] },
    { allow: groups, groups: ["users"], operations: [read] },
    { allow: owner, ownerField: "owner", operations: [read, create, update, delete] },
    { allow: private, provider: iam, operations: [read, update, delete] },
    { allow: public, provider: iam, operations: [read] }
  ])
{
    id: ID!
    owner: String
    ...
}

For your requirements, my guess would be:

public (unauth-role) users can read

{ allow: public, provider: iam, operations: [read] }

logged in (auth-role) users can read

{ allow: groups, groups: ["users"], operations: [read] },

(once a Cognito trigger is setup to add all users to the default group (called "users" in my example), they no longer have auth-role, they have a separate group-based role (at least that was my observation so far). The auth-role remains "purely" for lambda (private IAM) - the key point for separating lambdas and logged in users.

Admin can create\read\update\delete

{ allow: groups, groups: ["Admin"], operations: [read, create, update, delete] }

my lamba function also create\read\update\delete

{ allow: private, provider: iam, operations: [read, create, update, delete] }

Putting it all together:

type YourTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: groups, groups: ["Admin"], operations: [read, create, update, delete] },
    { allow: private, provider: iam, operations: [read, create, update, delete] },    
    { allow: groups, groups: ["users"], operations: [read] },
    { allow: public, provider: iam, operations: [read] }
  ])
{
    id: ID!
    ...
}

Thanks @multidis the does help clarify things !

Is order matters in how you put the rules? For example, are these two equivalent?

type YourTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: private, provider: iam, operations: [read, create, update, delete] },    
    { allow: groups, groups: ["users"], operations: [read] }
  ])
{

vs

type YourTypeWithDynamoTable
  @model
  @auth(rules: [
    { allow: groups, groups: ["users"], operations: [read] },
    { allow: private, provider: iam, operations: [read, create, update, delete] }
  ])
{

The reason I asked is because I am still trying to understand how rules are applied. What happens if something belongs to the group Users and also has iam role?

Is there a potential security hazard on the work around you propose? Upon creating an account, the user would be automatically granted IAM role. There is a small latency before Lamda triggers and user is added to group. Someone might still be able to takes advantage of this short period of time to perform some actions.

AWS amplify should have an easier way separating IAM role from Lamda and Cognito user pool!

Was this page helpful?
0 / 5 - 0 ratings