Geolocation service with AWS API Gateway and Lambda - amazon-web-services

What we are trying to do
We are trying to set up a very simple Geolocation service with API Gateway and Lambda.
Very similar to https://ipstack.com/, but we don't want to use an external service as we believe it could be an issue in some jurisdictions to send a non-anonymized IP address to a service we don't control (before getting the user's consent).
Would like to have a simple api https://location.my-site.com that returns the country (for GDPR, cookies, etc purposes).
Now it seems that there is a light Cloudfront behind API Gateway that would produce the header "Cloudfront-Viewer-Country", which would be very simple and achieve what we need. i.e. lambda receives Cloudfront-Viewer-Country and just sends it back.
What we have tried
I have seen solutions such as this one: Build a Geolocation API using AWS Lambda and MaxMind, but I struggle to see why deploying an RDS and maintaining the MaxMind database would make sense for us, if it is already available from Cloudfront-Viewer-Country.
I have seen this question: Accessing cloudfront-viewer-country header in AWS API Gateway using HTTP Proxy?, and tried implementing the answer from Michael - sqlbot. But I cannot seem to access the headers.
I have also tried what is suggested in this post, but I can't seem to access the value of Cloudfront-Viewer-Country either.
What we are doing (in conjunction with 'What we have tried')
To access and check if the header is available I am using the following python lambda function
import json
def lambda_handler(event, context):
response = {
'status': '200',
'statusDescription': 'Found',
'headers': {
'location' : [ {
'event': json.dumps(event)
} ]
}
}
return response
What the problem is
but the event json dump doesn't contain Cloudfront-Viewer-Country.
I suspect I'm doing something wrong but I really can't figure it out. Any pointer would be very much appreciated.
Thank you

I was able to get access to Cloudfront-Viewer-Country by setting a Endpoint Type = Edge optimized.
I could not get it to work with Endpoint Type = Regional or with http api gateway.

Related

How to set the api version being called in API Gateway when integrating with AWS Service CloudWatch?

I get the following error message when calling actions for CloudWatch in API Gateway.
"Error": {
"Code": "InvalidAction",
"Message": "Could not find operation DescribeAlarms for version 2009-05-15",
"Type": "Sender"
}
I've been using DescribeAlarms for testing. My setup is as follows.
Integration Type = AWS Service
AWS Service = CloudWatch
HTTP method = POST
Action = DescribeAlarms
The error references the API Version 2009-05-15, which only has ListMetrics and GetMetricStatistics according to it's documentation on page 54. ListMetrics does indeed work as expected with my setup.
The current version is 2010-08-01 but I don't see anyway to reference that in API Gateway. In an example of a POST request in the documentation it shows a header labeled x-amz-target with a value of GraniteServiceVersion20100801.API_Name.
My interpretation is I can put Name = x-amz-target and value 'GraniteServiceVersion20100801.DescribeAlarms' in my http header for the Integration Request in API Gateway.
This doesn't change the response and gives the same error message.
I also used the --debug in CLI when calling describe-alarms, and in the body it shows...
"body": {
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
So I also set http headers to include Content-Type with a value of 'application/x-amz-json-1.1' and then put in
{
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
but nothing changed with that either.
Any help or guidance would be greatly appreciated.
Under Method Integration -> URL Query String Parameters
I added Version as the Name and '2010-08-01' under Mapped From.
All actions are now working as expected.
I'm trying to PutMetrics directly from Api Gateway -> Cloudwatch using PutMetricData, Version in the query string params didn't work for me.
These 3 HTTP headers in the Integration Request solved it for me:
Content-Type 'application/json'
X-Amz-Target 'GraniteServiceVersion20100801.PutMetricData'
Content-Encoding 'amz-1.0'

Cannot get AWS API Gateway to override response codes - FitBit endpoint verification

I am integrating FitBit with my company's platform and we are switching over from syncing with our own server to sending the data to an AWS Kinesis stream. This requires us to also set up an AWS API Gateway with a POST method to write the data to the stream. I've also set up a GET method on the same resource for the verification process.
Here's the problem I'm facing:
Once I have the API Endpoint properly set up, FitBit provides a verification code and requires a verification process in which it sends a GET request to the endpoint with a ?verify={correctVerificaitonCode} query param and wants a 204 response, and one with a ?verify={incorrectVericationCode} param and wants a 404 response. This would obviously be easy for me to accomplish in our Rails backend, where I'm in control of the code, but on AWS it's a tangled mess with little control.
I have read endless documentation on AWS about Mapping Templates and Integration Response, but no matter what I do, I cannot get the API to respond with anything other than a 200 (when the request is clean and has any ?verify param) or 500 (when I purposefully make a bad request). There is no straightforward answer in the AWS docs about this.
This is the closest I have come to a setup that the docs promise should work, yet it does not:
Using the Integration Response HTTP Status Regex
And with this mapping template
I'm two days in on this and frustrated to my wits' end. Help!
Just in case anyone find this thread in the future and is struggling with the same issue - here is how you verify a FitBit Developer API app with an Amazon Kinesis stream being fed by an AWS API Gateway:
First, set up the POST method of your API - there are AWS guides to this. Select AWS service as the integration type and kinesis as the service, then set up a mapping template for 'application/json' to look like this:
#set($event = $input.body)
#set($data = '{"action":' + $event +', "authorization": "' + $input.params('Authorization') + '", "stage":"' + $context.stage + '"}')
#set($body = $util.base64Encode($data))
{
"Data": "$body",
"PartitionKey": "shard-1",
"StreamName": "gm-fitbit"
}
Once you've done that, create a GET method on the same resource. Set MOCK as the integration type and create the endpoint. Now click on the GET method and visit Method Request. Expand URL Query String Parameters and add verify as a query param. Now, go back to the method and visit Integration Response.
Under the already existing 200 response method, expand it and add an HTTP status regex of 2\d{2} and passthrough handling.
Expand Mapping Templates, and for 'application/json' create this mapping template:
{
#if( $input.params('verify') == "theVerificationCodeProvidedToYouByFitbit" )
#set($context.responseOverride.status = 204)
#else
#set($context.responseOverride.status = 404)
#end
}
That's it! Deploy the API again, head back to Fitbit, and click verify!
There. Now there is officially a guide online to integrating Fitbit with an AWS Kinesis stream, the one I wish I had when struggling with this for 3 days.
Cheers!

API Gateway - Get StatusCode

This should have been such a simple issue and I don't understand why it hasn't come up through all my searching (maybe it's just been a long day).
I have an API Gateway API setup, and I am adding a Body Mapping Template to my Integration Response for a 400* error group: see image -
All I would like to get is the StatusCode of the current response (as this is a 400* group - e.g. 401 / 403 / 404 etc.)
The closest I came was through this site: AWS help documentation and I thought I would be able to use something like $context.statusCode - but no luck.
Am I going crazy, or is this just not something required often?
PS - Making changes to any Lambda functions being called, is not an option.
Thanks
There's currently no mapping template variable in API Gateway dedicated to the integration response status code.
We will certainly add this as a feature request.
At current time you are limited to hardcoding the status code value in your response templates. You would either need to define generic status codes (i.e "4XX") or define integration responses for every status code you want to capture. While this seems tedious, this could be managed relatively easily in a Swagger template.
At current time the only way to see the integration response status code is via CloudWatch Logs.
Thanks,
Ryan / Amazon API Gateway
If you are sending error codes from your server then you can easily map them.
I have done something similar but I have used different trick to do. I used to send my own error entities and codes from server.
You have to map those error entities and error codes coming from server to the response that comes from amazon servers. I will try and explain what I mean by this. Api Gateway doesn't send response coming from your own server to the client automatically. You have to map those responses. For example, map 200 as a SUCESS and response entity will be default, that is whatever coming from server.
Now, we default success response is managed but what about error codes and error entities. You have to map them manually.
There are two ways you can do this,
One is manual, go to your api. Create error entities or models. Map them manually for each response code.
This one uses swaggger,
Solution is to import swagger specification of error entities. Add response templates to the swagger specification and let amazon do their job.
I can help you more with swagger. It depends how you are setting up your api on amazon.
Visit this for amazon extenstions to swagger,
http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html#api-gateway-swagger-extensions-integration-response

API Gateway CORS Issue

So I have CORS enabled going through the basic setup given by AWS Gateway. However for this API I need to allow Control Origins for all requests and allow credentials.
Here is what it looks like
The issue as you may have guessed is this setup is not allowed by CORS, you can not have a wildcard for Origin and have credentials as true. Normally the work around for this is to just grab the requesting domain and add it into the Origin Header. Which is more or less what I want to do. But I don't know how to get that information and add it as a mapping value. Where does API Gateway store that information and how do i get it?
UPDATE:
I have to pass through HTTP Header Host to my Lambda Function which I should have mentioned earlier, I have tried implementing the Answer below but I cannot access the header to pass it to the Lambda function using the instructions provided. Any more assistance with this is greatly appreciated.
Okay After hours of research and finding bits of information across the internet I have a solution and hopefully it is useful for other people.
To pass an HTTP Header that is not a default value provided by AWS API Gateway and then access that data via a Lambda Function and return that data in the Response Header follow the steps below
In "Method Request" go to "HTTP Request Headers" and add your desired header to capture. ie. if we want to get the host value of the API url you can enter "Host" here. if you want to get the website host of the caller use "Origin"
In "Integration Request" go to mapping templates and create a new template if an "application/json" does not exist, if it does just update it.
This is the important part, pass the header value you set in step 1. To do that write something similar to the following in the Template Box.
{
"origin" : "$input.params().header.Origin",
"host" : "$input.params().header.Host"
}
You can also pass in any url parameters you have defined in the same JSON.
Access the data from Lambda, The integration request passed the information into the "Event" parameter if using Node as the Lambda Backend code. to retrieve the value of any header just use the following within your handler.
event.origin;
When sending back your response from Lambda to API Gateway, it is best to format the response in JSON. Something similar to this.
{
"origin" : event.origin,
"host" : event.host,
"nonHeaderOutput" : "Hello World"
}
In "Integration Response" go to "Header Mappings", if the header you need is not listed you may add it in "Method Response" and it will then appear here. For this example I used "Access-Control-Allow-Origin" and edited the Mapping Value to be integration.response.body.origin
now go to "Mapping Templates and select the content type you want to use, and then edit the template to access the non header responses by adding this to the Template Box
$input.path("$.nonHeaderOutput")
That is it now the header that was sent to the API can be used in your method Response.
Jurgen from API Gateway here.
Thanks for reporting this issue. There is currently no simple way to set it up via the "Enable CORS" feature in the console. However, we are looking into it to improve the user experience for this use case.
As a potential workaround, you could passthrough the Origin header from the client to your backend and parse / create the value for the Access-Control-Allow-Origin header there. Then you would map the Header in the Integration Response from 'integration.response.header.Access-Control-Allow-Origin' to Access-Control-Allow-Origin and return it to the client.
Hope this helps.
Best,
Jurgen

Can you statically set a Header and it's value when setting up an HTTP Proxy using AWS Api Gateway endpoint?

I am creating an http proxy using AWS Api Gateway. I would like to hard code some of the headers and their values to be forwarded as part of the request. I thought this might be possible in the 'Integration Request' portion of the proxy setup, but I can't seem to figure it out.
I'm trying to pass an Authorization header with an oauth key. I don't want to share this key with clients that have access to this service, since I will only provide a subset of access to users of this specific endpoint.
In the Integration Request, you can configure a static header value to be sent to the integration endpoint by putting the value inside of single quotes, e.g. 'my_static_header_value'.
Is it a problem to put those hardcoded headers in the request body ? It not, you could just use a template (in the integration request screen) :
{
"hardcoded_header": "$input.params('hardcoded_header')"
}
Hope this helps.