Have got the IdentityPool created and userPool too.
Region, AccountId, IdentityPoolId and roleArn passed as params do hold the correct values.
A call to CognitoIdentityCredentials
AWS.config.region = region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
AccountId: accountId,
IdentityPoolId: identityPoolId,
RoleArn: roleArn,
});
returns (sensitive credentials obfuscated)
{
"expired": true,
"expireTime": null,
"refreshCallbacks": [],
"params": {
"AccountId": "9999999999",
"IdentityPoolId": "eu-west-2:bcfbd1c9-1234-1234-1234-423be5c3f7d8",
"RoleArn": "arn:aws:iam: : 999999999:role/Cognito_ElasticSearchIdentityPoolAuth_Role"
},
"data": null,
"_identityId": null,
"_clientConfig": {}
}
I would expect this call to return accessKeyId and sessionToken.
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html
Copied from the CognitoIdentityCredentials API documentation:
If a RoleArn is provided, then this provider gets credentials using the AWS.STS.assumeRoleWithWebIdentity() service operation, after first getting an Open ID token from AWS.CognitoIdentity.getOpenIdToken().
Consequently, I suggest that you try something along the lines, starting with CognitoIdentity.getId():
const getIdParams = {
IdentityPoolId: // ...
AccountId: // ...
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* '<IdentityProviderName>': ... */
}
};
const {IdentityId} = await cognitoidentity.getId(getIdParams).promise();
const openIdTokenParams = {
IdentityId,
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* '<IdentityProviderName>': ... */
}
};
const {Token} = await cognitoidentity.getOpenIdToken(openIdTokenParams).promise();
const assumeWebIdentityParams = {
DurationSeconds: // ...
ProviderId: // ...
RoleArn: // ...
RoleSessionName: // ...
WebIdentityToken: Token
}
const {Credentials} = await sts.assumeRoleWithWebIdentity(assumeWebIdentityParams).promise();
console.log(Credentials);
// prints AccessKeyId, Expiration, SecretAccessKey and SessionToken
Related
I have a Lambda Function running in a Tenant account that needs to query a DynamoDb Table B inside the Tenant and then query a DynamoDb Table A inside ROOT.
This is the code I have so far:
'use strict';
const AWS = require('aws-sdk');
const ddbDc = new AWS.DynamoDB.DocumentClient()
module.exports.testDynamo = async event => {
try {
let result
let params = {}
// Query Table B inside tenant
params = {
TableName: 'Table_B',
Key: { externalKey : 'CA6E03C' }
}
result = await ddbDc.get(params).promise()
console.log('🚀 result - ', result)
// Query Table A inside ROOT
// Restart ddbDc CLIENT with ROOT credentials ?
params = {
TableName: 'Table_A',
Key: { externalKey : 'MAP_CA6E03C' }
}
result = await ddbDc.get(params).promise()
console.log('🚀 result - ', result)
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(
{
response: response,
},
null,
2
),
}
} catch (error) {
console.error('🚀 testDynamo - error.stack:', error.stack)
return {
statusCode: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(error.stack)
}
}
}
I think I need to Restart ddbDc CLIENT with ROOT credentials in order to get this access to the ROOT resources.
How Can I do that?
You need to use STS AssumeRole and assume the role which you need to access that specific item/table.
https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
const AWS = require('aws-sdk');
const ddbDc = new AWS.DynamoDB.DocumentClient()
const sts = new AWS.STS()
module.exports.testDynamo = async event => {
try {
let result
let params = {}
// Query Table B inside tenant
params = {
TableName: 'Table_B',
Key: { externalKey : 'CA6E03C' }
}
result = await ddbDc.get(params).promise()
console.log('🚀 result - ', result)
// Query Table A inside ROOT
// Restart ddbDc CLIENT with ROOT credentials ?
// https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
const assumeRole = await sts
.assumeRole({
RoleArn: process.env.ROLE_ARN,
RoleSessionName: process.env.ROLE_SESSION_NAME,
})
.promise()
const credentials = new AWS.Credentials({
accessKeyId: assumeRole.Credentials?.AccessKeyId,
secretAccessKey: assumeRole.Credentials?.SecretAccessKey,
sessionToken: assumeRole.Credentials?.SessionToken,
})
const dynamodb = new AWS.DynamoDB.DocumentClient({
region: process.env.REGION,
credentials: credentials,
})
params = {
TableName: 'Table_A',
Key: { externalKey : 'MAP_CA6E03C' }
}
result = await dynamodb.get(params).promise()
console.log('🚀 result - ', result)
// TODO merge table data
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(
{
response: response,
},
null,
2
),
}
} catch (error) {
console.error('🚀 testDynamo - error.stack:', error.stack)
return {
statusCode: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(error.stack)
}
}
}
Using Lambda function and Serverless Framework do this:
npm install --save-dev serverless-iam-roles-per-function to install serverless-iam-roles-per-function. Link
...
plugins:
- serverless-iam-roles-per-function
...
functions:
listComponentsRootDynamo:
...
...
iamRoleStatements:
- Effect: Allow
Action:
- sts:AssumeRole
Resource: "arn:aws:iam::RootID:role/roleName"
It will grant access to this lambda function listComponentsRootDynamo the access to Root-DynamoDb Table inside the Control Tower ROOT account.
Note that the role that provide that access to the Dynamo specific tables must exist in the ROOT-IAM Roles. Just copy its Role ARN in the Resource: portion of iamRoleStatements:.
Here is an example of the Role inside Root that provides that access to a specific Dynamo Table <DynamoDb-Table-Name>:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"dynamodb:*"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:Root-Id:table/<DynamoDb-Table-Name>",
"arn:aws:dynamodb:us-east-1:Root-Id:table/<DynamoDb-Table-Name>/index/*"
],
"Effect": "Allow",
"Sid": ""
}
]
}
And Following the Lee Hannigan answer do this inside the Lambda that will query the Root - DynamoDb Table:
'use strict';
const AWS = require('aws-sdk');
const ddbDc = new AWS.DynamoDB.DocumentClient()
const sts = new AWS.STS()
module.exports.testDynamo = async event => {
try {
let result
let params = {}
// Query Table B inside tenant
params = {
TableName: 'Table_B',
Key: { externalKey : 'CA6E03C' }
}
result = await ddbDc.get(params).promise()
console.log('🚀 result - ', result)
// Query Table A inside ROOT
// Restart ddbDc CLIENT with ROOT credentials ?
// https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
const assumeRole = await sts
.assumeRole({
RoleArn: process.env.ROLE_ARN,
RoleSessionName: process.env.ROLE_SESSION_NAME,
})
.promise()
const credentials = new AWS.Credentials({
accessKeyId: assumeRole.Credentials.AccessKeyId,
secretAccessKey: assumeRole.Credentials.SecretAccessKey,
sessionToken: assumeRole.Credentials.SessionToken
})
const dynamodb = new AWS.DynamoDB.DocumentClient({
region: process.env.REGION,
credentials: credentials,
})
params = {
TableName: 'Table_A',
Key: { externalKey : 'MAP_CA6E03C' }
}
result = await dynamodb.get(params).promise()
console.log('🚀 result - ', result)
// TODO merge table data
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(
{
response: response,
},
null,
2
),
}
} catch (error) {
console.error('🚀 testDynamo - error.stack:', error.stack)
return {
statusCode: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(error.stack)
}
}
}
This is a very weird problem, I am trying to assme a role and then get signinToken by nodejs through lambda function.
If I use a IAM user's ak/sk to assume the role (arn:aws:iam::432127183xxx:role/ssm-test) to get Credentials, and then use this Credentials to get signinToken, it works fine.
But is I use lambda role to assume role (arn:aws:iam::432127183xxx:role/ssm-test) to get Credentials, and then use this Credentials to get signinToken, it works failed. Got below error response from "https://ap-southeast-1.signin.aws.amazon.com/federation" get request:
"code":"ERR_BAD_REQUEST","status":400
Below is code
Successful code:
const axios = require('axios').default;
var AWS = require('aws-sdk');
exports.handler = async (event) => {
const { accessKeyId, secretAccessKey, sessionToken } = await getCredentialsForRole();
await generateSigninSSMConsoleLink(accessKeyId, secretAccessKey, sessionToken);
};
async function getCredentialsForRole() {
const stsEndpoint = 'https://sts.ap-southeast-1.amazonaws.com';
AWS.config.update(
{
stsRegionalEndpoints: 'regional',
region: 'ap-southeast-1',
maxRetries: 6,
retryDelayOptions: { base: 1000 },
accessKeyId: 'xxx', //accessKeyId and secretAccessKey is my IAM user AK/SK
secretAccessKey: 'xxx',
});
const sts = new AWS.STS({ apiVersion: '2011-06-15', endpoint: stsEndpoint });
const params = {
RoleArn: 'arn:aws:iam::432127183xxx:role/ssm-test',
RoleSessionName: 'test',
};
const { Credentials: creds } = await sts.assumeRole(params).promise();
const { AccessKeyId: accessKeyId, SecretAccessKey: secretAccessKey, SessionToken: sessionToken } = creds;
return { accessKeyId, secretAccessKey, sessionToken };
}
async function generateSigninSSMConsoleLink(accessKeyId, secretAccessKey, sessionToken) {
const session = {'sessionId': accessKeyId,
'sessionKey': secretAccessKey,
'sessionToken': sessionToken}
const params={'Action': 'getSigninToken',
'SessionDuration': 43200,
'Session': session};
const getSigninTokenURL = `https://signin.aws.amazon.com/federation`;
const response = await axios.get(getSigninTokenURL, {params});
const signinToken = response.data.SigninToken;
return signinToken
}
Below is code failed code:
const axios = require('axios').default;
var AWS = require('aws-sdk');
exports.handler = async (event) => {
const { accessKeyId, secretAccessKey, sessionToken } = await getCredentialsForRole();
await generateSigninSSMConsoleLink(accessKeyId, secretAccessKey, sessionToken);
};
async function getCredentialsForRole() {
const stsEndpoint = 'https://sts.ap-southeast-1.amazonaws.com';
AWS.config.update(
{
stsRegionalEndpoints: 'regional',
region: 'ap-southeast-1',
maxRetries: 6,
retryDelayOptions: { base: 1000 },
});
const sts = new AWS.STS({ apiVersion: '2011-06-15', endpoint: stsEndpoint });
const params = {
RoleArn: 'arn:aws:iam::432127183xxx:role/ssm-test',
RoleSessionName: 'test',
};
const { Credentials: creds } = await sts.assumeRole(params).promise();
const { AccessKeyId: accessKeyId, SecretAccessKey: secretAccessKey, SessionToken: sessionToken } = creds;
return { accessKeyId, secretAccessKey, sessionToken };
}
async function generateSigninSSMConsoleLink(accessKeyId, secretAccessKey, sessionToken) {
const session = {'sessionId': accessKeyId,
'sessionKey': secretAccessKey,
'sessionToken': sessionToken}
const params={'Action': 'getSigninToken',
'SessionDuration': 43200,
'Session': session};
const getSigninTokenURL = `https://signin.aws.amazon.com/federation`;
const response = await axios.get(getSigninTokenURL, {params});
const signinToken = response.data.SigninToken;
return signinToken
}
only difference between successful code and failed code is:
accessKeyId: 'xxx', //accessKeyId and secretAccessKey is my IAM user AK/SK
secretAccessKey: 'xxx',
As I mentioned before, if I user a IAM user to assume role, then can get signintoken
if I use lambda role to assume role, then cannot get signinToken.
Who can tell what's wrong?
I am trying to call Amazon connect SDK Javascript V3 via lambda and my amazon connect instance is in another account. I am using sts assume role for cross-account access but i am having an error on resource not found. I am attaching my code so someone could help me. Thanks.
let { ConnectClient, SearchUsersCommand } = require("#aws-sdk/client-connect");
let { STSClient, AssumeRoleCommand } = require("#aws-sdk/client-sts");
let stsClient = new STSClient({ region: "eu-central-1" });
exports.handler = async function(event, context, callback) {
let params;
var stsParams = {
RoleArn: "arn:aws:iam::xxxxxxxx:role/Cross-Account-Role",
DurationSeconds: 1200,
RoleSessionName: "RoleSessionName" // any string
};
let stsCommand = new AssumeRoleCommand(stsParams);
const stsResp = await stsClient.send(stsCommand);
console.log({ stsResp });
let client = new ConnectClient({
region: "eu-central-1",
accessKeyId: stsResp.Credentials.AccessKeyId,
secretAccessKey: stsResp.Credentials.SecretAccessKey,
sessionToken: stsResp.Credentials.SessionToken,
})
// let client = await new ConnectClient(credentials);
console.log({ client });
params = {
InstanceId: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
MaxResults: 100
};
const command = new SearchUsersCommand(params);
console.log({command});
const resp = await client.send(command);
console.log({resp});
console.log("Users list", resp);
}
var stsParams = {
RoleArn: Role,
DurationSeconds: 1200,
RoleSessionName: RoleSessionName // any string
};
let stsCommand = new AssumeRoleCommand(stsParams);
const stsResp = await stsClient.send(stsCommand);
console.log({ stsResp });
client = new ConnectClient({
region: Region,
credentials: {
accessKeyId: stsResp.Credentials.AccessKeyId,
secretAccessKey: stsResp.Credentials.SecretAccessKey,
sessionToken: stsResp.Credentials.SessionToken,
}
})
I have recently started to work with the AWS SDK and how a user can authenticate with Cognito in an identity pool.
When querying the credentials I have the following question:
To what extent do the following approaches differ?
approach: getId() and getCredentialsForIdentity():
const cognitoidentity = new AWS.CognitoIdentity()
var params = {
IdentityPoolId: 'STRING_VALUE', /* required */
AccountId: 'STRING_VALUE',
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* '<IdentityProviderName>': ... */
}
};
cognitoidentity.getId(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
var params = {
IdentityId: 'STRING_VALUE', /* required */
CustomRoleArn: 'STRING_VALUE',
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* '<IdentityProviderName>': ... */
}
};
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // data AccesKeyId,Expiration, SecretKey, SessionToken
});
approach:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
Logins:{
'Provider': jwtToken
},
region: "eu-central-1"
});
AWS.config.credentials.get(function(){
// Credentials will be available when this function is called.
const{ accessKeyId} = AWS.config.credentials
const {secretAccessKey} = AWS.config.credentials
const {sessionToken} = AWS.config.credentials
})
the credentials of the two attempts are different, the SessionToken is the same. What is the difference between the SecretKey obtained in the first attempt and the SecretAccesKey obtained in the second attempt?
What are the differences between the two attempts?
The first attempt : enhanced simpliefied authflow with GetId and GetCredentialsForIdentity
https://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html
second attempt:
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-browser-credentials-cognito.html
I am grateful for any help :)
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html
According to this it is doing the same thing in the background.The difference in syntax for SecretKey and SecretAccesKey appears to be purely cosmetic. I have noticed the same in boto3. Regardless, you can pass either with accesskey and sessionid to authenticate other AWS services.
These are my first steps in AWS in general and Cognito specifically. So please bear with me as I'm a bit confused by all the concepts and documention is not very easy to follow.
So I set up 3 lambda functions, one that creates a user, one that confirms a user and a last one that is supposed to authenticate the user.
The first 2 work fine, my user is created an confirmed. Now I'm stuck with the 3rd one which is supposed to return a token to be used in APIG, where I've set up a simple endpoint with my cognito authorizer.
Every token I get back returns Unauthorized when tested in the APIG/Authorizers/Cognito Pool Authorizers section.
My 'sign in' code is the following:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.apiVersions = {
cognitoidentityserviceprovider: '2016-04-18'
};
AWS.config.region = 'us-east-1'; // Region
/*AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});*/
var identityId = null;
var params = {
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
IdentityId: identityId,
Logins: {
'login.auth.MYPROJECT': 'MY_USERNAME'
},
TokenDuration: 86400
};
var cognito = new AWS.CognitoIdentity({
region: AWS.config.region
});
cognito.getOpenIdTokenForDeveloperIdentity(params, function(err, data) {
if (err) {
return callback(err);
}
else {
/*AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: params.IdentityPoolId
});*/
AWS.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
callback(null, {
identityId: data.IdentityId,
token: data.Token,
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken
});
});
}
});
}
Both token and sessionToken return Unauthorized. Can someone tell me what is missing here?
Much appreciated.
EDIT 2016-11-15
The 'register' lambda code:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});
var poolData = {
UserPoolId : 'MY_USER_POOL_ID',
ClientId : 'MY_CLIENT_ID'
};
var userPool = new AWS.CognitoIdentityServiceProvider(poolData);
var email = "myemail+" + Math.floor(Math.random() * (100 - 1) + 1) + "#example.com";
var params = {
ClientId: 'MY_CLIENT_ID',
Password: '1234567890',
Username: 'testaccount' + Math.floor(Math.random() * (100 - 1) + 1),
UserAttributes: [
{
Name: 'email',
Value: email
}
]
};
userPool.signUp(params, function(err, result){
if (err) {
console.log(err)
return;
}
callback(null, {
"message": "Hello from Lambda",
"data": result
});
});
};
My 'activate' lambda code is the following:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});
var poolData = {
UserPoolId : 'MY_USER_POOL_ID',
ClientId : 'MY_CLIENT_ID'
};
var userPool = new AWS.CognitoIdentityServiceProvider(poolData);
var email = "email_address#example.com";
var params = {
ClientId: 'MY_CLIENT_ID',
Username: 'test_username',
ForceAliasCreation: false,
ConfirmationCode: '927000'
};
userPool.confirmSignUp(params, function(err, result){
if (err) {
console.log(err)
return;
}
callback(null, {
"message": "Hello from Lambda",
"data": result
});
});
};
In APIG, I created a Cognito User Pool Authorizer, selected my user pool, gave it a name, and set the identity token source to 'method.request.header.Authorization'.
In my APIG resource under the Method Request, I've set Authorization to my Cognito User Pool Authorizer. Additionally, API Key Required is set to true and I've a couple of keys I was testing with and that caused no issues.
I hope this covers everything.