154 lines
6.7 KiB
Markdown
154 lines
6.7 KiB
Markdown
|
---
|
||
|
title: "Self-hosted report-uri"
|
||
|
date: 2018-11-27T00:00:00+01:00
|
||
|
draft: false
|
||
|
share: false
|
||
|
---
|
||
|
|
||
|
I've been playing with the security headers for this website for the past few days, most notably with the `Content-Security-Policy` as well as the `Expect-CT` headers.
|
||
|
|
||
|
After having spent a few hours on this, I'm pretty happy with the results !
|
||
|
|
||
|
![Screenshot-2018-11-27-at-21.52.58](/images/mozilla_observatory.png)
|
||
|
Source : [Observatory by Mozilla](https://observatory.mozilla.org/)
|
||
|
|
||
|
This website runs on a [Ghost](https://ghost.org/) installation that I keep up-to-date. Since an update might mean that the site will try to load new external resources, the `Content-Security-Policy` header might need updating as well.
|
||
|
|
||
|
This header has a `report-uri` directive that makes web browsers send json-formatted messages of policy violations they encounter.
|
||
|
|
||
|
There's a great website ([Report-URI](https://report-uri.com/)) that you can use to handle these reports. It allows up to 10.000 reports per month with a free account, which should be enough for a low to mid trafic website once you've setup your initial policy.
|
||
|
|
||
|
However, since I'm all about self-hosting *all of the things*, I figured I would configure my own report-uri using a php script.
|
||
|
|
||
|
## The script
|
||
|
|
||
|
This script is heavily inspired from the ones available [here](https://github.com/LastBreach/csp-report-to-syslog) and [here](https://mathiasbynens.be/notes/csp-reports).
|
||
|
|
||
|
The script checks that the content that was sent by the web browser is correctly formatted json message. It then removes the backslashes from the message, opens a connection to the local syslog daemon and sends the message.
|
||
|
|
||
|
```php
|
||
|
<?php
|
||
|
// Send `204 No Content` status code.
|
||
|
http_response_code(204);
|
||
|
// collect data from post request
|
||
|
$data = file_get_contents('php://input');
|
||
|
if ($data = json_decode($data)) {
|
||
|
// Remove slashes from the JSON-formatted data.
|
||
|
$data = json_encode(
|
||
|
$data, JSON_UNESCAPED_SLASHES
|
||
|
);
|
||
|
# set options for syslog daemon
|
||
|
openlog('report-uri', LOG_NDELAY, LOG_USER);
|
||
|
|
||
|
# send warning about csp report
|
||
|
syslog(LOG_WARNING, $data);
|
||
|
}
|
||
|
?>
|
||
|
```
|
||
|
|
||
|
## Nginx
|
||
|
|
||
|
I won't go into too much details regarding the nginx configuration here as I've written on this subject before.
|
||
|
|
||
|
Since I now have a wildcard Let's Encrypt certificate on captainark.net, I've decided to use a dedicated vhost for my report-uri. However, a subfolder would work just as well. Just make sure the script is stored in a folder that nginx can access.
|
||
|
|
||
|
I've also decided to call the script `index.php`. You can call it whatever you want, but your `report-uri` directive will have to match the full URL of the script (if I had named the script `report.php`, my `report-uri` would have been `https://report-uri.captainark.net/report.php` instead of `https://report-uri.captainark.net`).
|
||
|
|
||
|
A nginx location configured as follows should do the trick :
|
||
|
|
||
|
```
|
||
|
location / {
|
||
|
index index.php;
|
||
|
|
||
|
location ~ \.php$ {
|
||
|
try_files $uri =404;
|
||
|
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||
|
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
|
||
|
fastcgi_index index.php;
|
||
|
include fastcgi.conf;
|
||
|
fastcgi_hide_header X-Powered-By;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
I've omitted the security headers I usually configure in all locations here because they are outside of the scope of this article (HSTS, X-Frame-Options, etc.)
|
||
|
|
||
|
Once you've configured nginx, you can `nginx -t` to check that the syntax is correct, and `nginx -s reload` to reload the configuration.
|
||
|
|
||
|
## Syslog-ng
|
||
|
|
||
|
Now that our reports are being sent to syslog-ng, we need to log them as proprely formatted json messages, in a dedicated file.
|
||
|
|
||
|
I've created a `/etc/syslog-ng/conf.d/report-uri.conf` configuration file for that :
|
||
|
|
||
|
```
|
||
|
filter f_report-uri { program ("report-uri"); };
|
||
|
destination d_report-uri { file ("/var/log/report-uri/report-uri.json" template("{\"@timestamp\": \"${ISODATE}\", \"host\": \"${HOST}\", \"message\": ${MSG} }\n")); };
|
||
|
log { source(s_src); filter (f_report-uri); destination (d_report-uri); flags(final); };
|
||
|
```
|
||
|
|
||
|
We'll also need to create the folder for the logs :
|
||
|
```
|
||
|
mkdir -m 0750 /var/log/report-uri
|
||
|
chown root:adm /var/log/report-uri
|
||
|
```
|
||
|
|
||
|
You can then reload syslog-ng with a `systemctl reload syslog-ng.service`
|
||
|
|
||
|
Policy violation messages should now start to appear in the `/var/log/report-uri/report-uri.json`
|
||
|
|
||
|
If you want to test that it's working, you can create a `csp.json` file with the following content :
|
||
|
```json
|
||
|
{"csp-report":{"document-uri":"https://www.captainark.net/foo/bar","referrer":"https://www.google.com/","violated-directive":"default-src self","original-policy":"default-src self; report-uri https://report-uri.captainark.net","blocked-uri":"http://jscryptocurrency.cx"}}
|
||
|
```
|
||
|
|
||
|
You can now `POST` it to your report-uri :
|
||
|
```
|
||
|
curl -XPOST https://report-uri.captainark.net -d @csp.json
|
||
|
```
|
||
|
|
||
|
The message should be added to your `report-uri.json` log file, and you should be able to prettify it with `jq` :
|
||
|
|
||
|
```json
|
||
|
tail -n1 /var/log/report-uri/report-uri.json | jq
|
||
|
{
|
||
|
"@timestamp": "2018-11-27T22:57:06+01:00",
|
||
|
"host": "webserver",
|
||
|
"message": {
|
||
|
"csp-report": {
|
||
|
"document-uri": "https://www.captainark.net/foo/bar",
|
||
|
"referrer": "https://www.google.com/",
|
||
|
"violated-directive": "default-src self",
|
||
|
"original-policy": "default-src self; report-uri https://report-uri.captainark.net",
|
||
|
"blocked-uri": "http://jscryptocurrency.cx"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Logrotate
|
||
|
|
||
|
It's always a good idea to configure a log rotation when you add a new log file. To do so, let's create the `/etc/logrotate.d/report-uri` file with the following content :
|
||
|
|
||
|
```
|
||
|
/var/log/report-uri/report-uri.json {
|
||
|
rotate 8
|
||
|
weekly
|
||
|
notifempty
|
||
|
missingok
|
||
|
create 640 root adm
|
||
|
compress
|
||
|
copytruncate
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Conclusion
|
||
|
|
||
|
This configuration works as a report-uri for the `Content-Security` header as well as the newer `Expect-CT` header, and any future header that uses a report-uri directive (as long as the generated messages are json formatted).
|
||
|
|
||
|
Having a log file instead of the clean web interface of [Report URI](https://report-uri.com/) is not for everybody, but it is more than enough for my use case (this site gets like 10 clicks a day when I'm not playing with it so... yeah.)
|
||
|
|
||
|
Since the log messages are formatted in json, they should be pretty easy to integrate in [Elasticsearch](https://www.elastic.co/) or [Graylog](https://www.graylog.org/). If I ever decide to configure one of those solutions, I should then be able to configure cool looking dashboards in Grafana as well.
|
||
|
|
||
|
As always, if you've found this article useful in any way, please let me know in the comments here, on [Twitter](https://twitter.com/captainark) or on the [Fediverse](https://social.captainark.net/users/captainark) if you're a real cool kid !
|