Skip to content
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

Merged
merged 11 commits into from
Jun 9, 2020

Conversation

tobiasso85
Copy link
Contributor

@tobiasso85 tobiasso85 commented May 20, 2020

With option serveCSPReports active

csp reports are gathered and can be retrieved with a request to "/.ui5/csp/csp-reports.json"

sample response:

{
  "csp-reports": [
    {
      "document-uri": "https://otherserver:8080/index.html",
      "referrer": "",
      "violated-directive": "script-src-elem",
      "effective-directive": "script-src-elem",
      "original-policy": "default-src 'self' myserver:443; report-uri /.ui5/csp/report.csplog",
      "disposition": "report",
      "blocked-uri": "inline",
      "line-number": 17,
      "source-file": "https://otherserver:8080/index.html",
      "status-code": 0,
      "script-sample": ""
    }
  ]
}

Reports file is written with the new config option
"cspReportPath" set. json file written contains
all csp-reports in an array.
@tobiasso85 tobiasso85 requested review from RandomByte and matz3 May 20, 2020 12:39
@coveralls
Copy link

coveralls commented May 20, 2020

Coverage Status

Coverage decreased (-0.07%) to 93.282% when pulling 0c7a241 on csp-reports-file into 2a7e208 on master.

* @param {string} cspReportPath path where the reports should be written to
* @returns {Function} Returns a server middleware closure.
*/
function createMiddleware(sCspUrlParameterName, oConfig, cspReportPath) {
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, adjusted it

// 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"]);
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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(() => {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
@tobiasso85 tobiasso85 requested a review from matz3 May 28, 2020 13:37
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.
@tobiasso85 tobiasso85 changed the title [FEATURE] csp: enable writing of reports file [FEATURE] csp: enable tracking and serving of csp reports May 29, 2020
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") ) {
Copy link
Member

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

Suggested change
oParsedURL.pathname.endsWith("/.ui5/csp/report.csplog") ) {
oParsedURL.pathname === "/.ui5/csp/report.csplog") {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else if (serveCSPReports && req.method === "GET") {
// serve csp reports
if (req.headers["content-type"] === "application/json" &&
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) {
Copy link
Member

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

Suggested change
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) {
oParsedURL.pathname === "/.ui5/csp/csp-reports.json") {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return;
}
next();
return;
} else if (serveCSPReports && req.method === "GET") {
// serve csp reports
if (req.headers["content-type"] === "application/json" &&
Copy link
Member

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?

Copy link
Contributor Author

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

if (req.headers["content-type"] === "application/json" &&
oParsedURL.pathname.endsWith("/.ui5/csp/csp-reports.json")) {
const body = JSON.stringify({
"csp-reports": cspReportEntries
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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;
Copy link
Member

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

Copy link
Contributor Author

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).
@tobiasso85 tobiasso85 requested a review from matz3 June 2, 2020 15:51
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"}));
Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, sounds good 👍

@tobiasso85 tobiasso85 requested a review from RandomByte June 8, 2020 05:54
Copy link
Member

@matz3 matz3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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"}));
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done with 0c7a241

@tobiasso85 tobiasso85 requested a review from matz3 June 9, 2020 06:38
@tobiasso85 tobiasso85 merged commit e0a0c5e into master Jun 9, 2020
@tobiasso85 tobiasso85 deleted the csp-reports-file branch June 9, 2020 07:19
openui5bot pushed a commit to SAP/openui5 that referenced this pull request Jun 16, 2020
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants