Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: IPv6 ranges not recognized for options masterKeyIPs, maintenanceKeyIPs #8501

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9ef1968
ci: fix node engine check (#7891)
mtrezza Mar 25, 2022
f63fb2b
fix: return correct response when revert is used in beforeSave (#7839)
dblythy Mar 26, 2022
b3199d7
chore(release): 5.2.1-alpha.1 [skip ci]
semantic-release-bot Mar 26, 2022
48bd512
perf: reduce database operations when using the constant parameter in…
dblythy Mar 26, 2022
e0cca58
chore(release): 5.2.1-alpha.2 [skip ci]
semantic-release-bot Mar 26, 2022
90155cf
feat: add MongoDB 5.1 compatibility (#7682)
github-actions[bot] Mar 27, 2022
499cead
chore(release): 5.3.0-alpha.1 [skip ci]
semantic-release-bot Mar 27, 2022
ef56e98
fix: security upgrade parse push adapter from 4.1.0 to 4.1.2 (#7893)
mtrezza Mar 27, 2022
119dbe0
chore(release): 5.3.0-alpha.2 [skip ci]
semantic-release-bot Mar 27, 2022
6b4b358
feat: add MongoDB 5.2 support (#7894)
mtrezza Mar 27, 2022
75eca2d
chore(release): 5.3.0-alpha.3 [skip ci]
semantic-release-bot Mar 27, 2022
61ef23f
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy May 3, 2022
606ed96
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy May 5, 2022
1e89817
Merge branch 'alpha' of https://github.com/dblythy/parse-server into …
dblythy Jun 7, 2022
9720e8e
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Jun 7, 2022
7bc5c36
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Jun 17, 2022
7b2ab38
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Jun 18, 2022
3c8f5cc
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Sep 18, 2022
5814437
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Sep 29, 2022
986c6c2
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Nov 3, 2022
6a40dc4
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Nov 10, 2022
a9fb355
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Dec 20, 2022
132ccaf
fix: improved masterKeyIPs
dblythy Apr 10, 2023
0df02bf
Update middlewares.js
dblythy Apr 10, 2023
4b74cf4
Update middlewares.js
dblythy Apr 10, 2023
3705640
Update middlewares.js
dblythy Apr 11, 2023
8c21d1e
wip
dblythy Apr 11, 2023
113aa96
fix version
dblythy Apr 11, 2023
e640f4f
Merge branch 'alpha' into masterKeysIPS
mtrezza Apr 11, 2023
de35146
Update middlewares.js
dblythy Apr 11, 2023
de8b8ea
Merge remote-tracking branch 'upstream/alpha' into alpha
dblythy Apr 12, 2023
bc01d3e
Merge branch 'alpha' into masterKeysIPS
dblythy Apr 12, 2023
543a849
convert to ipv6
dblythy Apr 12, 2023
5400928
Update middlewares.js
dblythy Apr 12, 2023
d52d389
refactor to blocklist
dblythy Apr 12, 2023
bd40ed5
Update Middlewares.spec.js
dblythy Apr 12, 2023
c2fdbd7
Update middlewares.js
dblythy Apr 12, 2023
6602acf
Update middlewares.js
dblythy May 16, 2023
8b3f51f
Merge branch 'alpha' into masterKeysIPS
mtrezza May 17, 2023
ee863d7
Merge branch 'alpha' into masterKeysIPS
dblythy Jun 19, 2023
ec01674
lint
dblythy Jun 20, 2023
becf01c
Update Middlewares.spec.js
dblythy Jun 20, 2023
8f4fa5d
Update Middlewares.spec.js
dblythy Jun 20, 2023
e81cad0
Merge branch 'alpha' into masterKeysIPS
mtrezza Jun 20, 2023
d5c7ae9
add tests
dblythy Jun 27, 2023
a5230fa
Merge branch 'alpha' into masterKeysIPS
dblythy Jun 27, 2023
cb7ec31
Merge branch 'alpha' into masterKeysIPS
mtrezza Jun 30, 2023
8ca0279
Merge branch 'alpha' into masterKeysIPS
mtrezza Oct 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 15 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"graphql-relay": "0.10.0",
"graphql-tag": "2.12.6",
"intersect": "1.0.1",
"ip-range-check": "0.2.0",
"jsonwebtoken": "9.0.0",
"jwks-rsa": "2.1.5",
"ldapjs": "2.3.3",
Expand Down
42 changes: 41 additions & 1 deletion spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('middlewares', () => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
const contentType = 'image/jpeg';
fakeReq.body._ContentType = contentType;
fakeReq.ip = '127.0.0.1';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeReq.headers['content-type']).toEqual(contentType);
expect(fakeReq.body._ContentType).toEqual(undefined);
Expand Down Expand Up @@ -151,7 +152,33 @@ describe('middlewares', () => {
);
});

it('should not succeed if the ip does not belong to masterKeyIps list', async () => {
it('can allow all with masterKeyIPs', async () => {
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['::/0'],
});
fakeReq.ip = '::ffff:192.168.0.101';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
expect(fakeReq.auth.isMaster).toBe(true);
});

it('can allow localhost with masterKeyIPs', async () => {
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['::'],
});
fakeReq.ip = '127.0.0.1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
expect(fakeReq.auth.isMaster).toBe(true);
});

it('should not succeed if the ip does not belong to masterKeyIps list (ipv4)', async () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['10.0.0.1'],
Expand All @@ -162,6 +189,19 @@ describe('middlewares', () => {
expect(fakeReq.auth.isMaster).toBe(false);
});

it('should not succeed if the ip does not belong to masterKeyIps list (ipv6)', async () => {
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['::1'],
});
fakeReq.ip = '::ffff:101.10.0.1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
expect(fakeReq.auth.isMaster).toBe(false);
});

it('should not succeed if the ip does not belong to maintenanceKeyIps list', async () => {
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
Expand Down
13 changes: 6 additions & 7 deletions spec/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,13 +434,12 @@ describe('server', () => {
reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' }).catch(done);
});

it('fails if you provides invalid ip in masterKeyIps', done => {
reconfigureServer({ masterKeyIps: ['invalidIp', '1.2.3.4'] }).catch(error => {
expect(error).toEqual(
'The Parse Server option "masterKeyIps" contains an invalid IP address "invalidIp".'
);
done();
});
it('fails if you provides invalid ip in masterKeyIps', async () => {
await expectAsync(
reconfigureServer({ masterKeyIps: ['1.2.3.4/0', 'invalidIp'] })
).toBeRejectedWith(
'The Parse Server option "masterKeyIps" contains an invalid IP address "invalidIp".'
);
});

it('should succeed if you provide valid ip in masterKeyIps', done => {
Expand Down
29 changes: 26 additions & 3 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -23,6 +23,29 @@ const getMountForRequest = function (req) {
return req.protocol + '://' + req.get('host') + mountPath;
};

const checkIpRanges = (ip, ranges = []) => {
const transformIp = ip => {
dblythy marked this conversation as resolved.
Show resolved Hide resolved
if (ip === '::1' || ip === '::') {
ip = '127.0.0.1';
}
return ip;
};
const getType = address => (isIPv4(address) ? 'ipv4' : 'ipv6');
const blocklist = new BlockList();
for (const range of ranges) {
if (range.includes('/')) {
const [net, prefix] = range.split('/');
const addr = transformIp(net);
blocklist.addSubnet(addr, Number(prefix), getType(addr));
} else {
const addr = transformIp(range);
blocklist.addAddress(addr, getType(addr));
}
}
const client = transformIp(ip);
return blocklist.check(client, getType(client));
};

// Checks that the request is authorized for this app and checks user
// auth too.
// The bodyparser should run before this middleware.
Expand Down Expand Up @@ -183,7 +206,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 (checkIpRanges(clientIp, req.config.maintenanceKeyIps)) {
req.auth = new auth.Auth({
config: req.config,
installationId: info.installationId,
Expand All @@ -199,7 +222,7 @@ export function handleParseHeaders(req, res, next) {
}

let isMaster = info.masterKey === req.config.masterKey;
if (isMaster && !ipRangeCheck(clientIp, req.config.masterKeyIps || [])) {
if (isMaster && !checkIpRanges(clientIp, req.config.masterKeyIps)) {
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'.`
Expand Down