captainarkdotnet/content/post/2018-11-27-self-hosted-repo...

154 lines
6.7 KiB
Markdown
Raw Normal View History

2019-01-06 16:29:44 +01:00
---
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 !