Files
Haven/test/clientIp.test.js

68 lines
3.3 KiB
JavaScript

'use strict';
// Who counts as the client when a request carries X-Forwarded-For. Sockets
// work it out in src/clientIp.js and HTTP through Express; the two must agree,
// and by default a directly exposed server must ignore a forged header.
const assert = require('node:assert/strict');
const test = require('node:test');
const proxyaddr = require('proxy-addr');
const { socketClientIp, trustProxySetting, DEFAULT_TRUST_PROXY, CONTAINER_TRUST_PROXY } = require('../src/clientIp');
const sock = (peer, xff) => ({ handshake: { address: peer, headers: xff ? { 'x-forwarded-for': xff } : {} } });
const expressIp = (peer, xff, trust) => proxyaddr(
{ connection: { remoteAddress: peer }, socket: { remoteAddress: peer }, headers: xff ? { 'x-forwarded-for': xff } : {} },
// Express turns a number of hops into this function before proxy-addr sees it.
typeof trust === 'number' ? (a, i) => i < trust : proxyaddr.compile(String(trust).split(',').map(s => s.trim()))
);
test('by default a forged header from the internet is ignored', () => {
assert.equal(socketClientIp(sock('203.0.113.9', '1.2.3.4'), DEFAULT_TRUST_PROXY), '203.0.113.9');
assert.equal(socketClientIp(sock('::ffff:203.0.113.9', '1.2.3.4'), DEFAULT_TRUST_PROXY), '203.0.113.9');
});
test('by default a proxy on this machine is believed, and a LAN device is not', () => {
assert.equal(socketClientIp(sock('127.0.0.1', '198.51.100.7'), DEFAULT_TRUST_PROXY), '198.51.100.7');
assert.equal(socketClientIp(sock('::1', '198.51.100.7'), DEFAULT_TRUST_PROXY), '198.51.100.7');
// Any device on a home network could otherwise pick its own address.
assert.equal(socketClientIp(sock('192.168.1.50', '198.51.100.7'), DEFAULT_TRUST_PROXY), '192.168.1.50');
// In a container the proxy sits on the container network.
assert.equal(socketClientIp(sock('172.18.0.2', '198.51.100.7'), CONTAINER_TRUST_PROXY), '198.51.100.7');
// A client-supplied value in front of what the proxy appended is not believed.
assert.equal(socketClientIp(sock('127.0.0.1', '6.6.6.6, 198.51.100.7'), DEFAULT_TRUST_PROXY), '198.51.100.7');
});
test('sockets and Express agree on the client address', () => {
const cases = [
['203.0.113.9', '1.2.3.4'],
['127.0.0.1', '198.51.100.7'],
['127.0.0.1', '6.6.6.6, 198.51.100.7'],
['10.0.0.5', '192.168.1.20, 198.51.100.7'],
['192.168.1.1', null],
['::1', '2001:db8::5'],
['fe80::1', '198.51.100.7'],
];
for (const trust of [DEFAULT_TRUST_PROXY, 1, 2, 0]) {
for (const [peer, xff] of cases) {
const ours = socketClientIp(sock(peer, xff), trust);
const theirs = expressIp(peer, xff, trust).replace(/^::ffff:/, '').replace(/^::1$/, '127.0.0.1');
assert.equal(ours, theirs, `trust=${trust} peer=${peer} xff=${xff}`);
}
}
});
test('TRUST_PROXY is read the way Express reads it', () => {
const saved = process.env.TRUST_PROXY;
try {
delete process.env.TRUST_PROXY;
assert.equal(trustProxySetting(), DEFAULT_TRUST_PROXY);
process.env.TRUST_PROXY = '1'; assert.equal(trustProxySetting(), 1);
process.env.TRUST_PROXY = '0'; assert.equal(trustProxySetting(), 0);
process.env.TRUST_PROXY = 'true'; assert.equal(trustProxySetting(), true);
process.env.TRUST_PROXY = 'loopback'; assert.equal(trustProxySetting(), 'loopback');
} finally {
if (saved === undefined) delete process.env.TRUST_PROXY; else process.env.TRUST_PROXY = saved;
}
});