<title>Self-hosted report-uri · Sysadmining. All day. Every day.</title>
<metaname="description"content="I&rsquo;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"/>
<p>I’ve been playing with the security headers for this website for the past few days, most notably with the <code>Content-Security-Policy</code> as well as the <code>Expect-CT</code> headers.</p>
<p>After having spent a few hours on this, I’m pretty happy with the results !</p>
Source : <ahref="https://observatory.mozilla.org/">Observatory by Mozilla</a></p>
<p>This website runs on a <ahref="https://ghost.org/">Ghost</a> installation that I keep up-to-date. Since an update might mean that the site will try to load new external resources, the <code>Content-Security-Policy</code> header might need updating as well.</p>
<p>This header has a <code>report-uri</code> directive that makes web browsers send json-formatted messages of policy violations they encounter.</p>
<p>There’s a great website (<ahref="https://report-uri.com/">Report-URI</a>) 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.</p>
<p>However, since I’m all about self-hosting <em>all of the things</em>, I figured I would configure my own report-uri using a php script.</p>
<h2id="the-script">The script</h2>
<p>This script is heavily inspired from the ones available <ahref="https://github.com/LastBreach/csp-report-to-syslog">here</a> and <ahref="https://mathiasbynens.be/notes/csp-reports">here</a>.</p>
<p>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.</p>
<pre><codeclass="language-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);
}
?>
</code></pre>
<h2id="nginx">Nginx</h2>
<p>I won’t go into too much details regarding the nginx configuration here as I’ve written on this subject before.</p>
<p>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.</p>
<p>I’ve also decided to call the script <code>index.php</code>. You can call it whatever you want, but your <code>report-uri</code> directive will have to match the full URL of the script (if I had named the script <code>report.php</code>, my <code>report-uri</code> would have been <code>https://report-uri.captainark.net/report.php</code> instead of <code>https://report-uri.captainark.net</code>).</p>
<p>A nginx location configured as follows should do the trick :</p>
<pre><code> 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;
}
}
</code></pre>
<p>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.)</p>
<p>Once you’ve configured nginx, you can <code>nginx -t</code> to check that the syntax is correct, and <code>nginx -s reload</code> to reload the configuration.</p>
<h2id="syslog-ng">Syslog-ng</h2>
<p>Now that our reports are being sent to syslog-ng, we need to log them as proprely formatted json messages, in a dedicated file.</p>
<p>I’ve created a <code>/etc/syslog-ng/conf.d/report-uri.conf</code> configuration file for that :</p>
<pre><code>filter f_report-uri { program ("report-uri"); };
<p>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 <code>/etc/logrotate.d/report-uri</code> file with the following content :</p>
<pre><code>/var/log/report-uri/report-uri.json {
rotate 8
weekly
notifempty
missingok
create 640 root adm
compress
copytruncate
}
</code></pre>
<h2id="conclusion">Conclusion</h2>
<p>This configuration works as a report-uri for the <code>Content-Security</code> header as well as the newer <code>Expect-CT</code> header, and any future header that uses a report-uri directive (as long as the generated messages are json formatted).</p>
<p>Having a log file instead of the clean web interface of <ahref="https://report-uri.com/">Report URI</a> 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.)</p>
<p>Since the log messages are formatted in json, they should be pretty easy to integrate in <ahref="https://www.elastic.co/">Elasticsearch</a> or <ahref="https://www.graylog.org/">Graylog</a>. If I ever decide to configure one of those solutions, I should then be able to configure cool looking dashboards in Grafana as well.</p>
<p>As always, if you’ve found this article useful in any way, please let me know in the comments here, on <ahref="https://twitter.com/captainark">Twitter</a> or on the <ahref="https://social.captainark.net/users/captainark">Fediverse</a> if you’re a real cool kid !</p>
<sectionclass="poweredby">Proudly generated by <aclass="icon-hugo"href="http://gohugo.io">HUGO</a>, with <aclass="icon-theme"href="https://github.com/vjeantet/hugo-theme-casper">Casper</a> theme</section>