542 lines
18 KiB
HTML
542 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en-us">
|
|
<head>
|
|
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
|
|
|
|
|
|
<meta name="twitter:card" content="summary"/>
|
|
|
|
|
|
|
|
|
|
<meta name="twitter:title" content="Self-hosted report-uri"/>
|
|
<meta name="twitter:description" content=""/>
|
|
<meta name="twitter:site" content="@"/>
|
|
|
|
|
|
|
|
<meta property="og:title" content="Self-hosted report-uri · Sysadmining. All day. Every day." />
|
|
<meta property="og:site_name" content="Sysadmining. All day. Every day." />
|
|
<meta property="og:url" content="https://www.captainark.net/2018/11/27/self-hosted-report-uri/" />
|
|
|
|
|
|
|
|
<meta property="og:image" content="/images/cover.jpg"/>
|
|
|
|
|
|
|
|
|
|
<meta property="og:description" content="" />
|
|
<meta property="og:type" content="article" />
|
|
<meta property="article:published_time" content="2018-11-27T00:00:00+01:00" />
|
|
|
|
|
|
|
|
|
|
<title>Self-hosted report-uri · Sysadmining. All day. Every day.</title>
|
|
|
|
|
|
<meta name="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" />
|
|
|
|
|
|
<meta name="HandheldFriendly" content="True" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<link rel="shortcut icon" href="/images/favicon.ico">
|
|
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png" />
|
|
|
|
<link rel="stylesheet" type="text/css" href="/css/screen.css" />
|
|
<link rel="stylesheet" type="text/css" href="/css/nav.css" />
|
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400|Inconsolata:700,400" />
|
|
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/solarized-light.min.css" integrity="sha384-bFKDPkG3geCujYJIbPornilfOgmYQoPS45Oh/8daqqo1SUwNY06OeHorpgnNvx82" crossorigin="anonymous">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js" integrity="sha384-BlPof9RtjBqeJFskKv3sK3dh4Wk70iKlpIe92FeVN+6qxaGUOUu+mZNpALZ+K7ya" crossorigin="anonymous"></script>
|
|
|
|
<script>hljs.initHighlightingOnLoad();</script>
|
|
|
|
|
|
|
|
|
|
<link href="/index.xml" rel="alternate" type="application/rss+xml" title="Sysadmining. All day. Every day." />
|
|
|
|
|
|
|
|
<meta name="generator" content="Hugo 0.53" />
|
|
|
|
<link rel="canonical" href="https://www.captainark.net/2018/11/27/self-hosted-report-uri/" />
|
|
|
|
|
|
|
|
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": ,
|
|
"logo": https://www.captainark.net/images/logo.png
|
|
},
|
|
"author": {
|
|
"@type": "Person",
|
|
"name": ,
|
|
|
|
"image": {
|
|
"@type": "ImageObject",
|
|
"url": https://www.captainark.net/images/author.jpg,
|
|
"width": 250,
|
|
"height": 250
|
|
},
|
|
|
|
"url": https://www.captainark.net,
|
|
"sameAs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
],
|
|
"description": Geek | Gamer | TV Shows Aficionado
|
|
|
|
},
|
|
"headline": Self-hosted report-uri,
|
|
"name": Self-hosted report-uri,
|
|
"wordCount": 849,
|
|
"timeRequired": "PT4M",
|
|
"inLanguage": {
|
|
"@type": "Language",
|
|
"alternateName": en
|
|
},
|
|
"url": https://www.captainark.net/2018/11/27/self-hosted-report-uri/,
|
|
"datePublished": 2018-11-27T00:00Z,
|
|
"dateModified": 2018-11-27T00:00Z,
|
|
|
|
|
|
"description": ,
|
|
"mainEntityOfPage": {
|
|
"@type": "WebPage",
|
|
"@id": https://www.captainark.net/2018/11/27/self-hosted-report-uri/
|
|
}
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
(function(f, a, t, h, o, m){
|
|
a[h]=a[h]||function(){
|
|
(a[h].q=a[h].q||[]).push(arguments)
|
|
};
|
|
o=f.createElement('script'),
|
|
m=f.getElementsByTagName('script')[0];
|
|
o.async=1; o.src=t; o.id='fathom-script';
|
|
m.parentNode.insertBefore(o,m)
|
|
})(document, window, '//stats.captainark.net/tracker.js', 'fathom');
|
|
fathom('set', 'siteId', 'BYKXT');
|
|
fathom('trackPageview');
|
|
</script>
|
|
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.5/css/fork-awesome.min.css" integrity="sha256-P64qV9gULPHiZTdrS1nM59toStkgjM0dsf5mK/UwBV4=" crossorigin="anonymous">
|
|
|
|
|
|
</head>
|
|
<body class="nav-closed">
|
|
|
|
<div class="nav">
|
|
<h3 class="nav-title">Menu</h3>
|
|
<a href="#" class="nav-close">
|
|
<span class="hidden">Close</span>
|
|
</a>
|
|
<ul>
|
|
|
|
|
|
|
|
<h3>This site</h3>
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="/">Home</a>
|
|
</li>
|
|
|
|
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="/about">About</a>
|
|
</li>
|
|
|
|
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="/resume">Resume</a>
|
|
</li>
|
|
|
|
<h3>Other services</h3>
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="https://git.captainark.net">Gitea</a>
|
|
</li>
|
|
|
|
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="https://pics.captainark.net">Chevereto</a>
|
|
</li>
|
|
|
|
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="https://paste.captainark.net">Privatebin</a>
|
|
</li>
|
|
|
|
|
|
<li class="nav-opened" role="presentation">
|
|
<a href="https://chat.captainark.net">Rocket.Chat</a>
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<a class="subscribe-button icon-feed" href="/index.xml">Subscribe</a>
|
|
|
|
</div>
|
|
<span class="nav-cover"></span>
|
|
|
|
|
|
<div class="site-wrapper">
|
|
|
|
|
|
|
|
<header class="main-header post-head no-cover">
|
|
<nav class="main-nav clearfix">
|
|
|
|
|
|
|
|
<a class="blog-logo" href="https://www.captainark.net/"><img src="/images/logo.png" alt="Home" /></a>
|
|
|
|
|
|
<a class="menu-button" href="#"><span class="burger">☰</span><span class="word">Menu</span></a>
|
|
|
|
</nav>
|
|
</header>
|
|
|
|
|
|
|
|
<main class="content" role="main">
|
|
|
|
|
|
|
|
|
|
<article class="post post">
|
|
|
|
<header class="post-header">
|
|
<h1 class="post-title">Self-hosted report-uri</h1>
|
|
<small></small>
|
|
|
|
<section class="post-meta">
|
|
|
|
<time class="post-date" datetime="2018-11-27T00:00:00+01:00">
|
|
27 November 2018
|
|
</time>
|
|
|
|
|
|
</section>
|
|
</header>
|
|
|
|
<section class="post-content">
|
|
|
|
|
|
<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>
|
|
|
|
<p><img src="/images/mozilla_observatory.png" alt="Screenshot-2018-11-27-at-21.52.58" />
|
|
Source : <a href="https://observatory.mozilla.org/">Observatory by Mozilla</a></p>
|
|
|
|
<p>This website runs on a <a href="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 (<a href="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>
|
|
|
|
<h2 id="the-script">The script</h2>
|
|
|
|
<p>This script is heavily inspired from the ones available <a href="https://github.com/LastBreach/csp-report-to-syslog">here</a> and <a href="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><code class="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>
|
|
|
|
<h2 id="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>
|
|
|
|
<h2 id="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"); };
|
|
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); };
|
|
</code></pre>
|
|
|
|
<p>We’ll also need to create the folder for the logs :</p>
|
|
|
|
<pre><code>mkdir -m 0750 /var/log/report-uri
|
|
chown root:adm /var/log/report-uri
|
|
</code></pre>
|
|
|
|
<p>You can then reload syslog-ng with a <code>systemctl reload syslog-ng.service</code></p>
|
|
|
|
<p>Policy violation messages should now start to appear in the <code>/var/log/report-uri/report-uri.json</code></p>
|
|
|
|
<p>If you want to test that it’s working, you can create a <code>csp.json</code> file with the following content :</p>
|
|
|
|
<pre><code class="language-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"}}
|
|
</code></pre>
|
|
|
|
<p>You can now <code>POST</code> it to your report-uri :</p>
|
|
|
|
<pre><code>curl -XPOST https://report-uri.captainark.net -d @csp.json
|
|
</code></pre>
|
|
|
|
<p>The message should be added to your <code>report-uri.json</code> log file, and you should be able to prettify it with <code>jq</code> :</p>
|
|
|
|
<pre><code class="language-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"
|
|
}
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h2 id="logrotate">Logrotate</h2>
|
|
|
|
<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>
|
|
|
|
<h2 id="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 <a href="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 <a href="https://www.elastic.co/">Elasticsearch</a> or <a href="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 <a href="https://twitter.com/captainark">Twitter</a> or on the <a href="https://social.captainark.net/users/captainark">Fediverse</a> if you’re a real cool kid !</p>
|
|
|
|
</section>
|
|
|
|
|
|
<footer class="post-footer">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<figure class="author-image">
|
|
<a class="img" href="https://www.captainark.net/" style="background-image: url(/images/author.jpg)"><span class="hidden">Antoine Joubert's Picture</span></a>
|
|
</figure>
|
|
|
|
|
|
<section class="author">
|
|
<h4><a href="https://www.captainark.net/">Antoine Joubert</a></h4>
|
|
|
|
<p>Geek | Gamer | TV Shows Aficionado</p>
|
|
|
|
<div class="author-meta">
|
|
<span class="author-location icon-location">Angers, France</span>
|
|
<span class="author-link icon-link"><a href="https://www.captainark.net">https://www.captainark.net</a></span>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- isso -->
|
|
<script data-isso="https://www.captainark.net/comments/" src="https://www.captainark.net/comments/js/embed.min.js"></script>
|
|
<noscript>Please enable JavaScript to view comments</noscript>
|
|
<section id="isso-thread"></section>
|
|
<!-- end isso -->
|
|
|
|
|
|
|
|
|
|
</footer>
|
|
</article>
|
|
|
|
</main>
|
|
|
|
|
|
<aside class="read-next">
|
|
|
|
<a class="read-next-story" style="no-cover" href="/2018/12/03/debian-repos-over-https/">
|
|
<section class="post">
|
|
<h2>Debian repos over HTTPS</h2>
|
|
|
|
</section>
|
|
</a>
|
|
|
|
|
|
<a class="read-next-story prev" style="no-cover" href="/2018/04/14/dns-zone-versioning/">
|
|
<section class="post">
|
|
<h2>DNS zone versioning</h2>
|
|
</section>
|
|
</a>
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<center>
|
|
<a class="fa-icons" href="mailto:contact@captainark.net">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://twitter.com/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://social.captainark.net/users/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-mastodon-alt fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://github.com/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://www.last.fm/user/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-lastfm fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://steamcommunity.com/id/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-steam fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
<a class="fa-icons" href="https://www.twitch.tv/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-twitch fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</center>
|
|
|
|
|
|
<footer class="site-footer clearfix">
|
|
<section class="copyright"><a href="">Sysadmining. All day. Every day.</a> © 2015 - 2019</section>
|
|
|
|
<section class="poweredby">Proudly generated by <a class="icon-hugo" href="http://gohugo.io">HUGO</a>, with <a class="icon-theme" href="https://github.com/vjeantet/hugo-theme-casper">Casper</a> theme</section>
|
|
|
|
</footer>
|
|
</div>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha384-CgeP3wqr9h5YanePjYLENwCTSSEz42NJkbFpAFgHWQz7u3Zk8D00752ScNpXqGjS" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fitvids/1.1.0/jquery.fitvids.min.js" integrity="sha384-2/VQUb0aZHixKnNLh7pD38DZk+acGpEw5LeHieWVDPR0h/H326kp/1qnRPDYmFXM" crossorigin="anonymous"></script>
|
|
<script type="text/javascript" src="/js/index.js"></script>
|
|
</body>
|
|
</html>
|
|
|