AWS Appsync - IAM permission - amazon-iam

I'm trying to protect my AWS Appsync API with IAM. All is fine on query level, but is it possible to restrict a client also on type level (fields of return type)?
This is a schema:
type Query {
getUserById(id: String): User
}
type User {
id: String!
email: String
firstName: String
lastName: String
}
And desired IAM permission:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:REG:ACCNO:apis/APIID/types/Query/fields/getUserById",
"arn:aws:appsync:REG:ACCNO:apis/APIID/types/User/fields/id",
"arn:aws:appsync:REG:ACCNO:apis/APIID/types/User/fields/email"
],
"Effect": "Allow"
}
]
}
I want a client to be able to get only user ID and email. Not firstName, nor lastName.
How to do it?
I cannot find any info in doc whether it is possible or not.
Based on this blog: https://aws.amazon.com/blogs/mobile/graphql-security-appsync-amplify/ it seems it's possible, but it's not explicit.

Only top level fields so everything that is defined inside Query, Mutation, Subscription types can be restricted using AWS IAM policies.
So id and email are not possible to be restricted using AWS IAM policies.

Related

List IAM Permissions for User/Role using AWS SDK

We're using AWS Cognito and AWS IAM to manage our users and their permissions to access certain resources in our static website built in JavaScript. Before we make certain requests using the AWS SDK we'd like to know what permissions the user has via their Role.
For example in AWS Console we can see the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cognito-idp:GetUser",
"iam:ListPoliciesGrantingServiceAccess",
"cognito-idp:ListUsers"
],
"Resource": "*"
}
]
}
Using the SDK we've found a method to get the Policies for the User/Role:
const iam = new AWS.IAM();
iam.listPoliciesGrantingServiceAccess({
Arn: arn,
ServiceNamespaces: [
"iam",
"cognito-idp"
]
}, (err, data) => {
if (err) {
console.log(err);
}
else {
console.log(data);
}
})
However this doesn't return the permissions these policies contain and only returns the actual policies themselves...
[
{
"ServiceNamespace":"iam",
"Policies":[
{
"PolicyName":"admin-policy",
"PolicyType":"INLINE",
"EntityType":"ROLE",
"EntityName":"Cognito_Auth_Role"
}
]
},
{
"ServiceNamespace":"cognito-idp",
"Policies":[
{
"PolicyName":"admin-policy",
"PolicyType":"INLINE",
"EntityType":"ROLE",
"EntityName":"Cognito_Auth_Role"
}
]
}
We haven't been able to find any methods that get permissions for a policy... How can we access this information for a given User/Role?
Here are the relevant SDK v3 methods. For the above example, you would use GetRolePolicy or ListRolePolicies:
PolicyType: INLINE
Get[User|Role|Group]PolicyCommand: Retrieves the specified inline policy document that is embedded in the specified entity.
List[User|Role|Group]PoliciesCommand: Lists the names of the inline policies embedded in the specified entity.
PolicyType: MANAGED
GetPolicyCommand to get the default version and GetPolicyVersionCommand to retrieve the policy document.
ListAttached[User|Role|Group]PoliciesCommand: Lists all managed policies that are attached to the specified IAM entity.

AWS cognito identity pool ABAC how to map custom multi-valued attributes?

Example open id token from the identity provider (Cognito user pool in this example):
{
"cognito:groups": [
"testers",
"admins",
],
"email_verified": false,
...
}
I want to use ABAC, like the example given here: https://docs.aws.amazon.com/cognito/latest/developerguide/using-attributes-for-access-control-policy-example.html
So that I can add policy statements to the role associated with the Cognito identity pool with conditions, example:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:eu-west-1:123456789012:dbuser:cluster-teapot/db-user",
"Effect": "Allow",
"Condition": {
"ForAnyValue:StringLike": {
"aws:PrincipalTag/groups": "admins"
}
}
}
]
}
It works like a charm if you use single valued custom claims, like the "email_verified" above, but if I try to map array-valued claims like "cognito:groups"
aws cognito-identity get-credentials-for-identity...
fails with: Invalid identity pool configuration. Check assigned IAM roles for this pool.
This is not a problem with trust since its tried and tested with single-valued claims, so I am wondering if someone knows if the syntax is wrong, or if this is a missing feature?

Public queries and mutations (no authentication)

The documentation says there are 3 ways we can authorise an application to interact with the API, but it doesn't look like there is a way of having a public endpoint.
For example, if I want anyone to query a list of todos, but only authenticated users can add a todo to that list, how can I achieve this?
Or if I want to allow anyone to do a schema introspection, but restrict all other queries to authenticated users, is it possible?
I'm using cognito for authentication. I noticed there is a AppId client regex field that says (Optional) Type a regular expression to allow or block requests to this API. but I can't find any example unfortunately. Maybe this is what I'm looking for?
Thanks
Julien
There are couple of ways in which you can do this based on Authentication mechanism.
Say you are using Cognito Identity and using AWS IAM flow for authentication. Then you would have 2 policies one for Authenticated User and One for Unauthenticated User.
Given a GraphQL Schema
schema{
query:Query
mutation:Mutation
}
type Query{
listTodo(count:Int, paginationToken:String):[TodoConnection];
}
type Mutation{
addTodo(input:TodoInput):Todo
}
Your Unauthenticated policy would look something like
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-west-2:<account-id>:apis/<api-id>/types/Query/fields/listTodo",
//-> below is for schema introspection
"arn:aws:appsync:us-west-2:<account-id>:apis/<api-id>/types/Query/fields/__schema"
]
]
}
}
Your authenticated user policy would look like
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-west-2:<account-id>:apis/<api-id>/types/Mutation/fields/addTodo",
"arn:aws:appsync:us-west-2:<account-id>:apis/<api-id>/types/Query/fields/listTodo",
//-> below is for schema introspection
"arn:aws:appsync:us-west-2:<account-id>:apis/<api-id>/types/Query/fields/__schema"
]
]
}
}
If you are using JWT Tokens then you will have to associate each Cognito User Pool User with a Group (like "Admin", "Users" etc). You then will have to associate each of the query/mutation with the Cognito Groups that can perform the operation using AWS AppSync auth directives. To do you you will only need to update the schema like below:
schema{
query:Query
mutation:Mutation
}
type Query{
listTodo(count:Int, paginationToken:String):[TodoConnection];
#aws_auth(cognito_groups:["Users", "Admin"])
}
type Mutation{
addTodo(input:TodoInput):Todo
#aws_auth(cognito_groups:["Admin"])
}
API Key based authentication, its not possible to have control over the operation.

AWS Cognito Groups and AWS Api Gateway

I am starting with serverless on AWS, and I am using AWS Cognito for user authentication and authorization. For what I saw on the documentation and examples out there, you can make groups for allowing certain users to be able to use an Api Gateway endpoint, attaching a role and a policy to that group. I try this, and then made a simple client and try with two different users, and both are able to get a 200 status code instead of one of them getting that it is unauthorize. For creating the role I went to IAM, create role, role for identity provider access, grant access to web identity providers, and then I choose Amazon Cognito and choose my user pool of Cognito.
Trust Relationship:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1_8TAUVKbGP"
}
}
}
]
}
Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"my-arn-resourse-from-api-gateway"
]
}
]
}
Then I assigned this role to my admin group and add a user to that group, which should allow access to that Api Gateway resource by attaching that policy to the user when it signs in. But when I try with a user not in that group it still works. By the way, on my Api Gateway resource in the request I put for authorization my cognito pool.
Thanks very much!
Ok finally I got it to work perfectly! The problem was in my IAM Role, in the trust relationship document
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "identity_pool_id"
},
In that part instead of using my Identity Pool Id, I was using my User Pool Id. Once that was fixed, I got the credentials back, and tried in Postman using those credentials and it worked perfectly. Then I change the same user to another role, and access was denied as planned! The most importante part for using role authorization on an easy way, is as agent420 said to use AWS_IAM method for securing the api, then the rest is handle by aws.
You would need to use the AWS_IAM method instead for your use case. Also, in this case all your API requests would need to be SIGv4 signed. You can use Postman (chrome extension) for testing. It includes an option for AWS credentials.
I try what you said! I think I am in the right way but AWS.config.credentials is returning sessionToken, and accessKeyId both with null. This is the code I am using:
let poolData = {
UserPoolId : 'my_user_pool_id',
ClientId : 'my_app_client_id'
};
let authenticationData = {
Username : 'username',
Password : 'password',
};
let userPool = new CognitoUserPool(poolData);
let userData = {
Username : 'username',
Pool : userPool
};
let authenticationDetails = new AuthenticationDetails(authenticationData);
let cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
console.log(result);
AWS.config.region = 'my_region';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : 'my_identity_pool_id',
Logins : {
'cognito-idp.my_region.amazonaws.com/my_user_pool_id' : result.getIdToken().getJwtToken()
}
});
console.log(AWS.config.credentials);
},
onFailure: (error) => {
}
});
The result from the authenticateUser returns the expected tokens. The problem I think is when retrieving the CognitoIdentityCredentials.
Thanks very much!

How to use DynamoDB fine grained access control with Cognito User Pools?

I'm having trouble understanding how to use fine-grained access control on DynamoDB when logged in using Cognito User Pools. I've followed the docs and googled around, but for some reason I can't seem to get it working.
My AWS setup is listed below. If I remove the condition in the role policy, I can get and put items no problem, so it seems likely that the condition is the problem. But I can't figure out how or where to debug policies that depend on authenticated identities - what variables are available, what are their values, etc etc.
Any help would be greatly appreciated!
DynamoDB table
Table name: documents
Primary partition key: userID (String)
Primary sort key: docID (String)
DynamoDB example row
{
"attributes": {},
"docID": "0f332745-f749-4b1a-b26d-4593959e9847",
"lastModifiedNumeric": 1470175027561,
"lastModifiedText": "Wed Aug 03 2016 07:57:07 GMT+1000 (AEST)",
"type": "documents",
"userID": "4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb"
}
Cognito User Pool User
User Status: Enabled / Confirmed
MFA Status: Disabled
sub: 4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb
email_verified: true
Role policy for "RoleName"
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": [
"arn:aws:dynamodb:ap-southeast-2:NUMBER:table/documents"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}
Login information returned from cognitoUser.getUserAttributes()
attribute sub has value 4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb
attribute email_verified has value true
attribute email has value ****#****com
Error message
Code: "AccessDeniedException"
Message: User: arn:aws:sts::NUMBER:assumed-role/ROLE_NAME/CognitoIdentityCredentials is not authorized to perform: dynamodb:GetItem on resource: arn:aws:dynamodb:ap-southeast-2:NUMBER:table/documents
The policy variable "${cognito-identity.amazonaws.com:sub}" is not the user sub which you get from Cognito user pools. It is in fact the identity id of a user which is generated by the Cognito Federated Identity service when you federate a user from Cognito User Pools with Federated identity service.
Since, the value in "${cognito-identity.amazonaws.com:sub}" never matches what you have in your DynamoDB row, it fails with AccessDenied. For this to work, the userId in your Dynamo entry should actually be the identity id, not sub. Currently, there is no direct link between IAM policy variables and Cognito User Pools service.
Here are some doc links which might help.
1. IAM roles with Cognito Federated Identity Service
2. Integrating User Pools with Cognito Federated Identity Service