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
Related
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.
I have a web app where users (logged in via Cognito with an ID Token JWT) can upload/download files from an S3. Users should only access S3 resources related to their organization. For that, I'm thinking of separating S3 path by organization:
"arn:aws:s3:::my_bucket/org1"
"arn:aws:s3:::my_bucket/org2"
"arn:aws:s3:::my_bucket/org3"
Question
How do you secure the S3 paths ("folders") so that users can only access resources for their org? I did some research, see options below - but is there an easier way? This seems like a fairly common use case. Thanks!
Options I've considered
EDIT: I initially thought of using JWT claims (#1 below). But it's dawning on me that AWS prefers the use of "Identity Pools" for this kind of thing. This makes sense because you can connect different IdP's (ex Auth0) to an Identiy Pool, and set policies using Identities (Identity Role -> Identity Policy). So I'm going to try that and report back if that works.
S3 Policy using JWT claims. Haven't found docs on using JWTs, so not sure if this is possible. Use the JWT claim as the path selector in the s3 policy. Each user has a custom claim called "organization".
User 1 has JWT with claim "organization: organization1"
User 2 has JWT with claim "organization: organization2"
etc.
Sample bucket policy [1] (not sure if syntax is correct)
{
"Version": "2012-10-17",
"Statement": [
{
"Action": ["s3:ListBucket"],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::mybucket"],
"Condition": {"StringLike": {"s3:prefix": ["${cognito-identity.amazonaws.com:organization}/*"]}} // here
},
{
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::mybucket/${cognito-identity.amazonaws.com:organization}/*"] // here
}
]
}
IAM policies with the Identity Pool's "Role Based Access Control" feature [0].
Assign each cognito user a Group value in the User Pool. This is their organization (ex. "organization1").
Map the Group to an IAM Role called "OrgRole" w/ an IAM policy that only allows s3 access to a path with their organization.
Something like this...
User | Cognito Group | IAM Role | IAM Policy S3 resource Mapping
user1 | organization1 | OrgRole | my-bucket/organization1/*
user2 | organization2 | OrgRole | my-bucket/organization1/*
user3 | organization2 | OrgRole | my-bucket/organization2/*
user4 | organization3 | OrgRole | my-bucket/organization3/*
Sample IAM policy for this role
"Resource": ["arn:aws:s3:::my_bucket/${current-users-cognito-group}/*"]
Cons: Complicated setup and per [0], only supports 25 groups per user pool which doesn't scale. Which makes me think my setup is incorrect.
[0] https://aws.amazon.com/blogs/aws/new-amazon-cognito-groups-and-fine-grained-role-based-access-control-2/
[1] https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html
[2] https://aws.amazon.com/blogs/security/writing-iam-policies-grant-access-to-user-specific-folders-in-an-amazon-s3-bucket/
Currently I am using Amazon Cognito for authentication in an AWS Amplify project, so only signed-in users have access to the api.
But I want to have some api calls publicly accessible.
How do I go about this?
I just solved this exactly same problem. This is what I did:
Update your API by running amplify update auth and select IAM as your users handler (everything else go with default)
Login to your AWS console -> Appsync and modify access to IAM (instead of Cognito Pool)
Go to the IAM console and create IAM policies for both AUTH and UNAUTH users (search them on the list by typing the name of your Appsync app)
Locate the AUTH user and attach the following policy (update it with your info):
AUTH USER
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/*"
]
}
]
}
Locate the unauth user and attach the following Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>",
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>",
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>"
]
}
]
}
And now the thing that is not documented (people transitioning from Cognito Pools to IAM ) You need to import {AUTH_TYPE}
import AWSAppSyncClient, {AUTH_TYPE} from "aws-appsync";
and use it to load the credentials in the AppSync initialization
const client = new AWSAppSyncClient(
{
disableOffline: true,
url: aws_config.aws_appsync_graphqlEndpoint,
region: aws_config.aws_cognito_region,
auth: {
// IAM
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials(),
});
Hope this helps.
For AppSync APIs - API Keys are considered "unauthenticated"
See the below documentation:
https://docs.aws.amazon.com/appsync/latest/devguide/security.html#api-key-authorization
I'm having a problem accessing a new DynamoDB table via a successfully authenticated Cognito user.
I get the following AccessDeniedException when attempting a scan of the table (using the AWS JavaScript SDK):
Unable to scan. Error: {
"message": "User: arn:aws:sts::MY-ACCOUNT-NUM:assumed-role/Cognito_VODStreamTestAuth_Role/CognitoIdentityCredentials
is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:us-east-1:MY-ACCOUNT-NUM:table/VideoCatalog",
"code": "AccessDeniedException",
"time": "2019-01-27T02:25:27.686Z",
"requestId": "blahblah",
"statusCode": 400,
"retryable": false,
"retryDelay": 18.559011800834146
}
The authenticated Cognito user policy has been extended with the following DynamoDB section:
{
"Sid": "AllowedCatalogActions",
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
"arn:aws:dynamodb:us-east-2:MY-ACCOUNT-NUM:table/VideoCatalog"
]
}
Shouldn't this be sufficient to give my authenticated Cognito users access to any DynamoDB table I might create, as long as I specify the table resource as I do above? Or do I also need to add "Fine-grained access control" under the table's 'Access control' tab?
I can say that I created the VideoCatalog DynamoDB table under my non-root Administrator IAM role (represented above by MY-ACCOUNT-NUM). Is that a problem? (Prior to trying to move to a DynamoDB table I was using a JSON file on S3 as the video catalog.)
IAM confused!
Looking at the error message from AWS and the policy document that you provided, I can see that there are two different regions here.
AWS is saying that your user does not have access to aws:dynamodb:us-east-1:MY-ACCOUNT-NUM:table/VideoCatalog, whereas your policy document is providing access to aws:dynamodb:us-east-2:MY-ACCOUNT-NUM:table/VideoCatalog.
Are you perhaps provisioning your resources in two different regions by mistake?
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!