fix(csp): make CSP reports more detailed, remove sample rate

Fix #3297
This commit is contained in:
Vlad Filippov 2015-12-29 18:41:18 -05:00
Родитель 9dbb9ac48d
Коммит 41d919c629
4 изменённых файлов: 17 добавлений и 53 удалений

Просмотреть файл

@ -72,10 +72,6 @@ var conf = module.exports = convict({
default: false, default: false,
doc: 'Only send the "Content-Security-Policy-Report-Only" header' doc: 'Only send the "Content-Security-Policy-Report-Only" header'
}, },
reportSampleRate: {
default: 10,
doc: 'Sample rate at which CSP violation reports should be logged'
},
reportUri: { reportUri: {
default: '/_/csp-violation', default: '/_/csp-violation',
doc: 'Location of "report-uri"' doc: 'Location of "report-uri"'

Просмотреть файл

@ -28,8 +28,7 @@ module.exports = function (config, i18n) {
require('./routes/get-client.json')(i18n), require('./routes/get-client.json')(i18n),
require('./routes/post-metrics')(), require('./routes/post-metrics')(),
require('./routes/post-csp')({ require('./routes/post-csp')({
path: config.get('csp.reportUri'), path: config.get('csp.reportUri')
reportSampleRate: config.get('csp.reportSampleRate')
}), }),
require('./routes/get-metrics-errors')(), require('./routes/get-metrics-errors')(),
require('./routes/get-openid-login')(config), require('./routes/get-openid-login')(config),

Просмотреть файл

@ -9,30 +9,12 @@
module.exports = function (options) { module.exports = function (options) {
options = options || {}; options = options || {};
/**
* 'reportSampleRate' % of messages.
* @param reportSampleRate
* @returns {boolean}
*/
function isSampledUser() {
// random between 0 and 100, inclusive
var rand = Math.floor(Math.random() * (100 + 1));
return rand < options.reportSampleRate;
}
return { return {
method: 'post', method: 'post',
path: options.path, path: options.path,
process: function (req, res) { process: function (req, res) {
res.json({result: 'ok'}); res.json({result: 'ok'});
// TODO: This is a temporary measure
// Not sure how many CSP errors we will get, for now we rate limit this.
// To avoid overflowing Heka logs rate limit the logging
if (! isSampledUser()) {
return false;
}
if (! req.body || ! req.body['csp-report']) { if (! req.body || ! req.body['csp-report']) {
return false; return false;
} }
@ -42,11 +24,16 @@ module.exports = function (options) {
var report = req.body['csp-report']; var report = req.body['csp-report'];
var entry = { var entry = {
agent: req.get('User-Agent'),
blocked: report['blocked-uri'], blocked: report['blocked-uri'],
column: report['column-number'],
line: report['line-number'],
op: 'server.csp', op: 'server.csp',
referrer: report['referrer'], referrer: report['referrer'],
sample: report['script-sample'],
source: report['source-file'],
time: today.toISOString(), time: today.toISOString(),
violated: report['violated-directive'] violated: report['violated-directive'],
}; };
process.stderr.write(JSON.stringify(entry) + '\n'); process.stderr.write(JSON.stringify(entry) + '\n');

Просмотреть файл

@ -22,51 +22,33 @@ define([
'csp-report': { 'csp-report': {
'blocked-uri': 'http://bing.com' 'blocked-uri': 'http://bing.com'
} }
} },
'get': function () {}
}; };
var mockResponse = { var mockResponse = {
json: function () {} json: function () {}
}; };
suite['it drops if no csp-report set'] = function () { suite['it drops if no csp-report set'] = function () {
var options = { var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})();
reportSampleRate: 100
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);
assert.isFalse(postCsp.process({ assert.isFalse(postCsp.process({
body: {} body: {},
'get': function () {}
}, mockResponse)); }, mockResponse));
assert.isTrue(postCsp.process({ assert.isTrue(postCsp.process({
body: { body: {
'csp-report': {} 'csp-report': {}
} },
'get': function () {}
}, mockResponse)); }, mockResponse));
assert.isTrue(postCsp.process(mockRequest, mockResponse)); assert.isTrue(postCsp.process(mockRequest, mockResponse));
}; };
suite['it works with csp reports'] = function () {
suite['it allows no messages with 0% sample rate '] = function () { var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})();
var options = {
reportSampleRate: 0
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);
// check 5 times that all messages come through
assert.isFalse(postCsp.process(mockRequest, mockResponse));
assert.isFalse(postCsp.process(mockRequest, mockResponse));
assert.isFalse(postCsp.process(mockRequest, mockResponse));
assert.isFalse(postCsp.process(mockRequest, mockResponse));
assert.isFalse(postCsp.process(mockRequest, mockResponse));
};
suite['it allows all messages with 100% sample rate'] = function () {
var options = {
reportSampleRate: 100
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);
// check 5 times that all messages drop // check 5 times that all messages drop
assert.isTrue(postCsp.process(mockRequest, mockResponse)); assert.isTrue(postCsp.process(mockRequest, mockResponse));
assert.isTrue(postCsp.process(mockRequest, mockResponse)); assert.isTrue(postCsp.process(mockRequest, mockResponse));