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?
To get access to the sourceIp
you should use the APIGatewayV2HTTPEvent
class. This is because Lambda function URL uses payload format version 2.0.