Reporting CSP violations with AWS CloudFront
Content Security Policy response headers provide us control over the content allowed on our sites. In this post I’ll show you how to implement the report functionality of CSP violations using AWS' CloudFront configured in terraform. Furthermore I’ll share some thoughts on how to process these violations.
CSP violations reporting
The CSP header contains a report-uri directive which you can use to let the browser send a CSP Report to in case of a violation. The exact structure of such a report depends on the browser.
Here’s an example report:
{
"csp-report": {
"document-uri": "http://example.com/signup.html",
"referrer": "",
"blocked-uri": "http://example.com/css/style.css",
"violated-directive": "style-src cdn.example.com",
"original-policy": "default-src 'none'; style-src cdn.example.com; report-uri /_/csp-reports",
"disposition": "report"
}
}
Why would you want a CSP report?
If the website you’re maintaining has clearly defined content you’d only get reports of XSS attacks (against which CSP protects), which can be useful in some cases. At enterprise level there could be multiple teams adding content, like adding a 3rd party recaptcha, and maybe forgetting to adjust the CSP header to allow their new content. Or, like in my case, you’re asked to implement a CSP header but you’ve got no overview of the used content sources.
In these cases a CSP report is useful, and specifically for the latter case there is a Content-Security-Policy-Report-Only
header available. With the report-only header no policy is enforced, so it enables you to experiment with your policy.
Implementing the report-only header in AWS using Terraform
Below is a simple implementation of the aws_cloudfront_response_headers_policy
resource with a custom header:
resource "aws_cloudfront_response_headers_policy" "security_headers" {
name = "myservice-security-headers-policy"
comment = "myservice security headers policy"
custom_headers_config {
items {
header = "Content-Security-Policy-Report-Only"
override = true
value = "default-src 'self'; report-uri /api/cspreport"
}
}
}
I’ve set the report-uri to a custom endpoint which we’ll see later on. There are also tools available to process these reports for you, like report-uri.com.
Next up is to declare the policy in the aws_cloudfront_distribution
resource at the default_cache_behavior
block:
resource "aws_cloudfront_distribution" "cloudfront_module" {
...
default_cache_behavior {
response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers.id
...
}
}
After applying the configuration you’ll see the custom header at CloudFront in the policies tab.
Possible way to analyse reports
Now for the question 'What to do with these reports?'. In my case I needed an overview of content sources used to create a content security policy. So I chose to log the reports as well as count them in metrics, to see if a certain source is used frequently.
I’ve implemented the following endpoint using Spring in Kotlin:
@RestController
class CspReportController(
val metrics: Metrics,
val om: ObjectMapper
) {
@PostMapping("/api/cspreport")
fun cspReport(@RequestBody cspReportJson: String) {
try {
val blockedUri = om.readValue(cspReportJson, ObjectNode::class.java)["csp-report"]["blocked-uri"].asText()
val blockedUriHost = URI(blockedUri).host
log.warn(
"CSP Report for blocked-uri-host: {} \n {}",
blockedUriHost,
raw("cspReportJson", cspReportJson)
)
metrics.countCspReports(blockedUriHost)
} catch (e: Exception) {
log.warn("CSP report handling error, with cspReportJson: {}", cspReportJson, e)
}
}
companion object {
val log: Logger = LoggerFactory.getLogger(CspReportController::class.java)
}
}
Disclaimer: the report-uri directive is deprecated, but the newer report-to directive isn’t supported yet by most browsers. See this "can i use" link for an up to date support overview of all browsers.