aws-sdk send request with IAM Auth - amazon-web-services

I would like to make my Lambda function comunicate with a API Gateway...
However what I want is that the API Gateway checks the request with AWS_IAM... therefore the lambda function should in some way "sign" the request with a specific IAM token i suppose...
I was reading this https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html, but I'm not able to find any example on how to sign the request with a specific IAM User
(I've already created a User in IAM with AmazonAPIGatewayInvokeFullAccess and I have both access key and secret key, so I suppose I really need only the "how to sign the request")

We need to build regular nodejs request options with three additional parameters
service: "execute-api" for Api Gateway
region: "us-east-1" AWS Region.
body: postData , typically we pass body req.write, we also need it in options because it is needed for signing.
and finally the aws4.sign(...) is passed to request.
All .sign method does is adds 4 additional headers X-Amz-Content-Sha256, X-Amz-Security-Token, X-Amz-Date and Authorization
var aws4 = require("aws4");
var https = require("https");
const requestBody = { name: "test" };
var postData = JSON.stringify(requestBody);
var options = {
method: "POST",
hostname: "abcdefgh.execute-api.us-east-1.amazonaws.com",
path: "/qa",
headers: {
"Content-Type": "application/json",
},
service: "execute-api",
region: "us-east-1",
body: postData,
maxRedirects: 20,
};
const signedRequest = aws4.sign(options, {
secretAccessKey: "abcadefghijknlmnopstabcadefghijknlmnopst",
accessKeyId: "ABCDEFGHIJKLMNOPQRST",
sessionToken: "this is optional ==",
});
console.log(signedRequest);
var req = https.request(signedRequest, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on("error", function (error) {
console.error(error);
});
});
req.write(postData);
req.end();
since this call is made from lambda whole object with keys can be skipped and simply call aws4.sign(options), it should use from environment variables.

Related

AWS Lambda referer cropped

I have a Vue.js website on an S3 bucket, that uses API Gateway to run a Lambda function, all behind Cloudfront. Nothing special!
This was live and working circa 2019 and has been left on the backburner since then.
Jump forward to now, the Lambda function no longer works, it gets a referer that is different to what I used to get back in 2019.
2019: referer = https://xxxxxxxxxxxxxx.cloudfront.net/machine/12345
2022: referer = https://xxxxxxxxxxxxxx.cloudfront.net/
the "/machine/12345" is no longer part of the referer, is there any way to configure Cloudfront, Lambda or API Gateway to pass this through as before?
Edit:
My Lambda function is in node.js
Here's the code up until the failure point on 'split'
// Load the SDK for JavaScript
const AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'eu-west-1'});
const ddb = new AWS.DynamoDB.DocumentClient();
const cognitoClient = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
//Check for lambda invoked from pre-flight CORS OPTION request
console.log('event = ', event);
if(event.httpMethod == 'OPTIONS') {
callback(null, {
statusCode: 201,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Amz-Security-Token,Content-Type,X-Amz-Date,Authorization,X-Api-Key'
},
});
}
else
{
//Normal invocation
console.log('referer =', event.params.header.Referer);
//get machine name from event.params.header.Referer
//e.g. event.params.header.Referer = "http://localhost:8080/machine/12345/2";
var referer = event.params.header.Referer.split("machine/")[1].split("/");
//e.g. referer = [ '12345', '2'];
var selectedmachine = referer[0];
var pagenum = Number(referer[1]);

add aws signature to the postman script using pm.sendRequest

I would like to use a postman pre-fetch script to refresh my app secret from an api protected by aws signature. I am able to make a basic authentication like this. However I need an aws signature authentication
var url = "https://some.endpoint"
var auth = {
type: 'basic',
basic: [
{ key: "username", value: "postman" },
{ key: "password", value: "secrets" }
]
};
var request = {
url: url,
method: "GET",
auth: auth
}
pm.sendRequest(request, function (err, res) {
const json = res.json() // Get JSON value from the response body
console.log(json)
});
hi just create a normal postman request that work properly and then copy that request to a variable by adding the below line in test script
pm.environment.set("awsrequest", pm.request)
Now you can use the awsrequest variable to send use in pm.sendRequest
pm.sendRequest(pm.environment.get("awsrequest"))

List Users in a Group Cognito - Amplify - Lambda - API REST

I am struggle with AWS amplify to get the users list of a specific cognito group.
I always get this issue : localhost/:1 Access to XMLHttpRequest at 'https://ixa37ulou3.execute-api.eu-central-1.amazonaws.com/dev/users?groupName=xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I tried a lot of things, see my last try below :
First Step: creatation of my Lambda function using "amplify add function":
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({region:"eu-central-1"});
exports.handler = async (event) => {
const params = {
GroupName: event.groupName,
UserPoolId: 'eu-central-1_xqIZx0wkT',
};
const cognitoResponse = await cognito.listUsersInGroup(params).promise()
const response = {
statusCode:200,
headers:{
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify(cognitoResponse),
};
return response;
};
Second Step: Creation of my REST Api using "amplify add api" (pathname: /users)
Third Step: create in API Gateway a new authorizer "using cognito type and link to my user pool, and for the token source : Authorization. And for /users - ANY - Method Request => i added my authorizer in Authorization field.
Fourth Step: creation of my Calling API Function:
async function callApi(){
const user = await Auth.currentAuthenticatedUser()
const token = user.signInUserSession.idToken.jwtToken
console.log({token})
const requestInfo = {
headers:{
Authorization: token
},
queryStringParameters:{
groupName:"MyuserGroup"
}
}
const data = await API.get('apilistusers','/users',requestInfo)
console.log({data})
}
I would very much appreciate any help on this topic, thank you very much.
the current version of amplify has an option to create a lambda function for administrating cognito users, see "amplify add auth" and this page https://docs.amplify.aws/cli/auth/admin/

Lambda Authorizer fails with invalid token after generating using apigClientFactory

This is within a VUE project using VUEX.
The aim is to be able to control access to the API Gateway for both authenticated users and unauthenticated users.
In my Identity pool I can see that both authenticated and unauthenticated users are being picked up but I cannot get the Authorizer to accept the token that the SDK (aws-api-gateway-client) generates.
First I am getting accessKeyId etc
let AWS = require("aws-sdk");
AWS.config.region = env.aws.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId:
env.aws.aws_cognito_region +
":" +
env.aws.aws_cognito_identity_pool_id,
Logins: {
["cognito-idp." +
env.aws.aws_cognito_region +
".amazonaws.com/" +
env.aws
.aws_user_pools_id]: rootState.UserStore.authSession
.getIdToken()
.getJwtToken(),
},
});
AWS.config.credentials.get(function() {
commit("SET_ACCESS_CREDS", {
updatedAt: new Date(),
accessKeyId: AWS.config.credentials.accessKeyId,
secretAccessKey: AWS.config.credentials.secretAccessKey,
sessionToken: AWS.config.credentials.sessionToken,
});
});
These credentials looks good. I can output them and am happy they don't look broken.
Then I try to use these to access the api gateway using apigClientFactory.newClient
var apigClientFactory = require("aws-api-gateway-client").default;
var client = apigClientFactory.newClient({
accessKey: state.AccessCredentials.accessKeyId,
secretKey: state.AccessCredentials.secretAccessKey,
sessionToken: state.AccessCredentials.sessionToken,
region: env.aws.aws_cognito_region,
invokeUrl: process.env.VUE_APP_APIBASEURL,
});
console.log(client);
client.invokeApi({}, "/testauth", "GET").then(function(data) {
console.log(data);
});
My understanding here is that the SDK takes those values and generates what it needs to send to the API. I've read about it's algorithm, I don't understand it entirely but it implies it creates the access token and wraps it into a request.
The route testauth has the authorizer assigned.
The code for the authorizer is from the AWS doc
// A simple token-based authorizer example to demonstrate how to use an authorization token
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
// the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty
// string, the authorizer function returns an HTTP 401 status code. For any other token value,
// the authorizer returns an HTTP 500 status code.
// Note that token values are case-sensitive.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken;
switch (token) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token"); // Return a 500 Invalid token response
}
};
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval",
"numberKey": 123,
"booleanKey": true
};
return authResponse;
}
The cloudwatch logs for the authorizer say "Invalid token".
I don't see how it could be anything but that because all it's doing it just seems to be asking if the token says "allow" which seems a bit strange.
Am I by chance just using the wrong function to generate the token in this case?
It's surprising there isn't a comprehensive documentation on this type of requirement as it seems pretty standard.
In the end I am just validating the JWT token from Cognito which I got from here:
https://blog.mantalus.com/posts/apigw-custom-authorizer-cognito/
I do not use the API SDK

How do you pass query string parameters to the AWS API Gateway client with the Javascript SDK?

I have an endpoint defined in AWS API Gateway that uses a Lambda integration. The Lambda function expects query string parameters that would be available in the event object passed to it.
My API is at example.execute-api.us-east-1.amazonaws.com/dev/my-resource and I have query string parameters like foo=test.
So the full endpoint would be
example.execute-api.us-east-1.amazonaws.com/dev/my-resource?foo=test
I can visit this endpoint in a browser or request it in postman, and get the expected response, so I know that the API Gateway is configured properly. However when I use the Javascript SDK, I can't seem to pass query string parameters.
According to the last part of this page from the docs, I should be able to just pass in a JSON object that will be interpreted as query string parameters, like so:
var apiClient = apigClientFactory.newClient();
var requestParams = {"foo": "test"};
apiClient.myResourceGet(requestParams).then(function(result) {
// Do something with the response
});
However, in my case requestParams seems to be ignored. In the Lambda function, the event has an empty queryStringParameters field. How can I pass the key/values defined in the requestParams object as query string parameters to this endpoint?
since your following end point passing query param , you really no need json objet
example.execute-api.us-east-1.amazonaws.com/dev/my-resource?foo=test
create variable
var test = <assign value>
now
var params = {
host: "execute-api.us-east-1.amazonaws.com",
path: "/dev/my-resource?foo="+test
};
Example :
var https = require('https');
exports.handler = (event, context, callback) => {
var params = {
host: "bittrex.com",
path: "/api/v1.1/public/getmarketsummaries"
};
var req = https.request(params, function(res) {
let data = '';
console.log('STATUS: ' + res.statusCode);
res.setEncoding('utf8');
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
console.log("DONE");
console.log(JSON.parse(data));
});
});
req.end();
};