Skip to content

Commit a41cbcb

Browse files
Arthur Cinaderflovilmart
Arthur Cinader
authored andcommitted
Move password masking out of logging clients where possible (#2762)
Move password masking functionality into LoggerController. The is a more aggresive approach to masking password string in the logs. Cleaning the url is still in the PromiseRouter because picking it out of the log string would be fragile. This will cause more log messages to be scanned for password strings, and may cause a password string to be obsfucated that is not neccesarily part of parse internals -- but i think that is still a good thing.... see: #2755 & #2680
1 parent ad70745 commit a41cbcb

File tree

5 files changed

+50
-53
lines changed

5 files changed

+50
-53
lines changed

spec/WinstonLoggerAdapter.spec.js

-16
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,4 @@ describe('verbose logs', () => {
9797
done();
9898
})
9999
});
100-
101-
it("should not mask information in non _User class", (done) => {
102-
let obj = new Parse.Object('users');
103-
obj.set('password', 'pw');
104-
obj.save().then(() => {
105-
let winstonLoggerAdapter = new WinstonLoggerAdapter();
106-
return winstonLoggerAdapter.query({
107-
from: new Date(Date.now() - 500),
108-
size: 100,
109-
level: 'verbose'
110-
});
111-
}).then((results) => {
112-
expect(results[1].body.password).toEqual("pw");
113-
done();
114-
});
115-
});
116100
});

src/Controllers/LoggerController.js

+42-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Parse } from 'parse/node';
22
import PromiseRouter from '../PromiseRouter';
33
import AdaptableController from './AdaptableController';
44
import { LoggerAdapter } from '../Adapters/Logger/LoggerAdapter';
5+
import url from 'url';
56

67
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
78
const LOG_STRING_TRUNCATE_LENGTH = 1000;
@@ -19,8 +20,48 @@ export const LogOrder = {
1920

2021
export class LoggerController extends AdaptableController {
2122

23+
maskSensitiveUrl(urlString) {
24+
const password = url.parse(urlString, true).query.password;
25+
26+
if (password) {
27+
urlString = urlString.replace('password=' + password, 'password=********');
28+
}
29+
return urlString;
30+
}
31+
32+
maskSensitive(argArray) {
33+
return argArray.map(e => {
34+
if (!e) {
35+
return e;
36+
}
37+
38+
if (typeof e === 'string') {
39+
return e.replace(/(password".?:.?")[^"]*"/g, '$1********"');
40+
}
41+
// else it is an object...
42+
43+
// check the url
44+
if (e.url) {
45+
e.url = this.maskSensitiveUrl(e.url);
46+
}
47+
48+
if (e.body) {
49+
for (let key of Object.keys(e.body)) {
50+
if (key === 'password') {
51+
e.body[key] = '********';
52+
break;
53+
}
54+
}
55+
}
56+
57+
return e;
58+
});
59+
}
60+
2261
log(level, args) {
23-
args = [].concat(level, [...args]);
62+
// make the passed in arguments object an array with the spread operator
63+
args = this.maskSensitive([...args]);
64+
args = [].concat(level, args);
2465
this.adapter.log.apply(this.adapter, args);
2566
}
2667

@@ -61,14 +102,6 @@ export class LoggerController extends AdaptableController {
61102
return null;
62103
}
63104

64-
cleanAndTruncateLogMessage(string) {
65-
return this.truncateLogMessage(this.cleanLogMessage(string));
66-
}
67-
68-
cleanLogMessage(string) {
69-
return string.replace(/password":"[^"]*"/g, 'password":"********"');
70-
}
71-
72105
truncateLogMessage(string) {
73106
if (string && string.length > LOG_STRING_TRUNCATE_LENGTH) {
74107
const truncated = string.substring(0, LOG_STRING_TRUNCATE_LENGTH) + truncationMarker;

src/PromiseRouter.js

+2-22
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function makeExpressHandler(appId, promiseHandler) {
144144
return function(req, res, next) {
145145
try {
146146
let url = maskSensitiveUrl(req);
147-
let body = maskSensitiveBody(req);
147+
let body = Object.assign({}, req.body);
148148
let stringifiedBody = JSON.stringify(body, null, 2);
149149
log.verbose(`REQUEST for [${req.method}] ${url}: ${stringifiedBody}`, {
150150
method: req.method,
@@ -198,33 +198,13 @@ function makeExpressHandler(appId, promiseHandler) {
198198
}
199199
}
200200

201-
function maskSensitiveBody(req) {
202-
let maskBody = Object.assign({}, req.body);
203-
let shouldMaskBody = (req.method === 'POST' && req.originalUrl.endsWith('/users')
204-
&& !req.originalUrl.includes('classes')) ||
205-
(req.method === 'PUT' && /users\/\w+$/.test(req.originalUrl)
206-
&& !req.originalUrl.includes('classes')) ||
207-
(req.originalUrl.includes('classes/_User'));
208-
if (shouldMaskBody) {
209-
for (let key of Object.keys(maskBody)) {
210-
if (key == 'password') {
211-
maskBody[key] = '********';
212-
break;
213-
}
214-
}
215-
}
216-
return maskBody;
217-
}
218201

219202
function maskSensitiveUrl(req) {
220203
let maskUrl = req.originalUrl.toString();
221204
let shouldMaskUrl = req.method === 'GET' && req.originalUrl.includes('/login')
222205
&& !req.originalUrl.includes('classes');
223206
if (shouldMaskUrl) {
224-
let password = url.parse(req.originalUrl, true).query.password;
225-
if (password) {
226-
maskUrl = maskUrl.replace('password=' + password, 'password=********')
227-
}
207+
maskUrl = log.maskSensitiveUrl(maskUrl);
228208
}
229209
return maskUrl;
230210
}

src/Routers/FunctionsRouter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ export class FunctionsRouter extends PromiseRouter {
125125

126126
return new Promise(function (resolve, reject) {
127127
const userString = (req.auth && req.auth.user) ? req.auth.user.id : undefined;
128-
const cleanInput = logger.cleanAndTruncateLogMessage(JSON.stringify(params));
128+
const cleanInput = logger.truncateLogMessage(JSON.stringify(params));
129129
var response = FunctionsRouter.createResponseObject((result) => {
130130
try {
131-
const cleanResult = logger.cleanAndTruncateLogMessage(JSON.stringify(result.response.result));
131+
const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result));
132132
logger.info(`Ran cloud function ${functionName} for user ${userString} `
133133
+ `with:\n Input: ${cleanInput }\n Result: ${cleanResult }`, {
134134
functionName,

src/triggers.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ function userIdForLog(auth) {
212212
}
213213

214214
function logTriggerAfterHook(triggerType, className, input, auth) {
215-
const cleanInput = logger.cleanAndTruncateLogMessage(JSON.stringify(input));
215+
const cleanInput = logger.truncateLogMessage(JSON.stringify(input));
216216
logger.info(`${triggerType} triggered for ${className} for user ${userIdForLog(auth)}:\n Input: ${cleanInput}`, {
217217
className,
218218
triggerType,
@@ -221,8 +221,8 @@ function logTriggerAfterHook(triggerType, className, input, auth) {
221221
}
222222

223223
function logTriggerSuccessBeforeHook(triggerType, className, input, result, auth) {
224-
const cleanInput = logger.cleanAndTruncateLogMessage(JSON.stringify(input));
225-
const cleanResult = logger.cleanAndTruncateLogMessage(JSON.stringify(result));
224+
const cleanInput = logger.truncateLogMessage(JSON.stringify(input));
225+
const cleanResult = logger.truncateLogMessage(JSON.stringify(result));
226226
logger.info(`${triggerType} triggered for ${className} for user ${userIdForLog(auth)}:\n Input: ${cleanInput}\n Result: ${cleanResult}`, {
227227
className,
228228
triggerType,
@@ -231,7 +231,7 @@ function logTriggerSuccessBeforeHook(triggerType, className, input, result, auth
231231
}
232232

233233
function logTriggerErrorBeforeHook(triggerType, className, input, auth, error) {
234-
const cleanInput = logger.cleanAndTruncateLogMessage(JSON.stringify(input));
234+
const cleanInput = logger.truncateLogMessage(JSON.stringify(input));
235235
logger.error(`${triggerType} failed for ${className} for user ${userIdForLog(auth)}:\n Input: ${cleanInput}\n Error: ${JSON.stringify(error)}`, {
236236
className,
237237
triggerType,

0 commit comments

Comments
 (0)