Search code examples
javaamazon-web-servicesspring-bootaws-lambdaspring-cloud-function

Source IP address missing in APIGatewayProxyRequestEvent


I am building a lambda function using spring-cloud-function (aws-adapter) which will act as a webhook endpoint for Stripe. The lambda has configured a function URL.

What I am trying to do is to allow only requests coming from a whitelisted IP address. So far the code looks like this:

//imports omitted

@SpringBootApplication
@EnableConfigurationProperties(StripeProperties.class)
public class Application {

  private static final APIGatewayProxyResponseEvent EVENT_PROCESSED =
      new APIGatewayProxyResponseEvent()
          .withStatusCode(HttpStatus.OK.value())
          .withBody("Event processed");

  private static final APIGatewayProxyResponseEvent INVALID_SIGNATURE =
      new APIGatewayProxyResponseEvent()
          .withStatusCode(HttpStatus.BAD_REQUEST.value())
          .withBody("Invalid signature");

  public static void main(String[] args) {
    FunctionalSpringApplication.run(Application.class, args);
  }

  @Bean
  public Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> processEvent(
      StripeEventService stripeEventService) {
    return request -> {

      // var sourceIp = ???;
      // System.out.println(sourceIp);

      var jsonPayload = request.getBody();
      var sigHeader = request.getHeaders().get("stripe-signature");

      if (sigHeader == null) {
        return INVALID_SIGNATURE;
      }

      try {
        var event = Webhook.constructEvent(jsonPayload, sigHeader, "whsec_...");
        stripeEventService.processEvent(event);
        return EVENT_PROCESSED;
      } catch (SignatureVerificationException e) {
        return INVALID_SIGNATURE;
      }
    };
  }
}

Now, when making a rest call to the generated function URL, the JSON request logged looks like this (some values are intentionally masked):

{
    "version": "2.0",
    "routeKey": "$default",
    "rawPath": "/",
    "rawQueryString": "",
    "headers": {
        "content-length": "21",
        "x-amzn-tls-version": "TLSv1.2",
        "x-forwarded-proto": "https",
        "postman-token": "4f4ded59-3c14-4961-a984-bb323f58ec82",
        "x-forwarded-port": "443",
        "x-forwarded-for": "***.***.***.**",
        "accept": "*/*",
        "x-amzn-tls-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256",
        "x-amzn-trace-id": "**********",
        "host": "********.lambda-url.eu-central-1.on.aws",
        "content-type": "application/json",
        "accept-encoding": "gzip, deflate, br",
        "user-agent": "PostmanRuntime/7.33.0"
    },
    "requestContext": {
        "accountId": "anonymous",
        "apiId": "*******",
        "domainName": "******.lambda-url.eu-central-1.on.aws",
        "domainPrefix": "*******",
        "http": {
            "method": "POST",
            "path": "/",
            "protocol": "HTTP/1.1",
            "sourceIp": "***.***.***.**",
            "userAgent": "PostmanRuntime/7.33.0"
        },
        "requestId": "***********",
        "routeKey": "$default",
        "stage": "$default",
        "time": "23/Sep/2023:15:04:20 +0000",
        "timeEpoch": 1695481460790
    },
    "body": "{\n    \"test\": \"123\"\n}",
    "isBase64Encoded": false
}

The value I am looking for is $.requestContext.http.sourceIp. However, I cannot get the value from APIGatewayProxyRequestEvent object (docs), because it does not have a parameter for it.

There are similar questions out there with answers to use $.requestContext.identity.sourceIp, but this value is always null for me since I don't use any identity provider. Also header x-forwarded-for value is not recommended to use according to the mentioned question.

Now the question is, how can I achieve my goal in a secure way?


Solution

  • To get access to the sourceIp you should use the APIGatewayV2HTTPEvent class. This is because Lambda function URL uses payload format version 2.0.