feat(csp): enable report only CSP in prodiction #3179
Conversation
|
The log for this will give us: thoughts? |
|
todo: make this work only for 10% |
|
@jrgm let me know if the log format is okay |
37b83f3
to
647d027
|
Updated! |
|
|
||
| assert.isFalse(postCsp.process({ | ||
| body: { | ||
| 'csp-report': {} |
shane-tomlinson
Nov 5, 2015
Member
What happens if we intentionally pass bad data, like:
body: {
'csp-report': 'will this cause anything to go awry?'
}
What happens if we intentionally pass bad data, like:
body: {
'csp-report': 'will this cause anything to go awry?'
}| // correct is 'application/csp-report': https://w3c.github.io/webappsec/specs/content-security-policy/ | ||
| // https://bugzilla.mozilla.org/show_bug.cgi?id=1192840 | ||
| app.use(bodyParser.json({ | ||
| type: ['json', 'application/csp-report'] |
shane-tomlinson
Nov 5, 2015
Member
If chrome sends application/csp-report, will express automatically interpret that as JSON data? If not, we'll have to manually decode the data before this line
If chrome sends application/csp-report, will express automatically interpret that as JSON data? If not, we'll have to manually decode the data before this line
vladikoff
Nov 5, 2015
Author
Contributor
will express automatically interpret that as JSON data?
No,
That's why this line is added, it signals that the data must be interpret as JSON for that content type.
will express automatically interpret that as JSON data?
No,
That's why this line is added, it signals that the data must be interpret as JSON for that content type.
shane-tomlinson
Nov 6, 2015
Member
I totally read the code incorrectly, thanks for clarifying.
I totally read the code incorrectly, thanks for clarifying.
| today.setMinutes(0, 0, 0); | ||
|
|
||
| var entry = { | ||
| blocked: req.body['csp-report']['blocked-uri'], |
shane-tomlinson
Nov 5, 2015
Member
Looking at the CSP spec, it may be useful to know the referrer of the offending script. I have a suspicion that addons like LastPass will cause CSP violations if they insert scripts into the DOM automatically, and we'll want to know that.
Looking at the CSP spec, it may be useful to know the referrer of the offending script. I have a suspicion that addons like LastPass will cause CSP violations if they insert scripts into the DOM automatically, and we'll want to know that.
shane-tomlinson
Nov 5, 2015
Member
Which brings up the question would the referrer that causes an error be the addon's script, or would it be FxA?
Which brings up the question would the referrer that causes an error be the addon's script, or would it be FxA?
| // 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 (isRateLimited(options.rateLimitPercentage)) { |
shane-tomlinson
Nov 6, 2015
Member
Using isRateLimited seems a little strange and is the reverse of how metrics are handled. Metrics uses isSampledUser and a sampling percentage, whereas here we use a rate limiting percentage. 0% sample rate is no samples are let through, 0% rate limiting means all CSP reports are let through. For our own future sanity, we should use a consistent scheme in both places. I propose changing options.rateLimitPercentage to something like options.cspReportSamplePercentage
Using isRateLimited seems a little strange and is the reverse of how metrics are handled. Metrics uses isSampledUser and a sampling percentage, whereas here we use a rate limiting percentage. 0% sample rate is no samples are let through, 0% rate limiting means all CSP reports are let through. For our own future sanity, we should use a consistent scheme in both places. I propose changing options.rateLimitPercentage to something like options.cspReportSamplePercentage
| * @returns {boolean} | ||
| */ | ||
| function isRateLimited(rateLimitPercentage) { | ||
| var RATE_LIMIT = rateLimitPercentage; |
shane-tomlinson
Nov 6, 2015
Member
RATE_LIMIT isn't really a constant, so no need for the caps.
RATE_LIMIT isn't really a constant, so no need for the caps.
| var min = 0; | ||
| var max = 100; | ||
|
|
||
| var rand = min + Math.floor(Math.random() * (max - min + 1)); |
shane-tomlinson
Nov 6, 2015
Member
Ahha, I see this code comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random to include both endpoints. Could this be made into its own function (probably preferable) or simplified since both min and max are hard coded:
var rand = Math.floor(Math.random() * (100 + 1));
Ahha, I see this code comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random to include both endpoints. Could this be made into its own function (probably preferable) or simplified since both min and max are hard coded:
var rand = Math.floor(Math.random() * (100 + 1));| * Route to report CSP Violations to metrics | ||
| */ | ||
|
|
||
| var DEFAULT_RATE_LIMIT = 90; |
vladikoff
Nov 9, 2015
Author
Contributor
This is just a temporary measure, didn't want to add a config value, because we are planning to remove this in 1 train
This is just a temporary measure, didn't want to add a config value, because we are planning to remove this in 1 train
|
A report in Firefox: A report in Chrome: @vladikoff - when testing in Chrome, the reports are sent, and even with rate limiting code disabled no reports are sent to Heka. Here is the diff I'm using to test: diff --git a/server/lib/routes/post-csp.js b/server/lib/routes/post-csp.js
index 680b34a..d794efc 100644
--- a/server/lib/routes/post-csp.js
+++ b/server/lib/routes/post-csp.js
@@ -17,7 +17,7 @@ module.exports = function (options) {
* @returns {boolean}
*/
function isRateLimited(rateLimitPercentage) {
- var RATE_LIMIT = rateLimitPercentage;
+ var RATE_LIMIT = 0;//rateLimitPercentage;
// rate limit can be 0
if (typeof rateLimitPercentage === 'undefined') {
RATE_LIMIT = DEFAULT_RATE_LIMIT;
@@ -38,9 +38,13 @@ module.exports = function (options) {
// 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 (isRateLimited(options.rateLimitPercentage)) {
return false;
}
+ */
+
+ console.log('req.body', req.body);
if (! req.body || ! req.body['csp-report'] || ! req.body['csp-report']['blocked-uri']) {
return false;I use this command in the developer console to trigger a CSP violation: var script = document.createElement('script');script.innerHTML = 'alert(1);';document.body.appendChild(script);With the diff applied (which has logging), the console output is: { 'csp-report':
{ 'document-uri': 'http://127.0.0.1:3030/signup',
referrer: '',
'violated-directive': 'default-src \'self\'',
'effective-directive': 'script-src',
'original-policy': 'connect-src \'self\' http://127.0.0.1:9000 http://127.0.0.1:9010 http://127.0.0.1:1111 http://127.0.0.1:1114; default-src \'self\'; img-src \'self\' data: https://secure.gravatar.com http://127.0.0.1:1112; media-src blob:; report-uri /_/csp-violation',
'blocked-uri': '',
'status-code': 200 } }So, for Chrome, the Firefox sends: { 'csp-report':
{ 'blocked-uri': 'self',
'document-uri': 'http://127.0.0.1:3030/signup',
'line-number': 1,
'original-policy': 'connect-src http://127.0.0.1:3030 http://127.0.0.1:9000 http://127.0.0.1:9010 http://127.0.0.1:1111 http://127.0.0.1:1114; default-src http://127.0.0.1:3030; img-src http://127.0.0.1:3030 data: https://secure.gravatar.com http://127.0.0.1:1112; media-src blob:; report-uri http://127.0.0.1:3030/_/csp-violation',
referrer: '',
'script-sample': 'alert(1);',
'source-file': 'http://127.0.0.1:3030/signup',
'violated-directive': 'default-src http://127.0.0.1:3030' } }Safari sends a similar report to Chrome: { 'csp-report':
{ 'document-uri': 'http://127.0.0.1:3030/signup',
referrer: '',
'violated-directive': 'default-src \'self\'',
'original-policy': 'connect-src \'self\' http://127.0.0.1:9000 http://127.0.0.1:9010 http://127.0.0.1:1111 http://127.0.0.1:1114; default-src \'self\'; img-src \'self\' data: https://secure.gravatar.com http://127.0.0.1:1112; media-src blob:; report-uri /_/csp-violation',
'blocked-uri': '',
'source-file': '',
'line-number': 1 } }With this info, I think we should generate a CSP report even if Finally, the answer to my question about what happens if an addon causes a CSP violation. One of my addons is causing this report to be generated: { 'csp-report':
{ 'blocked-uri': 'self',
'document-uri': 'http://127.0.0.1:3030/signup',
'line-number': 1,
'original-policy': 'connect-src http://127.0.0.1:3030 http://127.0.0.1:9000 http://127.0.0.1:9010 http://127.0.0.1:1111 http://127.0.0.1:1114; default-src http://127.0.0.1:3030; img-src http://127.0.0.1:3030 data: https://secure.gravatar.com http://127.0.0.1:1112; media-src blob:; report-uri http://127.0.0.1:3030/_/csp-violation',
referrer: '',
'script-sample': '(function () {\n\n var event_id = docum...',
'source-file': 'http://127.0.0.1:3030/signup',
'violated-directive': 'default-src http://127.0.0.1:3030' } }The |
|
from mtg: could 'violated-directive' be useful? |
|
@jrgm how do we keep consistent with Firefox Hello CSP logging? What is the format? |
|
Updated! |
|
With no addons, we have problems in Firefox Beta. |
|
More context - the above problem is on the |
|
Here is the CSP report filed by Firefox: The .svg file contains an inline Here is relevant the portion of the W3C CSP spec: http://www.w3.org/TR/CSP/#directive-style-src We can:
|
|
V3 of the CSP spec: https://w3c.github.io/webappsec-csp/#directive-style-src |
|
I was wrong, styleSrc: [
SELF,
"'sha256-9n6ek6ecEYlqel7uDyKLy6fdGNo3vw/uScXSq9ooQlk=='"
], |
|
https://bugzilla.mozilla.org/show_bug.cgi?id=958702 - CSP 1.1: (followup) hash-source on multiprocess Gecko |
|
@vladikoff - while tooling around trying to figure out the CSP problem, I had the following patch: diff --git a/server/lib/configuration.js b/server/lib/configuration.js
index 4b4a8f6..75ccc31 100644
--- a/server/lib/configuration.js
+++ b/server/lib/configuration.js
@@ -65,6 +65,10 @@ var conf = module.exports = convict({
default: false,
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: {
default: '/_/csp-violation',
doc: 'Location of "report-uri"'
diff --git a/server/lib/routes.js b/server/lib/routes.js
index e9cfefd..4fc00d2 100644
--- a/server/lib/routes.js
+++ b/server/lib/routes.js
@@ -27,7 +27,10 @@ module.exports = function (config, i18n) {
require('./routes/get-config')(i18n),
require('./routes/get-client.json')(i18n),
require('./routes/post-metrics')(),
- require('./routes/post-csp')(),
+ require('./routes/post-csp')({
+ path: config.get('csp.reportUri'),
+ reportSampleRate: config.get('csp.reportSampleRate')
+ }),
require('./routes/get-metrics-errors')(),
require('./routes/get-openid-login')(config),
require('./routes/get-openid-authenticate')(config)
diff --git a/server/lib/routes/post-csp.js b/server/lib/routes/post-csp.js
index e49138c..2110ada 100644
--- a/server/lib/routes/post-csp.js
+++ b/server/lib/routes/post-csp.js
@@ -6,38 +6,30 @@
* Route to report CSP Violations to metrics
*/
-var DEFAULT_SAMPLE_RATE = 10;
-
module.exports = function (options) {
options = options || {};
/**
- * 'sampleRate' % of messages.
- * @param sampleRate
+ * 'reportSampleRate' % of messages.
+ * @param reportSampleRate
* @returns {boolean}
*/
- function isSampledUser(sampleRate) {
- var rateLimit = sampleRate;
- // rate limit can be 0
- if (typeof sampleRate === 'undefined') {
- rateLimit = DEFAULT_SAMPLE_RATE;
- }
-
+ function isSampledUser() {
// random between 0 and 100, inclusive
var rand = Math.floor(Math.random() * (100 + 1));
- return rand <= rateLimit;
+ return rand <= options.reportSampleRate;
}
return {
method: 'post',
- path: '/_/csp-violation',
+ path: options.path,
process: function (req, res) {
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(options.sampleRate)) {
+ if (! isSampledUser()) {
return false;
}
diff --git a/tests/server/routes/post-csp.js b/tests/server/routes/post-csp.js
index c1b57ae..ad9fdab 100644
--- a/tests/server/routes/post-csp.js
+++ b/tests/server/routes/post-csp.js
@@ -30,7 +30,7 @@ define([
suite['it drops if no csp-report set'] = function () {
var options = {
- sampleRate: 100
+ reportSampleRate: 100
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);
assert.isFalse(postCsp.process({
@@ -49,7 +49,7 @@ define([
suite['it allows no messages with 0% sample rate '] = function () {
var options = {
- sampleRate: 0
+ reportSampleRate: 0
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);
@@ -63,7 +63,7 @@ define([
suite['it allows all messages with 100% sample rate'] = function () {
var options = {
- sampleRate: 100
+ reportSampleRate: 100
};
var postCsp = proxyquire(path.join(process.cwd(), 'server', 'lib', 'routes', 'post-csp'), {})(options);The changes:
|
|
More info - A hash-source problem was fixed in Fx 43 - https://bugzilla.mozilla.org/show_bug.cgi?id=1026520 I can avoid the CSP error in Firefox with the following I had an extra Oddly, with full CSP enabled and the above styleSrc, Safari refuses to load the svg as a standalone page because of a CSP violation. If I load |
|
With the above |
|
Closing, re-opening against origin. |
Fixes #1426