Cloudfront and multiple Lambda#Edge functions - amazon-web-services

I have a Cloudfront distribution pointing at a custom url.
In that distribution, I have setup 2 lambda function associations.
1 on the Cloudfront event of viewer-request to query parameter store and redirect to the correct url
The other on the Cloudfront event of origin-response to gather response stats from the call.
The viewer-reqest function is as follows - note dummy urls for purpose of question
exports.handler = async (event, context) => {
const origin = "main";
const primaryUrl = "https://www.google.com";
const drUrl = "https://www.yahoo.com";
let url = primaryUrl;
if (origin != "main") {
url = drUrl;
} else {
url = primaryUrl;
}
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: url,
}],
},
};
return response;
};
The origin response lambda is very simple for now
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var cloudwatch = new AWS.CloudWatch();
exports.handler = async (event, context) => {
console.log("Custom Metrics Event");
console.log(JSON.stringify(event));
const response = {
status: '200',
};
return response;
};
When I call the cloudfront distribution domain, the viewer-request lambda#edge kicks in but the origin-response lambda does not trigger and I see nothing in cloudwatch
If I remove the viewer-request lambda - the origin-response lambda does trigger
Am I doing anything wrong here or does anyone have any recommendations?
Thank you
Damien

#ChrisWilliams comment was accurate
I changed my code to perform the redirect in the origin response and all worked fine then

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]);

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/

aws-sdk send request with IAM Auth

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.

AWS CDK CORS error with Cloudfront + Static Website on S3 + API Gateway + Lambda + DynamoDb setup

I am using AWS CDK (v1.87.1 (build 9eeaa93)) to define my infrastructure as code. I use C# to define my CDK stack(s).
I have my data stored in DynamoDb and an API gateway backed by Lambda functions to read/write to the DynamoDb. This is my backend.
My frontend is a simple static website (HTML + JS) hosted on AWS S3 distributed through CloudFront.
My API works fine when I test it independently with curl or in the AWS console. However, when I call the API using the fetch() browser API from within my static website page, I get the following error (in the browser):
Access to fetch at
'https://xxxxxxxx.execute-api.ap-south-1.amazonaws.com/prod/Account'
from origin 'https://abcdefg.cloudfront.net' has been blocked by
CORS policy: No 'Access-Control-Allow-Origin' header is present on the
requested resource. If an opaque response serves your needs, set the
request's mode to 'no-cors' to fetch the resource with CORS disabled.
My CorsOptions are defined as follows:
var defaultCorsPreflightOptions = new CorsOptions() {
AllowOrigins = Cors.ALL_ORIGINS,
AllowMethods = Cors.ALL_METHODS,
AllowHeaders = new [] {"*"},
AllowCredentials = true,
MaxAge = Duration.Days(0)
};
My API is as follows:
var api = new RestApi(this, "my-api", new RestApiProps {
RestApiName = "My Service",
Description = "This is the service API"
});
My resource creation adds the CorsOption for preflight (In the above error message 'Account' would be a resource added to the root):
var resourceType = api.Root.AddResource(ent);
resourceType.AddCorsPreflight(defaultCorsPreflightOptions);
My lambda handler also has
if(method == "OPTIONS") {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,PUT,GET,DELETE"
}
};
return response;
} else if(method === "POST") {
// ... omitted
}
The JS client code that calls the REST API is:
const response = await fetch(`${api_url}/${entity}`,{
method: 'POST',
mode: 'cors',
body: item,
headers: {
'Content-Type': 'application/json'
}
});
The Behavior that is attached to the CloudFront distribution:
// The cloudfront distribution for the website
var behavior = new Behavior() {
IsDefaultBehavior = true,
AllowedMethods = CloudFrontAllowedMethods.ALL,
MaxTtl = Duration.Seconds(0),
MinTtl = Duration.Seconds(0),
DefaultTtl = Duration.Seconds(0),
Compress = false,
ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
QueryString = true,
Headers = new [] {"Authorization", "Access-Control-Allow-Origin"}
}
};
My CloudFront distribution is as follows:
var distribution = new CloudFrontWebDistribution(this, "StaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
OriginConfigs = new [] {
new SourceConfiguration() {
S3OriginSource = new S3OriginConfig() {
S3BucketSource = bucket
},
Behaviors = new [] {
behavior
}
}
}
});
My S3 Bucket deployment code is:
// The S3 bucket deployment for the website
var deployment = new BucketDeployment(this, "WebsiteDeployment", new BucketDeploymentProps(){
Sources = new [] {Source.Asset("./website")},
DestinationBucket = bucket,
Distribution = distribution
});
I have tried looking into the AWS CDK documentation. I have tried adding the default CORS option at the API level also, but without any success. What am I missing? Please help.
I figured it out. The error was was in the Lambda handler for the POST / GET / DELETE / PUT methods. I needed to return the headers (example given below):
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*", // I was missing this
"Content-Type": "application/json" // and this
},
body: JSON.stringify({"id":`${id}`}) // and this was a string earlier
};
I had another error in the client side with the fetch() response handling. I was using response.json() whereas it should have been response.text() (since I was sending text in the response body earlier).
I was misled by the curl response (in my testing) which was just the plain text whereas there was a JSON parse issue with handling the fetch() response.
Key takeaway: Check your Lambda handler responses.

How do I get response headers for AWS JavaScript SDK calls?

I have a simple AWS Lambda function which makes an S3.getObject() call as follows:
const AWS = require('aws-sdk');
AWS.config.logger = console;
const s3 = new AWS.S3();
exports.handler = async (event) => {
return await getObject({
Bucket: "<MY-BUCKET>",
Key: "<MY-KEY>"
}).then( (res) => {
console.log('Retrieved object from S3');
console.log(res);
return res.Body.toString('ascii');
})
};
async function getObject(params){
return await s3.getObject(params).promise();
}
I've enabled logging SDK calls as per this document.
How do I get response headers for the s3.getObject() SDK call that was made? I am basically trying to retrieve the S3 request ID and extended request ID.
The in-built logger added via the "AWS.config.logger = console;" line does not seem to log response headers. How else do I get response headers for AWS JavaScript SDK calls?
P.S: Bonus points if you can let me know whether or not I need two await keywords in the code above.
Listen to httpHeaders event.
var requestObject = s3.getObject(params);
requestObject.on('httpHeaders', (statusCode, headers, response, statusMessage) => {
// your code here.
});
requestObject.promise()
.then(response => { ... })