2

I was using this technique (How could I retrieve AWS Lambda public IP address by using Python?) but it gives the IPAddress of the Lambda Server within AWS.

Based on this: How can I retrieve a user's public IP address via Amazon API Gateway + Lambda (node), it looks like I should be able to use

ip_address = event['requestContext']['identity']['sourceIp'];

My handler starts like this:

def lambda_handler(event, context):

but if I do a pprint.pprint(event), I don't see any RequestContext in it, only the "body".

The last comment by FFXSam on the Jonathan answer says "It should be noted that event.requestContext.identity is not present if you're serving a page that's not behind an authorizer.". I'm not sure what that means or why it is true. I'm using API Gateway, and JavaScript code on the client side is calling it.

I could ask the client coder to send me the local IP Address in the body, but it seems like I should be able to get it in the Lambda function itself.

Someone ask for the events, even though I said it only had the fields being passed in a json element called "body":

code:

print("pprint event:")
pprint.pprint(event)


2021-06-06T13:30:01.231-05:00   pprint event:
2021-06-06T13:30:01.231-05:00   {'body': {'ResponseTimeMilliseconds': 2225,
2021-06-06T13:30:01.231-05:00   'authToken': '12312312',
2021-06-06T13:30:01.231-05:00   'handNumber': 7}}
NealWalters
  • 17,197
  • 42
  • 141
  • 251
  • Check if you have attached authorizer in API Gateway via `aws apigateway get-authorizers --rest-api-id YOUR-API-ID` or in console under Authorizers section – Jenya Y. May 31 '21 at 19:20
  • @JenyaY. - no authorizer – NealWalters Jun 01 '21 at 16:42
  • If I add an "authorizer" that means the client code has to change, right? Does adding an authorizer automatically make the IP Address work, and if so why? – NealWalters Jun 04 '21 at 13:27
  • Read the 2nd answer in the post you linked: https://stackoverflow.com/a/46021715/13070 Also note that it is entirely dependent on the API Gateway integration type you have selected, and the mapping template, or request passthrough, options you have selected. Your question is really a duplicate of that other question. You need to take the time to read the answers in full, including the information about mapping templates. – Mark B Jun 04 '21 at 13:34
  • MarkB - I did a pprint on the events object, and didn't see anything like that there. I'll check again when I get a chance. Looks like maybe the context might have it thought, one of the other answers: $context.identity.sourceIp – NealWalters Jun 04 '21 at 16:36
  • Can you show the content of your `events`? – Marcin Jun 05 '21 at 01:44
  • I have added my "events" from a pprint statement. I am NOT using an authorizer. the context. There is not 'headers' section. Looks like $context.identity.sourceIp is only available if you are using an API-Gateway authorizer, which I am not. @Marcin – NealWalters Jun 06 '21 at 18:35
  • @MarkB - I had not idea what a mapping template was until I dug into this further (I was pretty good with Lambda, but my knowledge and experience with API Gateway is abysmal). So anybody that doesn't know API gateway and what templates are would not understand that answer in question I referred to. I have included a more lengthy and thorough answer for newbies. – NealWalters Jun 08 '21 at 00:21

3 Answers3

11

I rewarded bounty to Muzaffar Shaikh, but here I will give a more thorough explanation, which seems to be lacking on StackOverflow. His answer got the IP Address, but dropped my "body" field, but it certainly pointed me in the right direction.

In the AWS API Gateway tool, click "Resources" then your method (mine was "Post"), then click on "Integration Request" as shown here. enter image description here

Scroll down to the bottom, and if there are not any templates, type in "application/json" and click the checkbox (note, "application/json" is there in light gray letters, but just clicking the checkbox without typing it doesn't work).

enter image description here Then I put in the following template:

{
   "client_ip" : "$input.params('X-Forwarded-For')",
   "user_agent" : "$input.params('User-Agent')",
   "body" : $input.json('$.body') 
}

NOTE: if I put $input.json('$') or $input.json('body'), I ended up with a "body" field inside my "body" field, which broke the current logic.

When the web page was completed, it looked like this:

enter image description here

The next step is to redeploy your template to the "deploy stage" (environment) you were using.

enter image description here

enter image description here

By the way, when entering the template, if you click "Method Request passthrough" in the "Generate template" drop down box, it will generate a template like this (I didn't use this option, but will read more about it soon):

##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
##  This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
#set($allParams = $input.params())
{
"body-json" : $input.json('$'),
"params" : {
#foreach($type in $allParams.keySet())
    #set($params = $allParams.get($type))
"$type" : {
    #foreach($paramName in $params.keySet())
    "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
        #if($foreach.hasNext),#end
    #end
}
    #if($foreach.hasNext),#end
#end
},
"stage-variables" : {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
    #if($foreach.hasNext),#end
#end
},
"context" : {
    "account-id" : "$context.identity.accountId",
    "api-id" : "$context.apiId",
    "api-key" : "$context.identity.apiKey",
    "authorizer-principal-id" : "$context.authorizer.principalId",
    "caller" : "$context.identity.caller",
    "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
    "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
    "cognito-identity-id" : "$context.identity.cognitoIdentityId",
    "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
    "http-method" : "$context.httpMethod",
    "stage" : "$context.stage",
    "source-ip" : "$context.identity.sourceIp",
    "user" : "$context.identity.user",
    "user-agent" : "$context.identity.userAgent",
    "user-arn" : "$context.identity.userArn",
    "request-id" : "$context.requestId",
    "resource-id" : "$context.resourceId",
    "resource-path" : "$context.resourcePath"
    }
}

Two reference for mapping templates:

  1. https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
  2. https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html

I still have some research to do as to when to user "$input.params" vs "$context.some_value".

NealWalters
  • 17,197
  • 42
  • 141
  • 251
5

You can try this:

  1. Add the X-Forwarded-For to the "HTTP Request Headers" (goto the API-Gateway configuration -> Resources -> Method Request).
  2. Add a Template with Content-Type: application/json (Resources -> Integration Request -> "Mapping Templates")
  3. Add a Mapping to the template
{
   "client_ip" : "$input.params('X-Forwarded-For')",
   "user_agent" : "$input.params('User-Agent')"
}
  1. Now the Headers are available in Lambda as expected:

event.client_ip

You can also refer to this link : https://forums.aws.amazon.com/thread.jspa?messageID=648053

Muzaffar Shaikh
  • 630
  • 4
  • 14
  • Thank you, I think this is putting me on the right path. After I get it working, I will reward the bounty. My issue now is this: https://stackoverflow.com/questions/67872825/how-to-keep-existing-event-fields-when-creating-an-aws-api-gateway-template, I lose my existing "body" and trying to figure out how to put it back. – NealWalters Jun 07 '21 at 13:44
  • I awarded you the bounty, but put a more complete answer with some screen shots and how to get the 'body' to still work. – NealWalters Jun 07 '21 at 23:54
2

I spent a while pulling my hair out not finding how to add a mapping template in my integration request. Not sure what was happening but for anyone stumbling on this what i did was:

Check the Use Lambda Proxy integration checkbox in Integration Request.

Now i can access my headers very easily in my Python lambda function with event.["headers"].

Adding in screenshots to make it easier to find:

Go to Integration Request in the relevant method of your API. (See how i have already added the X-Forwarded-For header in Method Request, as recommended in other answers)

Accessing the Integration Request part in API Gateway

The checkbox is here:

How to set up Lambda Proxy integration

(I did not have to make a mapping template. Really not sure what is going on here.)

gtnbssn
  • 159
  • 5