-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] csp: enable tracking and serving of csp reports #323
Conversation
Reports file is written with the new config option "cspReportPath" set. json file written contains all csp-reports in an array.
bd2f2ae
to
8629195
Compare
adjust documentation
8629195
to
44694f6
Compare
be set from the outside when serving
lib/middleware/csp.js
Outdated
* @param {string} cspReportPath path where the reports should be written to | ||
* @returns {Function} Returns a server middleware closure. | ||
*/ | ||
function createMiddleware(sCspUrlParameterName, oConfig, cspReportPath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cspReportPath
is part of the oConfig
, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, adjusted it
lib/middleware/csp.js
Outdated
// Write the violation into a csp-reports json file if cspReportPath parameter is set | ||
if (cspReportPath && req.body && req.body["csp-report"]) { | ||
// extract the csp-report and add it to the cspReportEntries list | ||
cspReportEntries.push(req.body["csp-report"]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we be sure that the body has been parsed?
If the body-parser middleware is not used (which we don't), then I would expect req.body
to be a string.
So we should check and parse the body if necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/middleware/csp.js
Outdated
// extract the csp-report and add it to the cspReportEntries list | ||
cspReportEntries.push(req.body["csp-report"]); | ||
// write the csp report file | ||
writeCspReportsFiles(cspReportPath, cspReportEntries).then(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to write to the file for every single violation? I think this will cause a high disk I/O.
We should rather think how we can write the report before the server will be shut-down.
In the builder we have a way to define cleanup tasks, which seems to be quite similar to this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only write upon server#close or sigint
Use exit hook and server close function to write csp reports file. This avoids writing to a file with every request. The csp reports are stored in an array and only written once the server closes or gets killed (SIGINT)
report is served for URL "/.ui5/csp/csp-reports.json" when parameter serveCSPReports is active. Rather than saving the file on exit, the content now can be accessed on demand. Additionally the served content is valid json.
lib/middleware/csp.js
Outdated
oParsedURL.pathname.endsWith("/dummy.csplog") ) { | ||
// In report-only mode there must be a report-uri defined | ||
// For now just ignore the violation. It will be logged in the browser anyway. | ||
oParsedURL.pathname.endsWith("/.ui5/csp/report.csplog") ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the report-uri is absolute, this check should compare the whole URL
oParsedURL.pathname.endsWith("/.ui5/csp/report.csplog") ) { | |
oParsedURL.pathname === "/.ui5/csp/report.csplog") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/middleware/csp.js
Outdated
} else if (serveCSPReports && req.method === "GET") { | ||
// serve csp reports | ||
if (req.headers["content-type"] === "application/json" && | ||
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, should be an absolute URL
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) { | |
oParsedURL.pathname === "/.ui5/csp/csp-reports.json") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/middleware/csp.js
Outdated
return; | ||
} | ||
next(); | ||
return; | ||
} else if (serveCSPReports && req.method === "GET") { | ||
// serve csp reports | ||
if (req.headers["content-type"] === "application/json" && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to explicitly check for the requested content-type? This would require someone to pass this header explicitly and it's not possible to just load the file from the browser, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was changed to just compare the URL and the method to be a GET request
lib/middleware/csp.js
Outdated
if (req.headers["content-type"] === "application/json" && | ||
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) { | ||
const body = JSON.stringify({ | ||
"csp-reports": cspReportEntries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would opt for using some indentation, as otherwise the file is hard to read or needs to be formatted later on.
The current Java implementation uses tabs, so I think it makes sense to do the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/middleware/csp.js
Outdated
// Write the violation into an array | ||
// They can be retrieved via a request to '/.ui5/csp/csp-reports.json' | ||
if (serveCSPReports && req.body) { | ||
const reqBodyObject = typeof req.body === "string" ? JSON.parse(req.body) : req.body; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that there's no test case to cover the JSON.parse.
Error handling would also be interesting here. Will the server crash when invalid JSON is provided? I would expect that just this request fails with a proper error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Add body parser to parse request body in order to get csp-report. Add res.end() such that csp report requests are not stale (pending).
Instead of having the body-parser part of the MiddlewareManager it is now part of the csp middleware itself using router module. It allows also to split up the request branches.
// .csplog | ||
// body parser is required to parse csp-report in body (json) | ||
if (serveCSPReports) { | ||
router.post("/.ui5/csp/report.csplog", bodyParser.json({type: "application/csp-report"})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need the body-parser middleware? Can't we just parse the JSON string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I also thought initially, but the body-parser does way more than that. It reads the request stream, handles encoding, etc., so I don't think we should write that on our own.
But this way it's only done for that specific path, so no conflict with other middlewares and no overhead to handle other body content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, sounds good 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
lib/middleware/csp.js
Outdated
const router = new Router(); | ||
// .csplog | ||
// body parser is required to parse csp-report in body (json) | ||
router.post("/.ui5/csp/report.csplog", bodyParser.json({type: "application/csp-report"})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a minor optimization, maybe only require and use bodyParser if serveCSPReports
is true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done with 0c7a241
Update ui5-server dependency (and others) to include SAP/ui5-server#323 Setting the environment variable OPENUI5_SRV_CSP to true will configure the ui5-server to set the UI5 target CSP headers as well as to serve any reports at '/.ui5/csp/csp-reports.json' JIRA: CPOUI5FOUNDATION-212 Change-Id: I42057dd4d6127f4639385c3fe8dc71bef396a49a
With option serveCSPReports active
csp reports are gathered and can be retrieved with a request to
"/.ui5/csp/csp-reports.json"
sample response: