I am trying to create a solution where every client that will use my service will have a sqs (which is in my AWS account). So in order that the client will be able to send messages and read messages from the queue, I want to use cognito with a single role that has variables, as there is a limitation on the number of roles that a single account can have.
I have created cognito user pool with an application, also created federated identity, role, policy and linked everything together.
the policy is
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:DeleteMessageBatch",
"sqs:SendMessageBatch"
],
"Resource": [
"arn:aws:sqs:us-east-1:XXXX:test-${cognito-identity.amazonaws.com:sub}",
"arn:aws:sqs:us-east-1:XXXX:test"
]
}
]
}
the test client code is
const cognitoUser = userPool.getCurrentUser();
cognitoUser.getSession((err, session) => {
console.log(`session token: ${session.getIdToken().getJwtToken()}`);
const paramsCredentials = {
IdentityPoolId: 'XXXX',
Logins: {}
};
AWS.config.region = 'XXXX';
paramsCredentials.Logins[
`cognito-idp.${AWS.config.region}.amazonaws.com/XXXX`
] = session.getIdToken().getJwtToken();
AWS.config.credentials = new AWS.CognitoIdentityCredentials(
paramsCredentials
);
AWS.config.credentials.get(err => {
if (err) {
console.log(`got error - getting credentials. error: ${err}`);
}
const id = AWS.config.credentials.identityId;
console.log('Cognito Identity ID ' + id);
const sqs = new AWS.SQS({
region: AWS.config.region
});
const params = {
QueueName: 'test-9ea2b895-2971-4ee2-b372-451bf2b19731'
};
sqs.getQueueUrl(params, (err, data) => {
if (err) {
console.log(`got error getting url for queue, error: ${err}`);
} else {
console.log(`SQS url = ${data.QueueUrl}`);
}
});
});
});
and I am getting an error of
AWS.SimpleQueueService.NonExistentQueue: The specified queue does not exist or you do not have access to it.
Blockquote
But when I change the queue to the test one, all is working fine. I have double checked the sub and it is the correct id
What did i do wrong?
${cognito-identity.amazonaws.com:sub} IAM policy variable will return region:uuid your queue name will be test-us-east-1:9ea2b895-2971-4ee2-b372-451bf2b19731 which is a invalid SQS queue name(colon not allowed). So, it is not possible to restrict access to a queue named after that identity but you can create a policy limited to only a specific set of users of your application
Here is a blog from AWS on Understanding Amazon Cognito Authentication Part 3: Roles and Policies
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:DeleteMessageBatch",
"sqs:SendMessageBatch"
],
"Resource": [
"arn:aws:sqs:us-east-1:XXXX:test"
]
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:sub": [
"us-east-1:12345678-1234-1234-1234-123456790ab"
]
}
}
}
]
}
Related
I am trying to get parameters in AWS Parameter Store using #aws-sdk v3 with cognito unauthenticated identity pool like this:
import { SSMClient, GetParameterCommand } from "#aws-sdk/client-ssm";
import { fromCognitoIdentityPool } from "#aws-sdk/credential-providers";
const REGION = 'us-east-1'
const ssm = new SSMClient({
region: REGION,
credentials: fromCognitoIdentityPool({
clientConfig: { region: REGION },
identityPoolId: 'us-east-1:xxxx...'
})
});
const input = {
Name: '/config'
}
const response = await ssm.send(new GetParameterCommand(param));
But I am receiving this message:
AccessDeniedException: User:
arn:aws:sts::xxxxxxxxxxxx:assumed-role/Cognito_CredentialsUnauth_Role/CognitoIdentityCredentials
is not authorized to perform: ssm:GetParameter on resource:
arn:aws:ssm:us-east-1:xxxxxxxxxxxx:parameter/config because no session
policy allows the ssm:GetParameter action
My cognito role is:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": "*"
}
]
}
I talk to aws support and they sad that my role is right, and the problem is in my session policy. But I don't understand what I need to do
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
I'm working with AWS and I've the following setup: UserPool; API Gateway, Lambda Functions
The api gateway is using a UserPool authorizer to protect the lambda functions. This is working so far. Now I want to restrict every lambda function to a specific group of users. Therefore I've created two user groups in the CognitoPool (user and admin) and I've assigned a specific role to each group with a policy. Afterwards I've created a user in the UserPool and added him to the user group. That user is still able to submit requests to each route/lambda function.
How do I submit a request?
Postman
set IdToken (of the authenticated user) in the Authorization header
without Authorization header the response is a 401 (as expected)
with Authorization header every lambda function can be triggered (not expected)
Configuration of the UserPool Groups:
Group User:
Arn: Role ARN: arn:aws:iam::xxxxxx:role/User
UserRole is specified as
{
"Version": "2012-10-17",
"Statement": [
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": [
"arn:aws:lambda:region:xxxxxx:function:api-dev-getItems
],
"Effect": "Allow"
]
}
Group Admin:
Arn: Role ARN: arn:aws:iam::xxxxxx:role/Admin
AdminRole is specified as
{
"Version": "2012-10-17",
"Statement": [
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": [
"arn:aws:lambda:region:xxxxxx:function:api-dev-getItems
"arn:aws:lambda:region:xxxxxx:function:api-dev-getUsers
],
"Effect": "Allow"
]
}
The payload of the id token also contains:
'cognito:roles': [ 'arn:aws:iam::xxxxxx:role/User' ]
So I've found a solution to my problem. Here is the summary of my experiences:
Cognito Authorizer is more like a yes/no authorizer (authenticated or not; user groups are not evaluated)
Therefore I went with AWS IAM Authorizer in the API Gateway, which will evaluate the user group roles
Instead of a JWT the AWS signature v4 authorization has to be passed (there is a plugin for postman and several packages on npm)
Since I am using an API Gateway I had to change the role policy resources to execute-api:Invoke
In detail:
UserRole:
{
"Version": "2012-10-17",
"Statement": [
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": [
"arn:aws:execute-api:region:accountid:api-id/stage/GET/items
],
"Effect": "Allow"
]
}
AdminRole:
{
"Version": "2012-10-17",
"Statement": [
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": [
"arn:aws:execute-api:region:accountid:api-id/stage/GET/items
"arn:aws:execute-api:region:accountid:api-id/stage/*/users
],
"Effect": "Allow"
]
}
Instead of passing the ID Token into the Authorization header, I had to use Postman AWS Signature, which requires at least an AccessKey and a SecretKey. Those two can be retrieved when I sign in my user using the aws-sdk. aws-sdk-js with TypeScript as example:
import { CognitoUserPool, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
const userPool = new CognitoUserPool({
UserPoolId: 'my pool id',
ClientId: 'my client id'
});
function signIn(username: string, password: string) {
const authData = {
Username: username,
Password: password,
};
const authDetails = new AuthenticationDetails(authData);
const userData = {
Username: username,
Pool: userPool,
};
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authDetails, {
onSuccess: (result) => {
const cognitoIdpKey = `cognito-idp.${region}.amazonaws.com/${userPool.getUserPoolId()}`;
const credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'identity pool id,
Logins: {
[cognitoIdpKey]: result.getIdToken().getJwtToken(),
}
});
AWS.config.update({
credentials,
});
credentials.refreshPromise()
.then(() => {
console.log('Success refresh. Required data:', (credentials as any).data.Credentials);
})
.catch(err => console.error('credentials refresh', err));
}
});
}
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
I am trying to connect a kinesis trigger to a lambda function, but keep getting the same error no matter how I configure the IAM. I've also tried setting the IAM role to "*" in most fields. How can I determine what's preventing the trigger from connecting if the IAM role is configured the way the error has requested?
Here is the error:
There was an error creating the trigger: Cannot access stream [arn]. Please ensure the role can perform the GetRecords, GetShardIterator, DescribeStream, and ListStreams Actions on your stream in IAM.
Here is my IAM role which can perform the GetRecords, GetShardIterator, DescribeStream, and ListStreams PLUS more:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1505763107000",
"Effect": "Allow",
"Action": [
"kinesis:CreateStream",
"kinesis:DescribeStream",
"kinesis:GetShardIterator",
"kinesis:GetRecords",
"kinesis:ListRecords",
"kinesis:PutRecord",
"kinesis:PutRecords"
],
"Resource": [
"arn:aws:kinesis:us-east-2:219021079475:stream/lead"
]
},
{
"Sid": "Stmt1505763184000",
"Effect": "Allow",
"Action": [
"cloudwatch:*"
],
"Resource": [
"*"
]
},
{
"Sid": "Stmt1505763256000",
"Effect": "Allow",
"Action": [
"lambda:*"
],
"Resource": [
"arn:aws:lambda:us-east-2:219021079475:function:logsKinesisData"
]
}
]
}
I've not written the lambda function beyond set up:
'use strict';
console.log('Loading function');
exports.handler = (event, context, callback) => {
//console.log('Received event:', JSON.stringify(event, null, 2));
event.Records.forEach((record) => {
// Kinesis data is base64 encoded so decode here
const payload = new Buffer(record.kinesis.data, 'base64').toString('ascii');
console.log('Decoded payload:', payload);
});
callback(null, `Successfully processed ${event.Records.length} records.`);
};