AWS SDK v3 Assume role for a client - amazon-web-services

I'm writing a Node JS app using AWS JS SDK v3. I am correctly using STS to assume role and retrieve credentials. The assume call response is such:
{
"$metadata": {
"httpStatusCode": 200,
"requestId": "xxx",
"attempts": 1,
"totalRetryDelay": 0
},
"Credentials": {
"AccessKeyId": "xxx",
"SecretAccessKey": "xxx",
"SessionToken": "xxx",
"Expiration": "2021-02-26T15:40:17.000Z"
},
"AssumedRoleUser": {
"AssumedRoleId": "xxx",
"Arn": "arn:aws:sts::xxx"
}
}
I am taking the credentials and passing them to a DynamoDBClient constructor (along with region).
import { DynamoDBClient, ListTablesCommand } from '#aws-sdk/client-dynamodb';
public getTablesList(region: string) {
const credentials = await getCredentialsFromSTS(region);
const clientParams = Object.assign(credentials, region); // This just merges two JSONs into one
const dbClient = new DynamoDBClient(clientParams);
const command = new ListTablesCommand({});
const response = await dbClient.send(command);
console.log(response);
}
What I get in the response is a list of only the tables of the account, that runs my code. The tables in the account where I am assuming the role are not present. Maybe I am assuming the role wrongly? I tried to package the credentials in a "Credentials" key, but that didn't help either.
const clientParams = Object.assign({ Credentials: credentials }, region);
Any idea what I am doing wrong here? Maybe the DDBClient is not assuming it as it should, but is there a way to check the assumed role for the DBClient? I found a namespace called RoleInfo in the documentation, but I don't know how to invoke it.

Please read the documentation of the DynamoDBClient parameter object: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property
The credentials fields are lower-case whereas in the STS result they are upper-case.
There are also more errors in your code, such as the Object.assign(credentials, region); call. region here is a string, so by assigning it into credentials you will not get a property named "region" with the value of that region variable, but you will get numeric properties, each with one character of the region string.
The correct initialization should be:
const credentials = await getCredentialsFromSTS(region);
const dbClient = new DynamoDBClient({
region: region,
credentials: {
accessKeyId: credentials.AccessKeyId,
secretAccessKey: credentials.SecretAccessKey,
sessionToken: credentials.SessionToken,
}
});

Related

Permission Issue at an AWS API using Lambda

I'm testing my newly deployed AWS API using https://www.apitester.com/.
As you can see i cant access the API. The API is deployed and the Lambda code looks as following.
const AWS = require('aws-sdk');
var bucket = new AWS.S3();
exports.handler = (event, context, callback) => {
let data =JSON.parse(event.body);
var params = {
"Body": data,
"Bucket": "smartmatressbucket",
// "Key": filePath
};
bucket.upload(params, function(err, data){
if(err) {
callback(err, null);
} else {
let response = {
"statusCode": 200,
"headers": {
"my_header": "my_value"
},
"body": JSON.stringify(data),
"isBase64Encoded": false
};
callback(null, response);
}
});
};
Looking at the response log, it seems the API Gateway generates "ForbiddenException". I believe the most possible reason is using an incorrect API URL (eg- https://ogk2hm09j0.execute-api.eu-central-1.amazonaws.com/).
Suppose you configure the Lambda function to a GET method of a resource name "resourceA". Then you deploy the API to a stage named "dev". Then the correct URL should be https://ogk2hm09j0.execute-api.eu-central-1.amazonaws.com/dev/resourceA
But looking at the API URL in the logs, it seems the stage name or the resource name is not specified.

How to use assume role credential in dynamodb (aws-sdk javascript)?

I already have aws assume role credentials in .aws/credetials file.
how to use it to creat sts or dynamodb like:
const { DynamoDB } = require('aws-sdk');
const { DocumentClient } = DynamoDB;
const dynamo = new DynamoDB({
endpoint: process.env.AWS_ENDPOINT,
region: process.env.AWS_REGION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
secretToken: process.env.aws_security_token
});
I mean I got error as:
root#myubuntu:~/work/contacts_api# node ./seed/runner.js
```
Checking if 'contacts' table exists
{ UnrecognizedClientException: The security token included in the request is invalid.
at Request.extractError (/root/work/contacts_api/node_modules/aws-sdk/lib/protocol/json.js:51:27)
at Request.callListeners (/root/work/contacts_api/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/root/work/contacts_api/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/root/work/contacts_api/node_modules/aws-sdk/lib/request.js:683:14)
at Request.transition (/root/work/contacts_api/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/root/work/contacts_api/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /root/work/contacts_api/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request. (/root/work/contacts_api/node_modules/aws-sdk/lib/request.js:38:9)
at Request. (/root/work/contacts_api/node_modules/aws-sdk/lib/request.js:685:12)
at Request.callListeners (/root/work/contacts_api/node_modules/aws-sdk/lib/sequential_executor.js:116:18)
message: 'The security token included in the request is invalid.',
code: 'UnrecognizedClientException',
time: 2019-01-07T05:39:54.907Z,
requestId: 'A5CFV62P0TGHJH7VDIBSL0JRC3VV4KQNSO5AEMVJF66Q9ASUAAJG',
statusCode: 400,
retryable: false,
retryDelay: 5.013458338738063 }
```
I want to know the correct way to initial credetials, if I want to use mfa credetials.
I'm guessing that the error here should give you a clue:
"The security token included in the request is invalid"
Did you try printing out the environment value
env | grep aws_security_token
If it's empty you'll have to set the value prior to running your code.
Also, I've noticed that your other aws keys are all caps whereas your aws_security_token is all lowercase.
I suspect secretToken isn't a thing. Here are two examples of how it could be done (how I've done it before).
That said I would encourage the construction and use of a Credentials where ever possible (the second example), but if you wanted to do it inline- that should work too.
/** assume a role and build a DocumentClient object to make a single scan **/
;(async () => {
const sts = new AWS.STS()
const assumeRole = await sts
.assumeRole({
RoleArn: process.env.ROLE_ARN,
RoleSessionName: process.env.ROLE_SESSION_NAME,
})
.promise()
const dynamodb = new AWS.DynamoDB.DocumentClient({
region: process.env.REGION,
credentials: {
accessKeyId: assumeRole.Credentials?.AccessKeyId,
secretAccessKey: assumeRole.Credentials?.SecretAccessKey,
sessionToken: assumeRole.Credentials?.SessionToken,
},
})
const scan = await dynamodb
.scan({
TableName: process.env.TABLE_NAME,
})
.promise()
console.log(scan)
})()
/**
* assume a role and build a Credentials object and use it
* to build a DocumentClient object to make a single scan
**/
;(async () => {
const sts = new AWS.STS()
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,
})
const scan = await dynamodb
.scan({
TableName: process.env.TABLE_NAME,
})
.promise()
console.log(scan)
})()

Firebase as Identity Provider with Cognito / AWS

I am having a hard time using Firebase as an Open ID Connect provider.
Can you please further describe the steps you have been through before and after to make this work?
For information, here is what I have done so far:
In AWS Console:
1 - Create an IAM Identity Provider ( OpenID Connect ) and used securetoken.google.com/<FIREBASE_PROJECT_ID> as an URL, <FIREBASE_PROJECT_ID>for Audience
2 - Checked the Thumbprint manually (it matches the one generated by AWS)
3 - Created a role with the permissions to access the desired services
4 - Created an Identity Pool in Cognito and selected my newly created role in the 'Authenticated role' Dropdown
5 - Selected my Identity Provider under the Authentication Providers > OpenID category (format is therefore): securetoken.google.com/<FIREBASE_PROJECT_ID>
In my code (I am using Vue.js) here are the logical steps I went through:
Import / setup AWS SDK
Invoke Firebase Auth service
Create a new CognitoIdentity
Use the getOpenIdTokenForDeveloperIdentity and push the tokenID received from Firebase
The issue is that I keep getting "Missing credentials in config" errors.
The code:
import axios from 'axios';
const AWS = require('aws-sdk');
AWS.config.region = 'eu-west-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
});
export default {
name: 'My Vue.js component name',
data() {
return {
email: '',
password: '',
msg: '',
};
},
methods: {
submit() {
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=MY_KEY',
{
email: this.email,
password: password,
returnSecureToken: true,
},
)
.then((res) => {
// stores tokens locally
localStorage.setItem('jwt', JSON.stringify(res.data));
const cognitoidentity = new AWS.CognitoIdentity();
const params = {
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/<PROJECT_ID>': res.data.idToken,
},
IdentityId: null,
TokenDuration: 3600,
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
});
},
},
};
Here are the resources I have used so far while attempting to make this work:
http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
Using Firebase OpenID Connect provider as AWS IAM Identity Provider
https://github.com/aws/amazon-cognito-identity-js/blob/master/examples/babel-webpack/src/main.jsx
http://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-2-developer-authenticated-identities/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-3-roles-and-policies/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-4-enhanced-flow/
The final code if that can be any help for anyone:
import axios from 'axios';
const AWS = require('aws-sdk');
const aws4 = require('aws4');
export default {
name: 'VUE_CPNT_NAME',
data() {
return {
email: '',
password: '',
msg: '',
idToken: '',
};
},
methods: {
submit() {
// Firebase SignIn API
// Doc: https://firebase.google.com/docs/reference/rest/auth/
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[MY_KEY]',
{
email: this.email,
password: this.password,
returnSecureToken: true,
},
)
.then((res) => {
this.idToken = res.data.idToken;
localStorage.setItem('jwt', JSON.stringify(res.data));
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: {
'securetoken.google.com/<FIREBASE_PROJECT_ID>': res.data.idToken,
},
}, {
region: 'eu-west-1',
});
// AWS.config.crendentials.get() methods works as well
// or a call to cognitoidentity.getId() followed by a call to getCredentialsForIdentity()
// will achieve the same thing. Cool. But why!?
AWS.config.getCredentials((err) => {
if (err) {
console.log(err);
}
const request = {
host: 'API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com',
method: 'GET',
url: 'https://API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com/PATH',
path: '/API_ENDPOINT_PATH',
};
// Signing the requests to API Gateway when the Authorization is set AWS_IAM.
// Not required when Cognito User Pools are used
const signedRequest = aws4.sign(request,
{
secretAccessKey: AWS.config.credentials.secretAccessKey,
accessKeyId: AWS.config.credentials.accessKeyId,
sessionToken: AWS.config.credentials.sessionToken,
});
// removing the Host header to avoid errors in Chrome
delete signedRequest.headers.Host;
axios(signedRequest);
});
});
},
},
};
Try setting the login map i.e the firebase token in the CognitoIdentityCredentials object. See this doc.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/':
}
});
Try calling get method on the credentials object before initializing the Cognito client. You can also use getCredentials instead.
If the above steps do not work & they should, pass the credentials as an option while initializing the Cognito client. See this doc for options available while using the CognitoIdentity constructor.
const cognitoidentity = new AWS.CognitoIdentity({credentials: AWS.config.credentials});
If you still receive the error, try logging credentials object in the console after calling the get() method. Ideally, it should have temporary credentials (accessKey, secretKey & sessionToken)

How to get AWS credentials with a UserPools idToken?

Im currently using a USER-POOLS authorizer to get the first 3 tokens for my API:
idToken
refreshToken
accessToken
From here I would like to request credentials to be able to SigV4 request to my already set up API gateway, but first I need to get the requested credentials in order to do the SigV4.
In the docs I found this:
// Set the region where your identity pool exists (us-east-1, eu-west-1)
AWSCognito.config.region = 'us-east-1';
// Configure the credentials provider to use your identity pool
AWSCognito.config.credentials = new AWSCognito.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:009xxxx ...',
});
// Make the call to obtain credentials
AWSCognito.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWSCognito.config.credentials.accessKeyId;
var secretAccessKey = AWSCognito.config.credentials.secretAccessKey;
var sessionToken = AWSCognito.config.credentials.sessionToken;
});
To my surprise, the callback is called but the values for the
- accessKeyId
- secretAccessKey
- sessionToken
are all null.
I was expecting some kind of method, where I send my first idToken, and based on that I get the credentials, but it looks like this is all figured out under the hood?, anyways it is not working for me.
After some research, I realised that there is an undocumented way of doing this.
You need to construct this object first:
let url = 'cognito-idp.' + 'identity pool region' + '.amazonaws.com/' + 'your user pool id';
let logins = {};
logins[url] = idTokenJwt; // <- the one obtained before
let params = {
IdentityPoolId: 'the federated identity pool id',
Logins: logins
};
let creds = new AWS.CognitoIdentityCredentials(params);
AWS.config.region = 'us-east-1';
AWS.config.credentials = creds;
creds.get(function (err: any) {
if (!err) {
console.log("returned without error"); // <-- this gets called!!!
// and the values are correctly set!
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
}
else{
console.log("returned with error"); // <-- might get called if something is missing, anyways self-descriptive.
console.log(err);
}
});
In my case I still had to configure the trust relationship between the role and the identity pool, here the example:
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "your federated identity pool id"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
*You can also replace "authenticated" with "unauthenticated", "graph.facebook.com", "google ...", depending your needs.

How to call AWS API Gateway Endpoint with Cognito Id (+configuration)?

I want to call an AWS API Gateway Endpoint that is protected with AWS_IAM using the generated JavaScript API SDK.
I have a Cognito UserPool and a Cognito Identity Pool. Both properly synced via ClientId.
I use this code to Sign in and get the Cognito Identity
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXXXXXX' // your identity pool id here
});
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXXXXXX' // your identity pool id here
});
var poolData = {
UserPoolId: 'us-east-1_XXXXXXXX',
ClientId: 'XXXXXXXXXXXXXXXXXXXXXXXX'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationData = {
Username: 'user',
Password: '12345678',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: 'user',
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXX',
IdentityId: AWS.config.credentials.identityId,
Logins: {
'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXX': result.idToken.jwtToken
}
});
AWS.config.credentials.get(function (err) {
// now I'm using authenticated credentials
if(err)
{
console.log('error in autheticatig AWS'+err);
}
else
{
console.log(AWS.config.credentials.identityId);
}
});
},
onFailure: function (err) {
alert(err);
}
});
All this succeeds and I have an authorized Cognito Identity now.
Now I try to call the API Gateway Endpoint to execute the Lambda Function it points to.
var apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId, //'ACCESS_KEY',
secretKey: AWS.config.credentials.secretAccessKey, //'SECRET_KEY',
sessionToken: AWS.config.credentials.sessionToken, // 'SESSION_TOKEN', //OPTIONAL: If you are using temporary credentials you must include the session token
region: 'us-east-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1
});
var params = {
// This is where any modeled request parameters should be added.
// The key is the parameter name, as it is defined in the API in API Gateway.
};
var body = {
// This is where you define the body of the request,
query: '{person {firstName lastName}}'
};
var additionalParams = {
// If there are any unmodeled query parameters or headers that must be
// sent with the request, add them here.
headers: {},
queryParams: {}
};
apigClient.graphqlPost(params, body, additionalParams)
.then(function (result) {
// Add success callback code here.
console.log(result);
}).catch(function (result) {
// Add error callback code here.
console.log(result);
});
But unfortunately this fails. The OPTIONS request succeeds with 200 but the POST then fails with 403.
I am pretty sure that there is no CORS problem here.
I am pretty sure the problem has to do with IAM Roles and AWS Resource Configurations.
My question is basically, can you please provide me with all the necessary AWS Resource Configurations and IAM Roles that are necessary for this to work please?
Resources I have are
API Gateway - with deployed API Endpoints
Lambda Function - called by the Endpoint
Cognito User Pool - with App synced to the Identity Pool
Cognito Identity Pool - with Authorized and Unauthorized Role mapped to it.
IAM Roles - for the Lambda Function and the Authorized and Unauthorized Role of the Cognito Identity Pool.
But I don't know how these Resources need to be configured properly to get this to work.
Thank you
What access permissions does the role of the Cognito Identity have? Make sure it has access to perform execute-api:Invoke on your API.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:us-east-1:<account>:<rest-api>/*/POST/graphql"
]
}
]
}
You can get the exact resource ARN from the method settings page in the web console.
Even after following everything I was getting the same error. And the reason was I missed the "sessionToken" while initialising the apigClient.
var apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId, //'ACCESS_KEY',
secretKey: AWS.config.credentials.secretAccessKey, //'SECRET_KEY',
sessionToken: AWS.config.credentials.sessionToken, // 'SESSION_TOKEN', //OPTIONAL: If you are using temporary credentials you must include the session token
region: 'us-east-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1 });
//OPTIONAL: If you are using temporary credentials you must include the session token -- is not really optional