diff --git a/README.md b/README.md
index 1a47857..3c2a2c4 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Thank you to Upstash for reaching out to sponsor this project!
Price scales to zero with per request pricing
Built-in REST API designed for serverless and edge functions
-
+
[Start for free in 30 seconds!](https://upstash.com/?utm_source=serverless-http)
@@ -53,6 +53,18 @@ Thank you to Upstash for reaching out to sponsor this project!
* [Genezio](https://genezio.com/deploy-nodejs-express-on-genezio-serverless/)
* Azure (Experimental, untested, probably outdated)
+### Proxy trust
+Serverless-http may use `x-forwarded-for` headers to provide accurate client IP addresses when your application is behind proxies or load balancers.
+
+Proxy trust can be enabled using the `proxyTrust (Function|Array|String)` option, see [proxy-addr](https://www.npmjs.com/package/proxy-addr) for details:
+
+```javascript
+const handler = serverless(app, {
+ proxyTrust: () => true // Enable full proxy trust
+ proxyTrust: ['loopback', '192.168.0.0/16'] // Enable only specific address
+});
+```
+
## Deploy a Hello Word on Genezio
:rocket: You can deploy your own hello world example using the Express framework to Genezio with one click:
diff --git a/lib/provider/aws/create-request.js b/lib/provider/aws/create-request.js
index f2f76c5..410885a 100644
--- a/lib/provider/aws/create-request.js
+++ b/lib/provider/aws/create-request.js
@@ -98,6 +98,7 @@ module.exports = (event, context, options) => {
body,
remoteAddress,
url,
+ proxyTrust: options.proxyTrust,
});
req.requestContext = event.requestContext;
diff --git a/lib/provider/azure/create-request.js b/lib/provider/azure/create-request.js
index de6f34b..cfe3d69 100644
--- a/lib/provider/azure/create-request.js
+++ b/lib/provider/azure/create-request.js
@@ -25,11 +25,17 @@ function requestBody(request) {
throw new Error(`Unexpected request.body type: ${typeof request.rawBody}`);
}
-module.exports = (request) => {
+function requestRemoteAddress(event) {
+ return event.requestContext.identity.sourceIp;
+}
+
+
+module.exports = (request, options) => {
const method = request.method;
const query = request.query;
const headers = requestHeaders(request);
const body = requestBody(request);
+ const remoteAddress = requestRemoteAddress(request);
const req = new Request({
method,
@@ -38,7 +44,9 @@ module.exports = (request) => {
url: url.format({
pathname: request.url,
query
- })
+ }),
+ remoteAddress,
+ proxyTrust: options.proxyTrust,
});
req.requestContext = request.requestContext;
return req;
diff --git a/lib/request.js b/lib/request.js
index d1ffe76..3b69180 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -1,10 +1,11 @@
'use strict';
const http = require('http');
+const proxyAddr = require('proxy-addr');
const { PassThrough } = require('stream');
module.exports = class ServerlessRequest extends http.IncomingMessage {
- constructor({ method, url, headers, body, remoteAddress }) {
+ constructor({ method, url, headers, body, remoteAddress, proxyTrust }) {
// Create a real readable socket for IncomingMessage instead of a stub.
const socket = new PassThrough();
socket.encrypted = true;
@@ -17,8 +18,13 @@ module.exports = class ServerlessRequest extends http.IncomingMessage {
headers['content-length'] = Buffer.byteLength(body);
}
+ Object.defineProperty(this, 'ip', {
+ get: () => {
+ return proxyAddr(this, proxyTrust || proxyAddr.compile([]));
+ }
+ });
+
Object.assign(this, {
- ip: remoteAddress,
complete: true,
httpVersion: '1.1',
httpVersionMajor: '1',
diff --git a/package-lock.json b/package-lock.json
index 7373236..109d3e6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,6 +38,7 @@
"pino": "^8.15.0",
"pino-http": "^8.5.0",
"polka": "^0.5.2",
+ "proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"restana": "^4.0.7",
"sails": "^1.2.3",
@@ -12226,6 +12227,7 @@
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
diff --git a/package.json b/package.json
index bee9431..90035cd 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"pino": "^8.15.0",
"pino-http": "^8.5.0",
"polka": "^0.5.2",
+ "proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"restana": "^4.0.7",
"sails": "^1.2.3",
diff --git a/serverless-http.d.ts b/serverless-http.d.ts
index 965f8ba..aa44496 100644
--- a/serverless-http.d.ts
+++ b/serverless-http.d.ts
@@ -24,7 +24,8 @@ declare namespace ServerlessHttp {
request?: Object | Function,
response?: Object | Function,
binary?: boolean | Function | string | string[],
- basePath?: string
+ basePath?: string,
+ proxyTrust?: Function | Array | String
}
/**
* AWS Lambda APIGatewayProxyHandler-like handler.
diff --git a/test/express.js b/test/express.js
index 300c454..9821689 100644
--- a/test/express.js
+++ b/test/express.js
@@ -178,6 +178,79 @@ describe('express', () => {
});
});
+ it('ip should come from identity source ip for aws', () => {
+ app.use(morgan('short'));
+ app.use((req, res) => {
+ res.status(200).send(req.ip);
+ });
+
+ return request(app, {
+ httpMethod: 'GET',
+ requestContext: {
+ identity: {
+ sourceIp: '127.0.0.1'
+ }
+ }
+ })
+ .then(response => {
+ expect(response.statusCode).to.equal(200);
+ expect(response.body).to.equal('127.0.0.1');
+ });
+ });
+
+ it('ip should come from x-forwarded-for header if present', () => {
+ app.use(morgan('short'));
+ app.use((req, res) => {
+ res.status(200).send(req.ip);
+ });
+
+ return request(app, {
+ httpMethod: 'GET',
+ headers: {
+ 'x-forwarded-for': '1.3.3.7'
+ },
+ requestContext: {
+ identity: {
+ sourceIp: '127.0.0.1'
+ }
+ }
+ },
+ {
+ proxyTrust: () => true
+ })
+ .then(response => {
+ expect(response.statusCode).to.equal(200);
+ expect(response.body).to.equal('1.3.3.7');
+ });
+ });
+
+
+ it('ip should come from x-forwarded-for header if present 2', () => {
+ app.use(morgan('short'));
+ app.use((req, res) => {
+ res.status(200).send(req.ip);
+ });
+
+ return request(app, {
+ httpMethod: 'GET',
+ headers: {
+ 'x-forwarded-for': '192.0.0.1, 1.3.3.7'
+ },
+ requestContext: {
+ identity: {
+ sourceIp: '127.0.0.1'
+ }
+ }
+ },
+ {
+ proxyTrust: () => true
+ })
+ .then(response => {
+ expect(response.statusCode).to.equal(200);
+ expect(response.body).to.equal('192.0.0.1');
+ });
+ });
+
it('destroy weird', () => {
app.use((req, res) => {
// this was causing a .destroy is not a function error