Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
50 changes: 50 additions & 0 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const AppCachePut = (appId, config) =>
...config,
maintenanceKeyIpsStore: new Map(),
masterKeyIpsStore: new Map(),
readOnlyMasterKeyIpsStore: new Map(),
});

describe('middlewares', () => {
Expand Down Expand Up @@ -207,6 +208,55 @@ describe('middlewares', () => {
expect(fakeReq.auth.isMaster).toBe(true);
});

it('should not succeed and log if the ip does not belong to readOnlyMasterKeyIps list', async () => {
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
AppCachePut(fakeReq.body._ApplicationId, {
masterKeyIps: ['0.0.0.0/0'],
readOnlyMasterKey: 'readOnlyMasterKey',
readOnlyMasterKeyIps: ['10.0.0.1'],
});
fakeReq.ip = '127.0.0.1';
fakeReq.headers['x-parse-application-id'] = fakeReq.body._ApplicationId;
fakeReq.headers['x-parse-master-key'] = 'readOnlyMasterKey';

const error = await middlewares.handleParseHeaders(fakeReq, fakeRes, () => {}).catch(e => e);

expect(error).toBeDefined();
expect(error.message).toEqual('unauthorized');
expect(logger.error).toHaveBeenCalledWith(
`Request using read-only master key rejected as the request IP address '127.0.0.1' is not set in Parse Server option 'readOnlyMasterKeyIps'.`
);
});

it('should succeed if the ip does belong to readOnlyMasterKeyIps list', async () => {
AppCachePut(fakeReq.body._ApplicationId, {
masterKeyIps: ['0.0.0.0/0'],
readOnlyMasterKey: 'readOnlyMasterKey',
readOnlyMasterKeyIps: ['10.0.0.1'],
});
fakeReq.ip = '10.0.0.1';
fakeReq.headers['x-parse-application-id'] = fakeReq.body._ApplicationId;
fakeReq.headers['x-parse-master-key'] = 'readOnlyMasterKey';
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
expect(fakeReq.auth.isMaster).toBe(true);
expect(fakeReq.auth.isReadOnly).toBe(true);
});

it('should allow any ip to use readOnlyMasterKey if readOnlyMasterKeyIps is 0.0.0.0/0', async () => {
AppCachePut(fakeReq.body._ApplicationId, {
masterKeyIps: ['0.0.0.0/0'],
readOnlyMasterKey: 'readOnlyMasterKey',
readOnlyMasterKeyIps: ['0.0.0.0/0'],
});
fakeReq.ip = '10.0.0.1';
fakeReq.headers['x-parse-application-id'] = fakeReq.body._ApplicationId;
fakeReq.headers['x-parse-master-key'] = 'readOnlyMasterKey';
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
expect(fakeReq.auth.isMaster).toBe(true);
expect(fakeReq.auth.isReadOnly).toBe(true);
});

it('can set trust proxy', async () => {
const server = await reconfigureServer({ trustProxy: 1 });
expect(server.app.parent.settings['trust proxy']).toBe(1);
Expand Down
2 changes: 2 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class Config {
maintenanceKey,
maintenanceKeyIps,
readOnlyMasterKey,
readOnlyMasterKeyIps,
allowHeaders,
idempotencyOptions,
fileUpload,
Expand Down Expand Up @@ -158,6 +159,7 @@ export class Config {
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
this.validateIps('masterKeyIps', masterKeyIps);
this.validateIps('maintenanceKeyIps', maintenanceKeyIps);
this.validateIps('readOnlyMasterKeyIps', readOnlyMasterKeyIps);
this.validateDefaultLimit(defaultLimit);
this.validateMaxLimit(maxLimit);
this.validateAllowHeaders(allowHeaders);
Expand Down
6 changes: 6 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,12 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_READ_ONLY_MASTER_KEY',
help: 'Read-only key, which has the same capabilities as MasterKey without writes',
},
readOnlyMasterKeyIps: {
env: 'PARSE_SERVER_READ_ONLY_MASTER_KEY_IPS',
help: "(Optional) Restricts the use of read-only master key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the read-only master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the read-only master key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `\"0.0.0.0/0,::0\"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the read-only master key.",
action: parsers.arrayParser,
default: ['127.0.0.1', '::1'],
},
requestContextMiddleware: {
env: 'PARSE_SERVER_REQUEST_CONTEXT_MIDDLEWARE',
help: 'Options to customize the request context using inversion of control/dependency injection.',
Expand Down
1 change: 1 addition & 0 deletions src/Options/docs.js

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

3 changes: 3 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ export interface ParseServerOptions {
/* (Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key.
:DEFAULT: ["127.0.0.1","::1"] */
maintenanceKeyIps: ?(string[]);
/* (Optional) Restricts the use of read-only master key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the read-only master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the read-only master key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the read-only master key.
:DEFAULT: ["127.0.0.1","::1"] */
readOnlyMasterKeyIps: ?(string[]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
/* Sets the app name */
appName: ?string;
/* Add headers to Access-Control-Allow-Headers */
Expand Down
1 change: 1 addition & 0 deletions src/ParseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class ParseServer {
this.config = Config.put(Object.assign({}, options, allControllers));
this.config.masterKeyIpsStore = new Map();
this.config.maintenanceKeyIpsStore = new Map();
this.config.readOnlyMasterKeyIpsStore = new Map();
logging.setLogger(allControllers.loggerController);
}

Expand Down
12 changes: 11 additions & 1 deletion src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const getBlockList = (ipRangeList, store) => {
if (store.get('blockList')) { return store.get('blockList'); }
const blockList = new BlockList();
ipRangeList.forEach(fullIp => {
if (fullIp === '::/0' || fullIp === '::') {
if (fullIp === '::/0' || fullIp === '::' || fullIp === '::0') {
store.set('allowAllIpv6', true);
return;
}
Expand Down Expand Up @@ -270,6 +270,16 @@ export async function handleParseHeaders(req, res, next) {
req.config.readOnlyMasterKey &&
isReadOnlyMaster
) {
if (!checkIp(clientIp, req.config.readOnlyMasterKeyIps || [], req.config.readOnlyMasterKeyIpsStore)) {
const log = req.config?.loggerController || defaultLogger;
log.error(
`Request using read-only master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'readOnlyMasterKeyIps'.`
);
const error = new Error();
error.status = 403;
error.message = 'unauthorized';
throw error;
}
req.auth = new auth.Auth({
config: req.config,
installationId: info.installationId,
Expand Down
Loading