Security: limit Masterkey remote access (#4017)

* update choose_password to have the confirmation

* add comment mark

* First version, no test

* throw error right away instead of just use masterKey false

* fix the logic

* move it up before the masterKey check

* adding some test

* typo

* remove the choose_password

* newline

* add cli options

* remove trailing space

* handle  in case the server is behind proxy

* add getting the first ip in the ip list of xff

* sanity check the ip in config if it is a valid ip address

* split ip extraction to another function

* trailing spaces
This commit is contained in:
Worathiti Manosroi
2017-07-23 18:26:30 +02:00
committed by Florent Vilmart
parent 811d8b0c7a
commit 7e54265f6d
7 changed files with 223 additions and 2 deletions

View File

@@ -5,6 +5,7 @@
import AppCache from './cache';
import SchemaCache from './Controllers/SchemaCache';
import DatabaseController from './Controllers/DatabaseController';
import net from 'net';
function removeTrailingSlash(str) {
if (!str) {
@@ -26,6 +27,7 @@ export class Config {
this.applicationId = applicationId;
this.jsonLogs = cacheInfo.jsonLogs;
this.masterKey = cacheInfo.masterKey;
this.masterKeyIps = cacheInfo.masterKeyIps;
this.clientKey = cacheInfo.clientKey;
this.javascriptKey = cacheInfo.javascriptKey;
this.dotNetKey = cacheInfo.dotNetKey;
@@ -86,7 +88,8 @@ export class Config {
sessionLength,
emailVerifyTokenValidityDuration,
accountLockout,
passwordPolicy
passwordPolicy,
masterKeyIps
}) {
const emailAdapter = userController.adapter;
if (verifyUserEmails) {
@@ -108,6 +111,8 @@ export class Config {
}
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
this.validateMasterKeyIps(masterKeyIps);
}
static validateAccountLockoutPolicy(accountLockout) {
@@ -184,6 +189,14 @@ export class Config {
}
}
static validateMasterKeyIps(masterKeyIps) {
for (const ip of masterKeyIps) {
if(!net.isIP(ip)){
throw `Invalid ip in masterKeyIps: ${ip}`;
}
}
}
get mount() {
var mount = this._mount;
if (this.publicServerURL) {

View File

@@ -92,6 +92,7 @@ class ParseServer {
constructor({
appId = requiredParameter('You must provide an appId!'),
masterKey = requiredParameter('You must provide a masterKey!'),
masterKeyIps = [],
appName,
analyticsAdapter,
filesAdapter,
@@ -167,6 +168,11 @@ class ParseServer {
userSensitiveFields
)));
masterKeyIps = Array.from(new Set(masterKeyIps.concat(
defaults.masterKeyIps,
masterKeyIps
)));
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
const loggerController = new LoggerController(loggerControllerAdapter, appId);
logging.setLogger(loggerController);
@@ -228,6 +234,7 @@ class ParseServer {
AppCache.put(appId, {
appId,
masterKey: masterKey,
masterKeyIps:masterKeyIps,
serverURL: serverURL,
collectionPrefix: collectionPrefix,
clientKey: clientKey,

View File

@@ -19,6 +19,11 @@ export default {
help: "Your Parse Master Key",
required: true
},
"masterKeyIps": {
env: "PARSE_SERVER_MASTER_KEY_IPS",
help: "Restrict masterKey to be used by only these ips. defaults to [] (allow all ips)",
default: []
},
"port": {
env: "PORT",
help: "The port to run the ParseServer. defaults to 1337.",

View File

@@ -35,5 +35,6 @@ export default {
cacheTTL: 5000,
cacheMaxSize: 10000,
userSensitiveFields: ['email'],
objectIdSize: 10
objectIdSize: 10,
masterKeyIps: []
}

View File

@@ -111,6 +111,11 @@ export function handleParseHeaders(req, res, next) {
req.config.headers = req.headers || {};
req.info = info;
const ip = getClientIp(req);
if (info.masterKey && req.config.masterKeyIps && req.config.masterKeyIps.length !== 0 && req.config.masterKeyIps.indexOf(ip) === -1) {
return invalidRequest(req, res);
}
var isMaster = (info.masterKey === req.config.masterKey);
if (isMaster) {
@@ -171,6 +176,25 @@ export function handleParseHeaders(req, res, next) {
});
}
function getClientIp(req){
if (req.headers['x-forwarded-for']) {
// try to get from x-forwared-for if it set (behind reverse proxy)
return req.headers['x-forwarded-for'].split(',')[0];
} else if (req.connection && req.connection.remoteAddress) {
// no proxy, try getting from connection.remoteAddress
return req.connection.remoteAddress;
} else if (req.socket) {
// try to get it from req.socket
return req.socket.remoteAddress;
} else if (req.connection && req.connection.socket) {
// try to get it form the connection.socket
return req.connection.socket.remoteAddress;
} else {
// if non above, fallback.
return req.ip;
}
}
function httpAuth(req) {
if (!(req.req || req).headers.authorization)
return ;