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.
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 automatically 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. (Note: I also allow unauthenticated access to other graphql queries, which may be a factor)
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": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": {
"Ref": "(auth...IdentityPoolId resource id from top of file)"
}
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
}
}
Be sure to grant access for this custom resource to auth.
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.
Unfortunately the amplify instructions related to this don't mention this.