Identity Pool Role Can't Access S3 Bucket Access Point - amazon-web-services

Summary: I am using AWS Amplify Auth class with a pre-configured Cognito User Pool for authentication. After authentication, I am using the Cognito ID token to fetch identity pool credentials (using AWS CredentialProviders SDK) whose assumed role is given access to an S3 access point. I then attempt to fetch a known object from the bucket's access point using the AWS S3 SDK. The problem is that the request returns a response of 403 Forbidden instead of successfully getting the object, despite my role policy and bucket (access point) policy allowing the s3:GetObject action on the resource.
I am assuming something is wrong with the way my policies are set up. Code snippets below.
I am also concerned I'm not getting the right role back from the credentials provider, but I don't allow unauthenticated roles on the identity pool so I am not sure, and I don't know how to verify the role being sent back in the credentials' session token to check.
I also may not be configuring the sdk client objects properly, but I followed the documentation provided to the best of my understanding from the documentation (I am using AWS SDK v3, not v2, so slightly different syntax and uses modular imports)
Backend Configurations - IAM
Identity Pool: Authenticated Role Trust Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": [
"sts:AssumeRoleWithWebIdentity",
"sts:TagSession"
],
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "<MY_IDENTITY_POOL_ID>"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Identity Pool: Authenticated Role S3 Access Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Backend Configurations - S3
S3 Bucket and Access Points: Block All Public Access
S3 Bucket CORS Policy:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 300
}
]
S3 Bucket Policy (Delegates Access Control to Access Points):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DelegateAccessControlToAccessPoints",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "*",
"Resource": [
"arn:aws:s3:::<MY_BUCKET_NAME>",
"arn:aws:s3:::<MY_BUCKET_NAME>/*"
],
"Condition": {
"StringEquals": {
"s3:DataAccessPointAccount": "<MY_ACCT_ID>"
}
}
}
]
}
Access Point Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessPointToGetObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCT_ID>:role/<MY_IDENTITY_POOL_AUTH_ROLE_NAME>"
},
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:<REGION>:<ACCT_ID>:accesspoint/<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Front End AuthN & AuthZ
Amplify Configuration of User Pool Auth
Amplify.configure({
Auth: {
region: '<REGION>',
userPoolId: '<MY_USER_POOL_ID>',
userPoolWebClientId: '<MY_USER_POOL_APP_CLIENT_ID>'
}
})
User AuthZ process:
On user login event, call Amplify's Auth.signIn() which returns type CognitoUser:
// Log in user (error checking ommitted here for post)
const CognitoUser = await Auth.signIn(email, secret);
// Get ID Token JWT
const CognitoIdToken = CognitoUser.signInUserSession.getIdToken().getJwtToken();
// Use #aws-sdk/credentials-provider to get Identity Pool Credentials
const credentials = fromCognitoIdentityPool({
clientConfig: { region: '<REGION>' },
identityPoolId: '<MY_IDENTITY_POOL_ID>',
logins: {
'cognito-idp.<REGION>.amazonaws.com/<MY_USER_POOL_ID>': CognitoIdToken
}
})
// Create S3 SDK Client
client = new S3Client({
region: '<REGION>',
credentials
})
// Format S3 GetObjectCommand parameters for object to get from access point
const s3params = {
Bucket: '<MY_ACCESS_POINT_ARN>',
Key: '<MY_OBJECT_KEY>'
}
// Create S3 client command object
const getObjectCommand = new GetObjectCommand(s3params);
// Get object from access point (execute command)
const response = await client.send(getObjectCommand); // -> 403 FORBIDDEN

Related

AWS Amplify Storage.get() - AWSAccessKeyId is undefined

I'm struggling with a react native project utilizing AWS amplify, both Auth and Storage.
Calling Storage from this service file in my project:
import Amplify, {Auth, Storage} from 'aws-amplify'
import config from './config'
Amplify.configure({
Auth: config.Auth,
Storage: config.Storage,
})
export {
Auth,
Storage,
}
config is:
AWS: {
Auth: {
region: 'us-east-X',
userPoolId: 'us-east-XXXXXXX',
userPoolWebClientId: 'XXXXXXXXX',
identityPoolId: 'us-east-X:XXXXXXXXXXXXXX',
},
Storage: {
AWSS3: {
bucket: 'XXXXXXX',
region: 'us-east-X',
},
},
},
By this point, the user has authenticated with Auth. Calling:
Storage.get('public/fileName.gif', { expires: 120 })
Results in a signed URL that appears to be missing the access key, which it should be generating from the IAM logged in user.
Here's an example signed URL it generates:
https://expyhealth-stg.s3.amazonaws.com/public/activityImages/Ankle%20Pumps.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=undefined%2F20201020%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201020T204616Z&X-Amz-Expires=120&X-Amz-Signature=f40bad142a9b190f9d9959bb9db0ad077c0cecab5171098f033a700cb9aa45b5&X-Amz-SignedHeaders=host&x-amz-user-agent=aws-sdk-js-v3-react-native-%40aws-sdk%2Fclient-s3%2F1.0.0-gamma.8%20aws-amplify%2F3.6.0%20react-native&x-id=GetObject"
Notice the X-Amz-Credential=undefined
I've been following the thread here for hours and keep coming up short. I cannot determine why it isn't generating the access key.
Using aws-amplify version 3.3.4
Here is the bucket policy:
{
"Version": "2012-10-17",
"Id": "Policy1599854584652",
"Statement": [
{
"Sid": "Stmt1599854581275",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXXXXX:role/Cognito_ExpyHealthStagingAuth_Role"
},
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::XXXXXXXX",
"arn:aws:s3:::XXXXXXXX/*"
]
}
]
}
I solve a similar issue by editing the IAM trust relationship as follows:
Open the AWS console
Go to IAM
Go to roles
Select the role specified as the Authenticated role of your identity pool
Open "Trust Relationship" tab
Click "Edit trust relationship"
Paste the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "eu-west-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Tip: compare your configuration with the one generated by the AWS Amplify CLI.

Assigning permission to single cognito user access to a secret created on AWS's Secret Manager

I have created a secret on AWS's Secrets Manager. I have a python service with cognito authentication, and I want to assign to a particular user permission to get this secret. I created the following policy to allow users to get the secret's value.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:test_secret*"
}
]
}
I've then assigned this policy to my cognito identity pool's Authenticated role. Now every user in this pool has permission to get this secret's value. But I need to assign this permission to a single user, not all of them. Is there any way to do this?
You can put this user in a group and let this group assume a IAM Role.
Then attach the right to the IAM Role.
Role-Based Access Control
You can write your authenticated user IAM policy in a way that it only allows them permission to access resources that they create. E.g.:
Instantiate an AWS secretsmanager client using the ID token you get from CognitoIdentity
Specify your authenticated policy to look something like this:
{
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": [
"secretsmanager:UpdateSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:DeleteSecret"
],
"Resource": "*",
"Condition": {
"ForAllValues:StringEquals": {
"aws:TagKeys": [
"Sub",
"Service"
]
},
"StringEquals": {
"secretsmanager:ResourceTag/Service": "MYSERVICE",
"secretsmanager:ResourceTag/Sub": "${cognito-identity.amazonaws.com:sub}"
}
}
},
{
"Sid": "",
"Effect": "Allow",
"Action": [
"secretsmanager:TagResource",
"secretsmanager:CreateSecret"
],
"Resource": "*",
"Condition": {
"ForAllValues:StringEquals": {
"aws:TagKeys": [
"Sub",
"Service"
]
},
"StringEquals": {
"aws:RequestTag/Service": "MYSERVICE",
"aws:RequestTag/Sub": "${cognito-identity.amazonaws.com:sub}"
}
}
}
]
}
}
3) When you create the secret, be sure to apply tags in the CreateSecret request that map to your users identitypoolid and service name. (If you don't, your request will fail.)
Your users will now only be able to access secrets that they create. This is secure because the "${cognito-identity.amazonaws.com:sub}" value will be interpolated based on the AWS SDK session credentials. I.e. your other users' clients will have different "sub" values embedded as part of their session credentials, so they won't be able to access secrets they didn't create.

AWS Cognito and AWS S3 integration?

Is it possible to restrict AWS S3 objects such that only users who authenticate via AWS Cognito gain access to the object? I haven't figured out a way to do this, but it seems obvious to me that this would be a use case.
I want to host a website via AWS S3 and restrict some objects (my pages) so that if a user were to go them directly they'd get a permission denied error. If the user was authenticated via AWS Cognito tho the object should be available.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Deny access to objects in the secured directory.",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::domain.com/secured/*"
},
{
"Sid": "Only allowed authenticated users access to a specific bucket.",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::x:role/Cognito_Domain_IdP_Auth_Role"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::domain.com/secured/*"
},
{
"Sid": "Read access for web hosting.",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::domain.com/*"
}
]
}
You can pass a role to your users authenticated via cognito and then allow them certain actions.
Creating Roles for Role Mapping
It is important to add the appropriate trust policy for each role so that it can only be assumed by Amazon Cognito for authenticated users in your identity pool. Here is an example of such a trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:12345678-dead-beef-cafe-123456790ab"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Granting Pass Role Permission
To allow an IAM user to set roles with permissions in excess of the user's existing permissions on an identity pool, you grant that user iam:PassRole permission to pass the role to the set-identity-pool-roles API.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::123456789012:role/myS3WriteAccessRole"
]
}
]
}
More Info here:
https://docs.aws.amazon.com/cognito/latest/developerguide/role-based-access-control.html
Yes, you can achieve that
But for that you can not achieve it directly configuring through the console
→First you need to create policy with limited access of Cognito and S3 bucket
→Create a role attaching them
→Assign them to your EC2 or Lambda
→Authenticate the user in your application using Cognito
→Using that you can have a signed URL for S3 object and retrieve them
This may help you

AWS Lambda cross-account access denied

I have 2 AWS accounts. Account 1 has a CloudSearch domain that I need to query from a Lambda function in account 2. I've followed a tutorial here for creating a role in account 1 that allows cross-account access.
So, in account 1 I have a role arn:aws:iam::111111111111:role/my_cloudsearch_query_role that looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "cloudsearch:search",
"Resource": "*"
}
]
}
This role has one trusted entity, account 2, and I can see the correct account ID under the Trusted Entities section for the role in the IAM console.
In account 2, I've created a Lambda function with an execution role that looks like this:
{
"roleName": "my_cloudsearch_query_role",
"policies": [
{
"document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/my_cloudsearch_query_role"
}
]
},
"name": "oneClick_lambda_basic_execution_1526469274620",
"type": "inline"
}
]
}
My Lambda function (Python 3) code tries to query CloudSearch like this:
client = boto3.client('cloudsearchdomain', endpoint_url=endpoint)
response = client.search(
query="(and name:'foobar')",
queryParser='structured',
returnFields='curr_addr',
size=1
)
All calls to the Lambda function fail with the following error:
An error occurred (AccessDenied) when calling the Search operation: User: arn:aws:sts::222222222222:assumed-role/my_cloudsearch_query_role/my_lambda_func is not authorized to perform: cloudsearch:search on resource: myCSDomain
I'm positive that I've gotten the account IDs correct so there's no mix-up. Is there something else I need to do to get it working?
You need to assume the role in account 111111111111 and then use the returned credentials to create your client object. Use assume_role boto3 API call to get the credentials. Here's a sample code:
role_arn = "arn:aws:iam::111111111111:role/my_cloudsearch_query_role"
sts = boto3.client('sts', region_name="us-east-1")
token = sts.assume_role(RoleArn=role_arn, RoleSessionName="Session1")
credentials = token['Credentials']
access_key = credentials['AccessKeyId']
secret_key = credentials['SecretAccessKey']
token = credentials['SessionToken']
session = boto3.session.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=token
)
client = session.client('cloudsearchdomain', endpoint_url=endpoint)
response = client.search(...)
Please update the role_arn with the correct details. Documentation about Session object can be found here.

AWS ElasticSearch write to account "A" from lambda in account "B"

I have an AWS ElasticSearch Cluster in account "A".
I'm trying to create a lambda (triggered from a DynamoDB Stream) in account "B" that will write to ES in account "A".
I'm getting the following error:
{
"Message":"User: arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToES is not authorized to perform: es:ESHttpPost on resource: beta-na-lifeguard"
}
I have tried putting the STS as well as the ROLE into the ES access policy (within account "A") with no luck. Here is my policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::AccountA:user/beta-elasticsearch-admin"
},
"Action": "es:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::AccountA:user/beta-elasticsearch-readwrite",
"arn:aws:iam::AccountA:role/beta-na-DynamoDBStreamLambdaElasticSearch",
"arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToES",
"arn:aws:iam::AccountB:role/service-role/lambdaRole1"
]
},
"Action": [
"es:ESHttpGet",
"es:ESHttpPost",
"es:ESHttpPut"
],
"Resource": "*"
}
]
}
In my code above I was adding arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToSNS into the AccountA ES access list, that is wrong. Instead do the following:
I already had arn:aws:iam::AccountA:role/beta-na-DynamoDBStreamLambdaElasticSearch in the ES access list, I needed to add a trust relationship (from the IAM role screen) for that role to be assumable by AccountB. I added this into the trust relationship:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::AccountB:root"
},
"Action": "sts:AssumeRole"
}
Then, in my accountB lambda code, I needed to assume that role. Here is the relevent code from the lambda.
var AWS = require('aws-sdk');
var sts = new AWS.STS({ region: process.env.REGION });
var params = {
RoleSessionName: "hello-cross-account-session",
RoleArn: "arn:aws:iam::accountA:role/beta-na-DynamoDBStreamLambdaElasticSearch",
DurationSeconds: 900
};
sts.assumeRole(params, function (err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
context.fail('failed to assume role ' + err);
return;
}
log("assumed role successfully! %j", data)
postToES(bulkUpdateCommand, context);
});
When you create a "role" for another account you also need to setup the "Trust relationships". This is done in the AWS IAM console under "Roles". Second tab for your role is "Trust relationships". You will need to specify the account details for the other account as trusted.
The "Trust relationships" is a policy document itself. Here is an example that will allow you to call AssumeRole from another account to my AWS account.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::2812XXXXYYYY:root"
},
"Action": "sts:AssumeRole"
}
]
}
In your role, just specify permissions as normal just like you were granting permissions for another IAM user / service (e.g. remove all those account type entries). The Trust relationships policy document defines who can call AssumeRole to be granted those permissions.
Creating a Role to Delegate Permissions to an IAM User
Modifying a Role