perf: Improved IP validation performance for masterKeyIPs, maintenanceKeyIPs (#8510)

This commit is contained in:
Antoine Cormouls
2023-11-19 23:13:16 +01:00
committed by GitHub
parent 759731926f
commit b87daba067
5 changed files with 151 additions and 41 deletions

View File

@@ -10,9 +10,9 @@ import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageA
import rateLimit from 'express-rate-limit';
import { RateLimitOptions } from './Options/Definitions';
import { pathToRegexp } from 'path-to-regexp';
import ipRangeCheck from 'ip-range-check';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';
import { BlockList, isIPv4 } from 'net';
export const DEFAULT_ALLOWED_HEADERS =
'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control';
@@ -23,6 +23,46 @@ const getMountForRequest = function (req) {
return req.protocol + '://' + req.get('host') + mountPath;
};
const getBlockList = (ipRangeList, store) => {
if (store.get('blockList')) return store.get('blockList');
const blockList = new BlockList();
ipRangeList.forEach(fullIp => {
if (fullIp === '::/0' || fullIp === '::') {
store.set('allowAllIpv6', true);
return;
}
if (fullIp === '0.0.0.0/0' || fullIp === '0.0.0.0') {
store.set('allowAllIpv4', true);
return;
}
const [ip, mask] = fullIp.split('/');
if (!mask) {
blockList.addAddress(ip, isIPv4(ip) ? 'ipv4' : 'ipv6');
} else {
blockList.addSubnet(ip, Number(mask), isIPv4(ip) ? 'ipv4' : 'ipv6');
}
});
store.set('blockList', blockList);
return blockList;
};
export const checkIp = (ip, ipRangeList, store) => {
const incomingIpIsV4 = isIPv4(ip);
const blockList = getBlockList(ipRangeList, store);
if (store.get(ip)) return true;
if (store.get('allowAllIpv4') && incomingIpIsV4) return true;
if (store.get('allowAllIpv6') && !incomingIpIsV4) return true;
const result = blockList.check(ip, incomingIpIsV4 ? 'ipv4' : 'ipv6');
// If the ip is in the list, we store the result in the store
// so we have a optimized path for the next request
if (ipRangeList.includes(ip) && result) {
store.set(ip, result);
}
return result;
};
// Checks that the request is authorized for this app and checks user
// auth too.
// The bodyparser should run before this middleware.
@@ -183,7 +223,7 @@ export function handleParseHeaders(req, res, next) {
const isMaintenance =
req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey;
if (isMaintenance) {
if (ipRangeCheck(clientIp, req.config.maintenanceKeyIps || [])) {
if (checkIp(clientIp, req.config.maintenanceKeyIps || [], req.config.maintenanceKeyIpsStore)) {
req.auth = new auth.Auth({
config: req.config,
installationId: info.installationId,
@@ -199,7 +239,8 @@ export function handleParseHeaders(req, res, next) {
}
let isMaster = info.masterKey === req.config.masterKey;
if (isMaster && !ipRangeCheck(clientIp, req.config.masterKeyIps || [])) {
if (isMaster && !checkIp(clientIp, req.config.masterKeyIps || [], req.config.masterKeyIpsStore)) {
const log = req.config?.loggerController || defaultLogger;
log.error(
`Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.`