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/
Related
currently I am working with AWS DynamoDB and I struggle with user authorization and restricting access to specific items inside of a DynamoDB Table.
I have already read the documentation and came across multiple blog posts, but unfortunately I haven't found my use case yet.
Some background information:
Each user of the web app belongs to a company and each company has multiple orders. These orders are inside of the DynamoDB table "Orders". What I want to achieve is that the users can only read order items from the company they belong to.
My Approach
My idea was to create the "Orders" table with a partition key of "companyId" and a sort key of "orderId". During my research I figured out that I can restrict the access through IAM Policy roles, but I couldn't find a way to access the companyId of the user inside of the policy role. Users are authenticating through AWS Cognito.
My Question
How can I restrict the user access specific items inside of a DynamoDB? Taking into account the each user belongs to a company and should only see orders of this company.
Looking forward to some help!
AWS has published Isolating SaaS Tenants with Dynamically Generated IAM Policies blog on their website. This blog explains exactly the thing that you want to achieve.
In short, I can explain:
Use CustomerId as PartitionKey
Create an IAM policy with access on Orders table like below
1 {
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Action": [
6 "dynamodb:GetItem",
7 "dynamodb:BatchGetItem",
8 "dynamodb:Query",
9 "dynamodb:DescribeTable"
10 ],
11 "Resource": "arn:aws:dynamodb:us-west-2:123456789012:table/Orders",
12 "Effect": "Allow"
13 }
14 ]
15 }
Create a template for Session Policy where you will replace CustomerId with incoming request's customerId on runtime.
1 {
2 "Effect": "Allow",
3 "Action": [
4 "dynamodb:*"
5 ],
6 "Resource": [
7 "arn:aws:dynamodb:*:*:table/{{table}}"
8 ],
9 "Condition": {
10 "ForAllValues:StringEquals": {
11 "dynamodb: LeadingKeys": [ "{{customerId}}" ]
12 }
13 }
14 }
Now, invoke STS (Security Token Service) with above IAM & Session policy to get temporary credentials that has access limited to a single tenant/customer data.
Below is the pseudo code, you can use your programming language's SDK to write below code.
AssumeRoleResponse response = sts.assumeRole (ar -> ar
.webIdentityToken(openIdToken)
.policy(scopedPolicy)
.roleArn(role));
Credentials tenantCredentials = response.credentials();
DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
.credentialsProvider(tenantCredentials)
.build();
Finally, create DynamoDBClient object using these temporary credentials and use it. This object of DynamoDBClient will have access to only current user's customer data.
Hope this should help!
Using custom attributes, you can create a backend layer that will check these parameters, query DynamoDB with the specified attribute, and return them - https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
The second option is to set up role for each company - https://docs.aws.amazon.com/cognito/latest/developerguide/role-based-access-control.html#using-rules-to-assign-roles-to-users
I have created one table in Dynamodb and setup role in IAM with following policy attached:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "arn:aws:dynamodb:ap-south-1:**AccountAID**:table/employee"
}]
}
I have added trusted entity AccountB ID in the role. Then I have also created policy in AccountB for the access of dynamodb table created in AccountA with following policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "*",
"Resource": "arn:aws:iam::**AccountAID**:role/DynamodbCrossAccountAccessRole"
}]
}
I have configured profiles of AccountA user and AccountB user and with the credentials of AccountA's profile user, I can list tables of dynamodb but while trying to get with AccountB's profile user I always returns with empty-table list.
Krunal-MacBook-Air:.aws krunal$ aws dynamodb list-tables --profile Krunal
{
"TableNames": [
"employee"
]
}
Krunal-MacBook-Air:.aws krunal$ aws dynamodb list-tables --profile Krunal2
{
"TableNames": []
}
Can anyone help me out of this why am I not able to access dynamodb with AccountB's profile?
Policies are attached to the account users respectively.
Based on the comments.
The issue was solved by assuming the role in AccountB. The useful links showing how to do this are:
Cross-Account Access Control with Amazon STS for DynamoDB
Delegate Access Across AWS Accounts Using IAM Roles
Based on the comments and with the help of provided documents, I can access to my AccountB's resources. I have configured generated API Key, Secret Key and Tokens in credentials file of my profile and it's working as expected.
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 want to add a S3 permission for a specific user. The AWS console is asking me for the Canonical ID for the user.
I used the AWS CLI command aws iam list-users to retrieve the list of users, but there was no "Canonical ID" field, and the "User ID" is not recognized, giving me an "Invalid ID" message. I tryied also with ARN and it did not work.
To grant permissions to an IAM user on a bucket, you'll need to create a Bucket Policy - which is an JSON document. The "Access for other AWS accounts" option in the ACL is for granting access to other (wholly separate) root AWS accounts, not for granting access to IAM users within your own root account.
Creating/Editing a Policy
To access the bucket policy, browse to a bucket in the S3 web console. There you'll see the Overview/Properties/Permissions/Management tabs. Under Permissions there is a sub-tab called "Bucket Policy". At the bottom of the Bucket Policy page there is a link to a "Policy Generator", which will generate the JSON for you. (or the direct link is http://awspolicygen.s3.amazonaws.com/policygen.html)
Identifying an IAM User to Assign Rights To
To identify the IAM user you want to grant permissions to, you'll use an ARN (Amazon Resource Name). The ARN format for IAM users is as follows: "arn:aws:iam::{Account-ID}:user/{Username}" (note the curly braces aren't part of the format). An example IAM ARN looks like this: arn:aws:iam::100123456789:user/Daniel
To get your numeric account ID, sign in as the root user and click your user name in the upper right corner of the page and choose "My Account" (which takes you to https://console.aws.amazon.com/billing/home?#/account ). The account ID is listed under "Account Settings" at the top of the page.
Plug that user ARN into the "Principal" field of the policy generator, and choose which action(s) to grant to the user from the dropdown list.
Specifying what to grant permissions on
To grant permissions to a bucket, or a set of files (objects) within a bucket you need to enter an ARN that identifies the bucket, or some subset of objects within the bucket into the "Amazon Resource Name" field (e.g. if I had a bucket called daniels-stuff and a folder in that bucket called images that I wanted to grant access to then I could provide an ARN such as arn:aws:s3:::daniels-stuff/images/*
Hit "Add Statement" when you've put in the necessary information and then hit "Generate Policy". Note you can put multiple statements (access right assignments) into the one policy.
More Info
Finally, there is a good primer to s3 bucket policies at https://brandonwamboldt.ca/understanding-s3-permissions-1662/ which includes some example policies.
Good luck (although I assume you've probably solved your issue now, others may find this helpful).
The user's canonical ID is easiest to find by calling, as the user whose ID you want to find, aws s3api list-buckets:
aws --profile PROD s3api list-buckets
{
"Owner": {
"DisplayName": "a-display-name",
"ID": "a-64?-char-hex-string" <<-- this HERE is the canonical user ID
},
"Buckets": [
{
"CreationDate": "2018-03-28T21:50:56.000Z",
"Name": "bucket-1"
},
{
"CreationDate": "2018-03-22T14:08:48.000Z",
"Name": "bucket-2"
}
]
}
With this ID, you can then call the s3api to grant access - eg to give read-access - like this:
aws --profile OTHER s3api put-object-acl \
--bucket bucket-3 \
--key path/to/file \
--grant-read id="the-64-char-hex"
To find Canonical ID of account follow the below steps:
Click on your username which is located in the top menu bar.
A drop down will appear. Select My Security Credentials.
Canonical user ID is there
For getting the canonical ID, one of the simplest ways is to use CLI and run aws s3api list-buckets command. You will get the ID in the output.
There are other ways also for getting the canonical ID and are clearly described in the aws docs: https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html
list-buckets aws docs: https://docs.aws.amazon.com/cli/latest/reference/s3api/list-buckets.html
You can also get your Canonical ID for your IAM user from the CLI command:
List all the buckets and your iam Canonical ID:
aws s3api list-buckets
And if you just want to get your Canonical ID:
aws s3api list-buckets | grep ID
Keep in mind, the above commands are done via CLI where you would have set up your profile already.
1. Create a new policy using the following JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::<your_bucket_name_here>"
},
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::<your_bucket_name_here>/*"
}
]
}
**2. Assign the policy to the user or a user group
Note:
I kept on getting Access Denied (Status code: 403) errors because I was missing the first 2 objects in the JSON array above. You have to give your user permission to:
See all your buckets
List the specific bucket and get its location
before it even gets a chance to perform any actions on the bucket contents at all.
How to view the Canonical ID in AWS.
In the top Click your user Account drop down box .
Click My Security Credential.
In the console Click the Account Identifiers
Pls don’s share your credential info.
To view the Canonical ID in AWS
Pala ( VR.PL)
paste this in the terminal
aws s3api list-buckets --query "Owner.ID"
Make sure to have * at the end of the line to grant permissions to everything!
arn:aws:s3:::daniels-stuff/images/*
There is another way to find your canonical name in case you are not the ROOT user,
You will need to log using CLI (using your access key and secret key) afterwards just type
aws iam list-users
and it will show the following information
{
"Users": [
{
"UserName": "yoda",
"PasswordLastUsed": "2018-02-24T17:47:15Z",
"CreateDate": "2018-02-11T02:23:11Z",
"UserId": "AIDASDY9WSX6QD", <== Canonical Name
"Path": "/",
"Arn": "arn:aws:iam::7783412456453:user/yoda"
}
]
Note: The User ID and ARN originals were edited :-)
I hope this will help
Neo
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