<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Lieter&#39;s blog</title>
		<link>https://blog.lieter.nl/posts/</link>
		<description>Recent content on Lieter&#39;s blog</description>
		<generator>Hugo -- gohugo.io</generator>
		<language>en-us</language>
		<copyright>This work is licensed under a Creative Commons Attribution 4.0 International License.</copyright>
		<lastBuildDate>Mon, 21 Sep 2020 11:15:00 +0200</lastBuildDate>
		<atom:link href="https://blog.lieter.nl/posts/index.xml" rel="self" type="application/rss+xml" />
		
		<item>
			<title>Running systemd-nspawn containers with a VPN interface</title>
			<link>https://blog.lieter.nl/posts/systemd-nspawn-multi-interfaces-container-wireguard/</link>
			<pubDate>Mon, 21 Sep 2020 11:15:00 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/systemd-nspawn-multi-interfaces-container-wireguard/</guid>
			<description>Recently, there was a project where someone needed to do some measurements using my infrastructure (1). They just needed a &amp;ldquo;machine&amp;rdquo; that they could connect to over wireguard that also had an internet connection. Naturally, I wanted to run said &amp;ldquo;machine&amp;rdquo; in systemd-nspawn. With just a few lines of configuration for a few systemd components, we can create a container that takes a VPN interface from the host, configures an address on it so the container can be accessed via that VPN.</description>
			<content type="html"><![CDATA[<p>Recently, there was a project where someone needed to do some measurements using my infrastructure (1).
They just needed a &ldquo;machine&rdquo; that they could connect to over <a href="https://www.wireguard.com/">wireguard</a> that also had an internet connection.
Naturally, I wanted to run said &ldquo;machine&rdquo; in <a href="https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html"><code>systemd-nspawn</code></a>.
With just a few lines of configuration for a few systemd components, we can create a container that takes a VPN interface from the host, configures an address on it so the container can be accessed via that VPN.</p>
<p>The container is called <code>ubuntu-focal-interfaces</code> and the wireguard interface is called <code>wg1</code>.</p>
<p>Here&rsquo;s the steps needed to accomplish that.</p>
<h1 id="adding-the-wireguard-interface-on-the-host-with-systemd-networkd">Adding the Wireguard interface on the host with systemd-networkd</h1>
<p>If you&rsquo;re not using <a href="https://wiki.archlinux.org/index.php/Systemd-networkd"><code>systemd-networkd</code></a> on the host machine, just create a wireguard interface as described in the wireguard docs but don&rsquo;t set an IP address on the interface.</p>
<p><code>systemd-networkd</code> can create wireguard interfaces itself using a <a href="https://www.freedesktop.org/software/systemd/man/systemd.netdev.html#"><code>.netdev</code></a> file.
Create one in <code>/etc/systemd/network/wg1.netdev</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[NetDev]</span>
<span style="color:#a6e22e">Name</span><span style="color:#f92672">=</span><span style="color:#e6db74">wg1</span>
<span style="color:#a6e22e">Kind</span><span style="color:#f92672">=</span><span style="color:#e6db74">wireguard</span>
<span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Wireguard client interface, used in a container</span>

<span style="color:#66d9ef">[WireGuard]</span>
<span style="color:#75715e"># Bonus points if you actually use PrivateKeyFile</span>
<span style="color:#a6e22e">PrivateKey</span><span style="color:#f92672">=</span><span style="color:#e6db74">XXXXXX</span>
<span style="color:#a6e22e">ListenPort</span><span style="color:#f92672">=</span><span style="color:#e6db74">51820</span>

<span style="color:#66d9ef">[WireGuardPeer]</span>
<span style="color:#a6e22e">Endpoint</span><span style="color:#f92672">=</span><span style="color:#e6db74">some-host.example.com:51820</span>
<span style="color:#a6e22e">PublicKey</span><span style="color:#f92672">=</span><span style="color:#e6db74">YYYYYY</span>
<span style="color:#a6e22e">AllowedIPs</span><span style="color:#f92672">=</span><span style="color:#e6db74">192.0.2.0/24</span>

<span style="color:#75715e"># Ensure the NAT knows about this connection</span>
<span style="color:#a6e22e">PersistentKeepalive</span><span style="color:#f92672">=</span><span style="color:#e6db74">60</span>
</code></pre></div><p>As it contains private keys, <code>systemd-networkd</code> will refuse to load it if the permissions are too wide, so</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">sudo chown root:systemd-network /etc/systemd/network/wg1.netdev
sudo chmod <span style="color:#ae81ff">640</span> /etc/systemd/network/wg1.netdev<span style="color:#e6db74">`</span>.
</code></pre></div><p>Now create the interface:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">sudo networkctl reload
</code></pre></div><h1 id="creating-the-image">Creating the image</h1>
<p>Now we actually need to run a container, for this we need an image that nspawn can run.
Fortunately, <a href="https://github.com/systemd/mkosi"><code>mkosi</code></a> has us covered.
Download/install it and create a directory where we&rsquo;ll add the image build config.</p>
<p>Now create a <code>mkosi.default</code> file:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[Distribution]</span>
<span style="color:#a6e22e">Distribution</span><span style="color:#f92672">=</span><span style="color:#e6db74">ubuntu</span>
<span style="color:#a6e22e">Release</span><span style="color:#f92672">=</span><span style="color:#e6db74">focal</span>

<span style="color:#66d9ef">[Packages]</span>
<span style="color:#a6e22e">Packages</span><span style="color:#f92672">=</span><span style="color:#e6db74">iproute2</span>
</code></pre></div><p>We&rsquo;ll bake in the setting of the IP address on the interface into the image, <code>mkosi.postinst</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>systemctl enable systemd-networkd
echo <span style="color:#e6db74">&#34;[Match]
</span><span style="color:#e6db74">Name=wg1
</span><span style="color:#e6db74">
</span><span style="color:#e6db74">[Network]
</span><span style="color:#e6db74">Address=192.0.2.2/24&#34;</span> &gt; /etc/systemd/network/wg1.network
</code></pre></div><p>Now build the image and import it into <a href="https://www.freedesktop.org/software/systemd/man/systemd-machined.service.html"><code>machined</code></a>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">chmod +x mkosi.postinst
sudo mkosi
<span style="color:#75715e"># wait......</span>
sudo machinectl import-raw image.raw ubuntu-focal-interfaces
</code></pre></div><h1 id="adding-the-right-interfaces-to-the-container">Adding the right interfaces to the container</h1>
<p>Configure nspawn to pass the wg1 interface to the container and set up a veth interface as well.
Create a file <code>/etc/systemd/nspawn/ubuntu-focal-interfaces.nspawn</code></p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[Network]</span>
<span style="color:#a6e22e">VirtualEthernet</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
<span style="color:#a6e22e">Interface</span><span style="color:#f92672">=</span><span style="color:#e6db74">wg1</span>
</code></pre></div><h1 id="boot-the-container">Boot the container</h1>
<p>Now start the container:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">sudo machinectl start ubuntu-focal-interfaces
</code></pre></div><p>If you now run <code>ip address show</code> on the host, you&rsquo;ll see the <code>wg1</code> interface is gone, as it has moved to the container&rsquo;s network namespace.</p>
<p>When you open a shell in the container (<code>sudo machinectl shell ubuntu-focal-interfaces</code>) and run <code>ip address show</code> there, you&rsquo;ll see two interfaces, one veth interface that has internet access and one <code>wg1</code> interface with the configured address.</p>
<p>Should you stop the container (<code>sudo machinectl stop ubuntu-focal-interfaces</code>), the <code>wg1</code> interface is moved back into the host&rsquo;s namespace.
The IP address, however, is no longer configured on the interface.
This means I can now now safely tell the people who need access the public key of the wireguard interface, start the container, install SSH and give them access to do whatever they need to do.</p>
<h1 id="security-considerations">Security Considerations</h1>
<p>Should the other party have root permissions inside your container, they can still edit the wireguard interface&rsquo;s parameters.
These changes will be lost once the host is rebooted of course, but keep this in mind when moving interfaces between namespaces and containers.</p>
<h1 id="footnotes">Footnotes</h1>
<ol>
<li>&ldquo;infrastructure&rdquo; sounds impressive, this whole thing ran on the Raspberry Pi 4 that is my house&rsquo;s wiring closet conencted to the uplink switch.</li>
</ol>
]]></content>
		</item>
		
		<item>
			<title>NLUUG Fall 2019 Conference</title>
			<link>https://blog.lieter.nl/posts/nluug-nj-2019/</link>
			<pubDate>Fri, 22 Nov 2019 10:19:47 +0100</pubDate>
			
			<guid>https://blog.lieter.nl/posts/nluug-nj-2019/</guid>
			<description>On the 21st of November the Dutch Unix User Group (NLUUG) held its fall conference in Utrecht.
The keynote on what Site Reliability Engineering (SRE) means by David Blank-Edelmann from Microsoft was a treat to listen to.
After that the multi-track part of the day started. It was hard to pick what talk to attend, but I was at no point disappointed by the choices made.
Michael Boelen did a highly interactive talk on improving the shell-scripting game of those in the audience.</description>
			<content type="html"><![CDATA[<p>On the 21st of November the Dutch Unix User Group (NLUUG) held its <a href="https://www.nluug.nl/events/nj19/programma/index.html">fall conference</a> in Utrecht.</p>
<p>The keynote on what <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab00.html">Site Reliability Engineering (SRE)</a> means by <a href="https://twitter.com/otterbook">David Blank-Edelmann</a> from Microsoft was a treat to listen to.</p>
<p>After that the multi-track part of the day started.
It was hard to pick what talk to attend, but I was at no point disappointed by the choices made.</p>
<p><a href="https://twitter.com/mboelen">Michael Boelen</a> did a <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab01.html">highly interactive talk</a> on improving the shell-scripting game of those in the audience.</p>
<p>After that, <a href="https://twitter.com/reseauxsansfil/">Roland van Rijswijk-Deij</a> donned a proper shirt (&ldquo;I was considering a suit&rdquo;) to explain the <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab09.html">Quantum Blockchain Cloud</a> and why not all crypto is doomed with the coming Quantum Supremecy.</p>
<p>Later in the afternoon, Anco Scholte ter Horst gave a <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab15.html">small history</a> of the absorbsion of XS4All into KPN, why and how <a href="https://freedom.nl/">Freedom Internet</a> started, and what the plans for the near-future are.</p>
<p>Just before I had to hit the stage, Wim ter Have from Oracle gave a highly technical talk on <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab16.html">NUMA awareness in libvirt</a>. Allowing the mapping of NUMA nodes from the host to guests for blazing performance.</p>
<p>After that it was my time to present an updated version of my LOADays 2019 on <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab08.html"><code>systemd-nspawn</code> and <code>mkosi</code></a> that I accidentally started in Dutch (sorry!). The slides are available <a href="/presentations/plexis-NLUUG-fall-2019-systemd-nspawn.pdf">here</a>.</p>
<p>After that, it was time for the <a href="https://www.nluug.nl/activiteiten/events/nj19/abstracts/ab03.html">closing keynote</a> on security fails and hack stories by <a href="https://twitter.com/Yafsec">Edwin van Andel</a>.</p>
<p>All the talks were recorded, and I&rsquo;ll certainly view some of the ones I missed.
Hats off to the NLUUG program committee for putting together such a highly informative and densely packed conference.</p>
]]></content>
		</item>
		
		<item>
			<title>Loadays 2018 review</title>
			<link>https://blog.lieter.nl/posts/loadays-2018/</link>
			<pubDate>Mon, 23 Apr 2018 10:14:15 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/loadays-2018/</guid>
			<description>This weekend I attended the small-scale LOADays conference in Antwerp, Belgium. LOADays (Linux Open Administration Days) is a conference focusing on many aspects of Linux (and UNIX) system administration.
I must admit I did not watch all the talks, as it was sunny outside and the discussions were good as well.
Frank Louwers kicked off day 1 with his talk &amp;ldquo;GDPR for nerds&amp;rdquo;. It was a good introduction into the terms, rules and consequences of the European General Data Protection Regulation that will go into effect May 25th of 2018.</description>
			<content type="html"><![CDATA[<p>This weekend I attended the small-scale <a href="https://loadays.org">LOADays</a> conference in Antwerp, Belgium.
LOADays (Linux Open Administration Days) is a conference focusing on many aspects of Linux (and UNIX) system administration.</p>
<p>I must admit I did not watch all the talks, as it was sunny outside and the discussions were good as well.</p>
<p><a href="https://twitter.com/frank_be">Frank Louwers</a> kicked off day 1 with his talk <a href="https://www.frank.be/gdpr-for-nerds/">&ldquo;GDPR for nerds&rdquo;</a>.
It was a good introduction into the terms, rules and consequences of the European General Data Protection Regulation that will go into effect May 25th of 2018.
The main take-away was &ldquo;When in doubt, you are a data processor&rdquo;.</p>
<p>Friend of <a href="https://twitter.com/powerdns">PowerDNS</a> and all-round excellent person <a href="https://twitter.com/jpmens">Jan-Piet Mens</a> gave an introduction to <a href="https://www.ansible.com/products/awx-project">Ansible AWX</a>.
AWX is the Open-Source version of the commercial Ansible Tower product, a centralized dashboard from where one can control their infrastructure.</p>
<p>On the security-front, <a href="https://twitter.com/Jean_Maes_1994">Jean-François Maes</a> gave a short pen-test walk-through of a (purposely) badly secured Docker deployment that allowed him to <a href="http://loadays.org/pages/pentest.html">gain root</a>.
During this walk-through, several easy to make mistakes in application deployments were discussed.</p>
<p>After this, it was time for more sunny discussions and the famous beer and fresh-baked pizza social.</p>
<p>The morning of the second day was filled with theoretical talks. First there was Bram Vogelaar discussing <a href="http://loadays.org/pages/solid_iac.html">Object-Oriented principles for Infrastructure as Code</a>, using Puppet as an example.
Followed by <a href="https://twitter.com/svg">Serge van Ginderachter</a> who talked about a non-existing project that would handle inventory and variables in an intuitive way, discussing theory and practical pitfalls alike.</p>
<p>In the afternoon it was up to <a href="https://twitter.com/lieter_">me</a> to tell the assembled collection of system administrators about the <a href="https://en.blog.nic.cz/2018/03/14/together-for-better-stability-speed-and-further-extensibility-of-the-dns-ecosystem/">upcoming removal of EDNS work arounds</a> by the 4 large Open-Source DNS Recursor vendors.
The most important message was &ldquo;run up-to-date software, check you firewalls and middleboxes and <a href="https://ednscomp.isc.org/ednscomp/">test your domains</a>&rdquo;.
For those interested, the slide deck is <a href="/presentations/plexis-edns-workaround-removal-loadays-2018.pdf">available</a>.</p>
<p><a href="https://twitter.com/PCzanik">Peter Czanik</a> was up next to talk about <a href="http://loadays.org/pages/syslog-ng.html">&ldquo;making sense of syslog-ng&rdquo;</a>, where to me it was clear that it can do almost everything <a href="https://www.elastic.co/products/logstash">LogStash</a> does, which was refreshing.</p>
<p>Last talk that I attended was <a href="https://twitter.com/kprovst">Kristof Provost</a>&rsquo;s humorous overview of <a href="https://www.freebsd.org/">FreeBSD</a>.
He discussed its history, differences with GNU/Linux and project philosophy.
As expected at a Linux-oriented conference, <a href="https://twitter.com/Habbie/status/988055084486545411">shenanigans</a> <a href="https://twitter.com/jpmens/status/988060025649336320">were</a> <a href="https://twitter.com/PCzanik/status/988066057767223297">aplenty</a>.</p>
<p>All in all, it was a very successful, sunny and densely-packed Loadays.
Thanks to <a href="https://twitter.com/toshywoshy">Toshaan Bharvani</a>, <a href="https://twitter.com/KrisBuytaert">Kris Buytaert</a> and the rest of the team for putting it together and allowing me the opportunity to speak.</p>
<p>Update 2018-05-06: <a href="https://twitter.com/maniacnl">@maniacnl</a> has uploaded a video of the presentation, embedded below.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/OXbbH0ORmSY" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

]]></content>
		</item>
		
		<item>
			<title>The powerdns.com SOA validation failure</title>
			<link>https://blog.lieter.nl/posts/powerdns.com-soa-dnssec-bogus/</link>
			<pubDate>Mon, 16 Oct 2017 17:12:33 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/powerdns.com-soa-dnssec-bogus/</guid>
			<description>On October 12th, the SOA record for powerdns.com did not pass DNSSEC validation due to a broken signature. A string of events lead to the creation of PowerDNS bug #5814 and incidently also to #5807.
The Plan and Execution On the 10th of October we started a small internal project to change the set up of our authoritative infrastructure. This project had as a goal to have a more representable DNS authoritative infrastructure we could use to better dogfood our own software.</description>
			<content type="html"><![CDATA[<p>On October 12th, the SOA record for powerdns.com did not pass DNSSEC validation due to a broken signature.
A string of events lead to the creation of PowerDNS bug <a href="https://github.com/PowerDNS/pdns/issues/5814">#5814</a> and incidently also to <a href="https://github.com/PowerDNS/pdns/issues/5807">#5807</a>.</p>
<h1 id="the-plan-and-execution">The Plan and Execution</h1>
<p>On the 10th of October we started a small internal project to change the set up of our authoritative infrastructure.
This project had as a goal to have a more representable DNS authoritative infrastructure we could use to better <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">dogfood</a> our own software.
In short, this was the work that had to be done:</p>
<ul>
<li>Upgrade to <a href="https://blog.powerdns.com/2017/08/31/powerdns-authoritative-server-4-1-0-release-candidate-1-released/">4.1.0-rc</a> on pdns-public-ns1.powerdns.com</li>
<li>Switch to the <a href="https://doc.powerdns.com/authoritative/backends/generic-sqlite3.html">gsqlite3 backend</a> from the <a href="https://doc.powerdns.com/authoritative/backends/bind.html">BIND backend</a></li>
<li>Add a hidden master on pdns-public-ns1.powerdns.com and slave via the local loopback</li>
</ul>
<p>The upgrade was easy enough with the use of the <a href="https://repo.powerdns.com">repositories</a>.
The whole migration from BIND to gsqlite3 was very straight-forward thanks to <a href="https://doc.powerdns.com/authoritative/migration.html#migrating-data-from-one-backend-to-another-backend"><code>pdnsutil b2b-migrate</code></a>, although <a href="https://github.com/PowerDNS/pdns/issues/5807">some bugs</a> were found and subsequently <a href="https://github.com/PowerDNS/pdns/pull/5810">fixed</a>.
Adding the hidden master was also effortless and after changing the zones from &lsquo;MASTER&rsquo; to &lsquo;SLAVE&rsquo; and setting the master addresses to the local loopback address the whole chain of zone transfers was tested and worked properly: NOTIFY mesages were sent, AXFRs were performed and <code>dig</code> was happy.</p>
<p>Yay, Time to stop working for the day!</p>
<h1 id="the-missed-warning-signs">The Missed Warning Signs</h1>
<p>The next morning I was pretty sleepy on a train on my way to <a href="https://www.open-xchange.com/summit/ox17-bruxelles/">Open-Xchange Summit</a> and saw a bunch of monitoring emails come in regarding the DNSSEC validation of our domains:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">Info:    CRITICAL powerdns.com: validation failure &lt;powerdns.com. SOA IN&gt;: RSA signature verification failed from 2604:a880:1:20::132:5001
</code></pre></div><p>And yes, we actually monitor <a href="https://github.com/pieterlexis/nagios-check-zoneauth">SOA freshness</a> between master and slaves and the <a href="https://github.com/pieterlexis/icinga-check-dnssec-validation">DNSSEC chain of trust</a> for all our domains (as one should).
In my sleepy state, I decided these were probably emails from yesterday and just stared out the window until I arrived in Brussels.
Once there, I got a message from the cool people at <a href="https://internet.nl">internet.nl</a> that the RRSIG sent alongside the SOA for powerdns.com was not valid.
Oops!</p>
<h1 id="the-issue">The Issue</h1>
<p>In the PowerDNS Authoritative Server, we try really hard to ensure that slave server will fetch fresh RRSIGs when zones are resigned.
One of these ways is the <a href="https://doc.powerdns.com/authoritative/domainmetadata.html#metadata-soa-edit">SOA-EDIT domain metadata</a>.
This piece of metadata lets the nameserver process bump the SOA serial artificially when serving the record from the backend.</p>
<p>There is also another piece of metadata called <a href="https://doc.powerdns.com/authoritative/domainmetadata.html#metadata-presigned">PRESIGNED</a>.
This indicates that the zone was AXFR&rsquo;d from a master and already has DNSSEC signatures in the backend, it is set automatically when we see RRSIGs in the incoming AXFR.</p>
<p>During the <code>b2b-migrate</code>, the domain metadata for all zones was correctly migrated.
This included the SOA-EDIT we had in place for powerdns.com.</p>
<p>So after AXFR&rsquo;ing the zone, we had the following metadata in the database:</p>
<pre><code>select * from domainmetadata where domain_id=41;
9|41|SOA-EDIT|INCEPTION-INCREMENT
11|41|PRESIGNED|1
</code></pre><p>When querying the nameserver the following records were returned:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">; &lt;&lt;&gt;&gt; DiG 9.11.2 &lt;&lt;&gt;&gt; +dnssec soa powerdns.com @pdns-public-ns1.powerdns.com.
...
;; ANSWER SECTION:
powerdns.com.      <span style="color:#ae81ff">3600</span>  IN   SOA     pdns-public-ns1.powerdns.com. pieter<span style="color:#ae81ff">\.</span>lexis.powerdns.com. <span style="color:#ae81ff">2017101203</span> <span style="color:#ae81ff">10800</span> <span style="color:#ae81ff">3600</span> <span style="color:#ae81ff">604800</span> <span style="color:#ae81ff">3600</span>
powerdns.com.      <span style="color:#ae81ff">3600</span>  IN   RRSIG   SOA <span style="color:#ae81ff">8</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3600</span> <span style="color:#ae81ff">20171026000000</span> <span style="color:#ae81ff">20171005000000</span> <span style="color:#ae81ff">36021</span> powerdns.com. C/VixIC.....
</code></pre></div><p>After some debugging, the PRESIGNED metadata was removed.
And lo and behold, the following was returned:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">;; ANSWER SECTION:
powerdns.com.      <span style="color:#ae81ff">3600</span>  IN   RRSIG   SOA <span style="color:#ae81ff">8</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3600</span> <span style="color:#ae81ff">20171026000000</span> <span style="color:#ae81ff">20171005000000</span> <span style="color:#ae81ff">36021</span> powerdns.com. C/VixIC.....
powerdns.com.      <span style="color:#ae81ff">3600</span>  IN   SOA     pdns-public-ns1.powerdns.com. pieter<span style="color:#ae81ff">\.</span>lexis.powerdns.com. <span style="color:#ae81ff">2017101201</span> <span style="color:#ae81ff">10800</span> <span style="color:#ae81ff">3600</span> <span style="color:#ae81ff">604800</span> <span style="color:#ae81ff">3600</span>
</code></pre></div><p>Notice that the SOA serial is reduced by 2.
This SOA record actually validates correctly:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">Info:    OK powerdns.com: the chain of trust is valid.
</code></pre></div><p>So it appears PowerDNS honors the SOA-EDIT metadata for pre-signed zones, oops!
To prevent others from running into this issue, the <a href="https://github.com/PowerDNS/pdns/issues/5814">problem</a> was clearly documented and a <a href="https://github.com/PowerDNS/pdns/pull/5815">solution</a> was writen within an hour.
This fix will land in the next release of the PowerDNS Authoritative Server, and will be backported to the 4.0 release train as well.</p>
<h1 id="what-we-learned">What We Learned</h1>
<p>Several things were learned in the course of migrating and figuring out what was going:</p>
<p>The SOA-EDIT interaction with PRESIGNED was not known and thus a corner case that was not foreseen in the migration plan.</p>
<p>Our DNSSEC trust-chain monitoring only found this issue because it queries for SOA. In the future we will write a checking script that will attempt to check all (or at least many) RRSIGs in the zone.</p>
<p>And most importantly: eating your own dogfood is an amazing way to find bugs before your users do!</p>
]]></content>
		</item>
		
		<item>
			<title>Setting up a WebRTC proxy for Mattermost</title>
			<link>https://blog.lieter.nl/posts/janus-for-mattermost/</link>
			<pubDate>Tue, 27 Jun 2017 10:41:30 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/janus-for-mattermost/</guid>
			<description>At PowerDNS, we&amp;rsquo;ve become quite fond of Mattermost, an MIT-licensed, self-hosted alternative for Slack, for our internal communication.
As a beta feature, Mattermost allows one-on-one video calls using WebRTC. A WebRTC proxy is needed to allow the calling parties to establish communication. Janus is the recommended proxy software. Unfortunately, the documentation is sparse on how to configure Janus to achieve this.
This guide assumes a Janus 0.2.3 installation on Debian or Ubuntu, but should be applicable to other versions and operating systems.</description>
			<content type="html"><![CDATA[<p>At <a href="https://powerdns.com">PowerDNS</a>, we&rsquo;ve become quite fond of <a href="https://mattermost.com">Mattermost</a>, an MIT-licensed, self-hosted alternative for <a href="https://slack.com">Slack</a>, for our internal communication.</p>
<p>As a <a href="https://docs.mattermost.com/deployment/webrtc.html">beta feature</a>, Mattermost allows one-on-one video calls using <a href="https://webrtc.org/">WebRTC</a>.
A WebRTC proxy is needed to allow the calling parties to establish communication. <a href="https://janus.conf.meetecho.com/">Janus</a> is the recommended proxy software.
Unfortunately, the <a href="https://docs.mattermost.com/administration/config-settings.html#webrtc-beta">documentation</a> is sparse on how to configure Janus to achieve this.</p>
<p>This guide assumes a Janus 0.2.3 installation on Debian or Ubuntu, but should be applicable to other versions and operating systems.
It only shows settings that should be modified, modify all others at you own discretion.
The configuration files for Janus have many comments for each setting.</p>
<h2 id="configuring-janus">Configuring Janus</h2>
<p>Janus support tons of transports and has many settings.
For Mattermost, the secure WebSockets transport and the HTTPS admin need to be configured.</p>
<p>After installing Janus, edit <code>/etc/janus/janus.cfg</code> to enable the admin:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[general]</span>
<span style="color:#a6e22e">token_auth</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">True</span>
<span style="color:#a6e22e">admin_secret</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">V3ryS3cr3t</span>
<span style="color:#a6e22e">server_name</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">webrtc-proxy</span>
</code></pre></div><p>And use valid certificates for DTLS (used to exchange key material inside the RTP session):</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[certificates]</span>
<span style="color:#a6e22e">cert_pem</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.pem</span>
<span style="color:#a6e22e">cert_key</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.key</span>
</code></pre></div><p>If you need IPv6 support, enable this as well:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[media]</span>
<span style="color:#a6e22e">ipv6</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">yes</span>
</code></pre></div><p>To create tokens, Mattermost needs to access the admin endpoint on the proxy.
This endpoint can be reached over HTTP and HTTPS (from Mattermost).
In this configuration the admin HTTP and the HTTP and HTTPS proxying are disabled.
The configuration file for the HTTP transport is <code>/etc/janus/janus.transport.http.cfg</code>.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[general]</span>
<span style="color:#a6e22e">http</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>
<span style="color:#a6e22e">https</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>

<span style="color:#66d9ef">[admin]</span>
<span style="color:#a6e22e">admin_http</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>
<span style="color:#a6e22e">admin_base_path</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/admin</span>
<span style="color:#a6e22e">admin_https</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">yes</span>
<span style="color:#a6e22e">admin_secure_port</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">7889</span>

<span style="color:#66d9ef">[certificates]</span>
<span style="color:#a6e22e">cert_pem</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.pem</span>
<span style="color:#a6e22e">cert_key</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.key</span>
</code></pre></div><p>The last bit of required configuration for Janus is the WebSockets transport.
As the admin over WebSockets is not used by Mattermost, this is disabled.
This configuration file is called <code>/etc/janus/janus.transport.websockets.cfg</code>.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[general]</span>
<span style="color:#a6e22e">ws</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>
<span style="color:#a6e22e">wss</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">yes</span>
<span style="color:#a6e22e">wss_port</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">8989</span>

<span style="color:#66d9ef">[admin]</span>
<span style="color:#a6e22e">admin_ws</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">False</span>
<span style="color:#a6e22e">admin_wss</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">False</span>

<span style="color:#66d9ef">[certificates]</span>
<span style="color:#a6e22e">cert_pem</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.pem</span>
<span style="color:#a6e22e">cert_key</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/etc/ssl/private/webrtc-proxy.example.com.key</span>
</code></pre></div><p>Now that Janus is configured, restart the service (<code>systemctl restart janus.service</code>) and move on to Mattermost.</p>
<h2 id="configuring-mattermost">Configuring Mattermost</h2>
<p>Mattermost can be configured via the web interface (System Console &gt; WebRTC (Beta)) or via the <code>config.json</code> file.
See the <a href="https://docs.mattermost.com/administration/config-settings.html#webrtc-beta">WebRTC (Beta)</a> documentation which settings in the web interface match the ones from the configuration file.</p>
<p>Some things to keep in mind:</p>
<ul>
<li>The <code>GatewayAdminUrl</code>&rsquo;s port must match the the <code>admin_secure_port</code> set in <code>/etc/janus/janus.transport.http.cfg</code></li>
<li>The <code>GatewayAdminUrl</code>&rsquo;s path must match the the <code>admin_base_path</code> set in <code>/etc/janus/janus.transport.http.cfg</code></li>
<li>The <code>GatewayWebsocketUrl</code> must use the <code>wss://</code> scheme for WebRTC to work</li>
<li>The <code>GatewayWebsocketUrl</code>&rsquo;s port must match the <code>wss_port</code> from <code>/etc/janus/janus.transport.websockets.cfg</code></li>
<li>The <code>GatewayAdminSecret</code> must match the <code>admin_secret</code> from <code>/etc/janus/janus.cfg</code></li>
</ul>
<p>The <code>WebrtcSettings</code> should look like this after configuring:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">  <span style="color:#e6db74">&#34;WebrtcSettings&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
    <span style="color:#f92672">&#34;Enable&#34;</span>: <span style="color:#66d9ef">true</span>,
    <span style="color:#f92672">&#34;GatewayAdminSecret&#34;</span>: <span style="color:#e6db74">&#34;V3ryS3cr3t&#34;</span>,
    <span style="color:#f92672">&#34;GatewayAdminUrl&#34;</span>: <span style="color:#e6db74">&#34;https://webrtc-proxy.example.com:7889/admin&#34;</span>,
    <span style="color:#f92672">&#34;GatewayWebsocketUrl&#34;</span>: <span style="color:#e6db74">&#34;wss://webrtc-proxy.example.com:8989&#34;</span>,
    <span style="color:#f92672">&#34;StunURI&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
    <span style="color:#f92672">&#34;TurnSharedKey&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
    <span style="color:#f92672">&#34;TurnURI&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
    <span style="color:#f92672">&#34;TurnUsername&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>
  }
</code></pre></div><p>Mattermost should pick up the configuration changes by itself.
If not, restart it.</p>
<p>Now the users can enable WebRTC for themselves (Account Settings &gt; Advanced &gt; Preview pre-release features &gt; Enable the ability to make and receive one-on-one WebRTC calls) and start calling each other.</p>
<h2 id="what-about-stun-and-turn">What about STUN and TURN?</h2>
<p>In the testing we have done with the video calls, no NAT-punching was needed.
Your mileage may vary, of course.</p>
<h2 id="bonus-installing-janus-on-debian-stretch">Bonus: Installing Janus on Debian Stretch</h2>
<p>There is no Janus package available on Debian Stretch.
As I like to run one distribution in my infrastructure, I had to backport Janus from Buster to Stretch.</p>
<p>To install Janus on Debian Stretch, add the following to <code>/etc/apt/sources.list.d/janus.list</code>:</p>
<pre><code>deb https://repo.plexis.eu/debian stretch-janus main
</code></pre><p>And add the public key to the keyring:</p>
<pre><code>curl -L https://repo.plexis.eu/6A8573EDDC4A8842.asc | sudo apt-key add -
</code></pre><p>Now install Janus:</p>
<pre><code>sudo apt-get update
sudo apt-get install janus janus-tools
</code></pre>]]></content>
		</item>
		
		<item>
			<title>Icinga/Nagios check for DNSSEC validation</title>
			<link>https://blog.lieter.nl/posts/icinga-nagios-check-dnssec-validation/</link>
			<pubDate>Mon, 19 Dec 2016 15:44:57 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/icinga-nagios-check-dnssec-validation/</guid>
			<description>When running a domain with DNSSEC enabled, it is important to monitor it. Apart from monitoring whether the nameserver is running, is responding to UDP and TCP queries, the domain is served by the nameserver and whether the RRSIGs are valid (you do all that already right?), knowing if the chain of trust to the domain is valid is just as important. Has someone removed the DS? Did someone do a KSK rollover without notifying the registrar?</description>
			<content type="html"><![CDATA[<p>When running a domain with DNSSEC enabled, it is important to monitor it.
Apart from monitoring whether the nameserver is running, is responding to UDP and TCP queries, the domain is served by the nameserver and whether the RRSIGs are valid (you do all that already right?),
knowing if the chain of trust to the domain is valid is just as important.
Has someone removed the DS? Did someone do a KSK rollover without notifying the registrar?
These are only some of the DNSSEC failures that can only be detected by validating the chain of trust.</p>
<p>For this purpose, I wrote a small <a href="https://www.nagios.org/">Nagios</a>/<a href="https://www.icinga.com/">Icinga</a> script a few years ago that does just this, but never released it publicly.
The code is up on <a href="https://github.com/pieterlexis/icinga-check-dnssec-validation">GitHub</a> for your downloading pleasure.</p>
<figure>
    <img src="/img/dnssec-status-icinga.png"/> <figcaption>
            <h4>Icinga 2 screenshot of check-dnssec-validation</h4>
        </figcaption>
</figure>

]]></content>
		</item>
		
		<item>
			<title>Berlin Adminstammtisch Presentation on DNSdist</title>
			<link>https://blog.lieter.nl/posts/adminstammtisch-dnsdist-nov-3rd-2016/</link>
			<pubDate>Fri, 04 Nov 2016 09:47:56 +0100</pubDate>
			
			<guid>https://blog.lieter.nl/posts/adminstammtisch-dnsdist-nov-3rd-2016/</guid>
			<description>On November 3rd I did an introductionary presentation on dnsdist during the monthly Adminstammtisch in Berlin. The presentation was entitled &amp;ldquo;dnsdist: the high-performant, DoS and abuse-aware DNS loadbalancer&amp;rdquo;. Here are the slides.
For those who don&amp;rsquo;t know, dnsdist is a DNS firewall/loadbalancer that is fully configured in Lua and provides (among many things) live DNS traffic inspection without slowing down the server. It also has a minimal memory-footprint.
In true daredevil fashion, there was a live-demo involved.</description>
			<content type="html"><![CDATA[<p>On November 3rd I did an introductionary presentation on <a href="http://www.dnsdist.org">dnsdist</a> during the monthly <a href="https://www.flarp.de/">Adminstammtisch</a> in Berlin. The presentation was entitled &ldquo;dnsdist: the high-performant, DoS and abuse-aware DNS loadbalancer&rdquo;. Here are the <a href="/presentations/PLexis-dnsdist-20161103.pdf">slides</a>.</p>
<p>For those who don&rsquo;t know, dnsdist is a DNS firewall/loadbalancer that is fully configured in <a href="http://lua.org">Lua</a> and provides (among many things) live DNS traffic inspection without slowing down the server. It also has a minimal memory-footprint.</p>
<p>In true daredevil fashion, there was a live-demo involved. Below I&rsquo;ve reproduced the dnsdist config:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-lua" data-lang="lua">controlSocket(<span style="color:#e6db74">&#39;0.0.0.0&#39;</span>) <span style="color:#75715e">-- for the console</span>
setKey(<span style="color:#e6db74">&#39;MXNeLFWHUe4363BBKrY06cAsH8NWNb+Se2eXU5+Bb74=&#39;</span>) <span style="color:#75715e">-- for crypto</span>
webserver(<span style="color:#e6db74">&#34;127.0.0.1:8080&#34;</span>, <span style="color:#e6db74">&#34;geheim2&#34;</span>) <span style="color:#75715e">-- Enable the webserver for live-graphs and overview</span>
carbonServer(<span style="color:#e6db74">&#39;37.252.122.50&#39;</span>, <span style="color:#e6db74">&#39;lieter-demo&#39;</span>, <span style="color:#ae81ff">10</span>) <span style="color:#75715e">-- Send stats to the public PowerDNS metronome (https://metrics.powerdns.com)</span>

<span style="color:#75715e">-- Add 11 listen sockets so we can have multiple clients connecting</span>
addLocal(<span style="color:#e6db74">&#39;127.0.0.1:5300&#39;</span>)
<span style="color:#66d9ef">for</span> x <span style="color:#66d9ef">in</span> pairs({<span style="color:#ae81ff">1</span>,<span style="color:#ae81ff">2</span>,<span style="color:#ae81ff">3</span>,<span style="color:#ae81ff">4</span>,<span style="color:#ae81ff">5</span>,<span style="color:#ae81ff">6</span>,<span style="color:#ae81ff">7</span>,<span style="color:#ae81ff">8</span>,<span style="color:#ae81ff">9</span>,<span style="color:#ae81ff">10</span>}) <span style="color:#66d9ef">do</span>
  addLocal(<span style="color:#e6db74">&#39;10.0.0.&#39;</span> <span style="color:#f92672">..</span> x <span style="color:#f92672">..</span> <span style="color:#e6db74">&#39;:5300&#39;</span>)
<span style="color:#66d9ef">end</span>

<span style="color:#75715e">-- Default pool with 3 recursors</span>
newServer({address<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;127.0.0.1:5301&#39;</span>, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;backend1&#39;</span>})
newServer({address<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;127.0.0.1:5302&#39;</span>, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;backend2&#39;</span>})
newServer({address<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;127.0.0.1:5303&#39;</span>, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;backend3&#39;</span>})

<span style="color:#75715e">-- One abuse recursor</span>
newServer({address<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;127.0.0.1:5304&#39;</span>, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;backend4&#39;</span>, pool<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;abuse&#39;</span>})

<span style="color:#66d9ef">function</span> <span style="color:#a6e22e">maintenance</span>()
  addresses<span style="color:#f92672">=</span>exceedQTypeRate(dnsdist.TLSA, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">10</span>) <span style="color:#75715e">-- Get all addresses that asked for more than 2TLSA records in the last 10 seconds</span>
  addDynBlocks(addresses,<span style="color:#e6db74">&#34;Exceeded TLSA&#34;</span>,<span style="color:#ae81ff">60</span>) <span style="color:#75715e">-- Block them for 60 seconds</span>
<span style="color:#66d9ef">end</span></code></pre></div>
<p>And here are some of the commands entered into the dnsdist console:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-lua" data-lang="lua"><span style="color:#75715e">-- Block all .ru domains</span>
addDomainBlock(<span style="color:#e6db74">&#34;ru.&#34;</span>)

<span style="color:#75715e">-- Create a netmaskgroup and add a single address to it</span>
nmg <span style="color:#f92672">=</span> newNMG()
nmg:addMask(<span style="color:#e6db74">&#39;127.0.0.1/32&#39;</span>)

<span style="color:#75715e">-- Match on the netmaskgroup and QTYPE AAAA</span>
selector <span style="color:#f92672">=</span> AndRule({NetmaskGroupRule(nmg), QTypeRule(dnsdist.AAAA)})

<span style="color:#75715e">-- Delay these queries by 1500 millisecond</span>
addAction(selector, DelayAction(<span style="color:#ae81ff">1500</span>))

<span style="color:#75715e">-- Allow a maximum of 3 queries per second to powerdns.com</span>
addQPSLimit(<span style="color:#e6db74">&#39;powerdns.com&#39;</span>, <span style="color:#ae81ff">3</span>)

<span style="color:#75715e">-- Sens add traffic for google.com (and everything below it) to the abue pool</span>
addPoolRule({<span style="color:#e6db74">&#39;google.com&#39;</span>}, <span style="color:#e6db74">&#39;abuse&#39;</span>)

<span style="color:#75715e">-- Add a packet cache of 10000 entries to the default pool</span>
pc <span style="color:#f92672">=</span> newPacketCache(<span style="color:#ae81ff">10000</span>, <span style="color:#ae81ff">86400</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">60</span>, <span style="color:#ae81ff">60</span>)
getPool(<span style="color:#e6db74">&#34;&#34;</span>):setCache(pc)</code></pre></div>
<p>What struck me as really awesome, was that during the demo I was processing around 10000 queries per second through dnsdist. While this was going on, memory-usage did not exceed 30 Megabytes and (even with the recursors doing all the heavy lifting) my CPU was only used for 20% tops.</p>
]]></content>
		</item>
		
		<item>
			<title>Sub-Minute Job Execution with Systemd Timers</title>
			<link>https://blog.lieter.nl/posts/sub-minute-job-execution-systemd-timers/</link>
			<pubDate>Wed, 15 Jun 2016 14:14:12 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/sub-minute-job-execution-systemd-timers/</guid>
			<description>Sometimes there&amp;rsquo;s a small job that needs to be run every X seconds. But having a daemon running calls sleep(3) is either overkill or you&amp;rsquo;re too lazy to implement starting/stopping/signal handling.
Cron only has a one-minute accuracy, so that won&amp;rsquo;t help in this case. Fortunately, systemd has the possiblity to run services at configured times or with a monotonic interval using timer units.
[Unit] Description=A job that stores the time [Service] Type=oneshot ExecStart=/usr/local/bin/foo.</description>
			<content type="html"><![CDATA[<p>Sometimes there&rsquo;s a small job that needs to be run every X seconds. But having
a daemon running calls <a href="http://linux.die.net/man/3/sleep"><code>sleep(3)</code></a> is either
overkill or you&rsquo;re too lazy to implement starting/stopping/signal handling.</p>
<p>Cron only has a one-minute accuracy, so that won&rsquo;t help in this case. Fortunately,
<a href="https://freedesktop.org/wiki/Software/systemd/">systemd</a> has the possiblity to
run services at configured times or with a monotonic interval using <a href="https://www.freedesktop.org/software/systemd/man/systemd.timer.html">timer units</a>.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[Unit]</span>
<span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">A job that stores the time</span>

<span style="color:#66d9ef">[Service]</span>
<span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">oneshot</span>
<span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/local/bin/foo.sh</span>
</code></pre></div><p><code>/etc/systemd/system/foo.service</code></p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[Unit]</span>
<span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Runs every 15 secs</span>

<span style="color:#66d9ef">[Timer]</span>
<span style="color:#a6e22e">OnActiveSec</span><span style="color:#f92672">=</span><span style="color:#e6db74">0 # Run when activated</span>
<span style="color:#a6e22e">OnUnitActiveSec</span><span style="color:#f92672">=</span><span style="color:#e6db74">15 # Run every 15 seconds</span>
<span style="color:#a6e22e">AccuracySec</span><span style="color:#f92672">=</span><span style="color:#e6db74">500msec # Don&#39;t delay too much</span>

<span style="color:#66d9ef">[Install]</span>
<span style="color:#a6e22e">WantedBy</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">multiuser.target</span>
</code></pre></div><p><code>/etc/systemd/system/foo.timer</code></p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh"><span style="color:#75715e">#!/bin/sh
</span><span style="color:#75715e"></span>date &gt;&gt; /tmp/timestamp
</code></pre></div><p><code>/usr/local/bin/foo.sh</code></p>
<p>Enable and start the timer: <code>systemctl enable --now foo.timer</code> and lo and behold:</p>
<pre><code>Wed Jun 15 16:26:44 CEST 2016
Wed Jun 15 16:27:00 CEST 2016
Wed Jun 15 16:27:15 CEST 2016
Wed Jun 15 16:27:31 CEST 2016
Wed Jun 15 16:27:46 CEST 2016
Wed Jun 15 16:28:02 CEST 2016
</code></pre><p><code>/tmp/timestamp</code></p>
<p>Note that the timestamps are not exactly 15 seconds apart. This is because <code>AccuracySec</code>
is set to half a second. Systemd attempts to limit the number of wake ups and to
schedule timers simultaniously. For a more precise interval, <code>AccuracySec</code> can be
set to <code>1us</code>.</p>
<p>An advantage of this approach (compared to to cron) is the fact that the
stdout/stderr output of <code>foo.service</code> goes directly to the journal. This allows
<code>foo.sh</code> (or whatever script used) to be extremely simple.</p>
<p>The Arch Linux wiki on systemd timers <a href="https://wiki.archlinux.org/index.php/Systemd/Timers#As_a_cron_replacement">has a good section</a>
on using systemd timers as a replacement for cron, including sending emails on
job failure.</p>
]]></content>
		</item>
		
		<item>
			<title>Generating DNS-zones from the Digital Ocean API</title>
			<link>https://blog.lieter.nl/posts/generating-dns-zones-from-do-api/</link>
			<pubDate>Wed, 01 Jun 2016 13:58:22 +0200</pubDate>
			
			<guid>https://blog.lieter.nl/posts/generating-dns-zones-from-do-api/</guid>
			<description>When you&amp;rsquo;re managing an ever-changing machine infrastructure in a public cloud, it is easy to forget to add new and remove old machines from your DNS.
At PowerDNS, we had a demonstration setup that was rebuilt several times in the span of two weeks, and I got tired of editing the zonefile* by hand, so let&amp;rsquo;s automate the hell out of it.
Fortunately, Digital Ocean has an API that allows you to retrieve Droplet (VPS) information.</description>
			<content type="html"><![CDATA[<p>When you&rsquo;re managing an ever-changing machine infrastructure in a public cloud,
it is easy to forget to add new and remove old machines from your DNS.</p>
<p>At <a href="https://powerdns.com">PowerDNS</a>, we had a demonstration setup that was rebuilt
several times in the span of two weeks, and I got tired of editing the zonefile* by
hand, so let&rsquo;s automate the hell out of it.</p>
<p>Fortunately, <a href="https://digitalocean.com">Digital Ocean</a> has an <a href="https://developers.digitalocean.com/">API</a>
that allows you to retrieve Droplet (VPS) information. This API has bindings in
several languages, so it&rsquo;s easy to generate these DNS-entries.</p>
<p>Here&rsquo;s the quick &lsquo;n dirty python script I whipped up:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e">#!/usr/bin/env python2</span>
<span style="color:#f92672">import</span> sys
<span style="color:#f92672">import</span> time
<span style="color:#f92672">import</span> os

<span style="color:#66d9ef">try</span>:
    <span style="color:#f92672">import</span> json
<span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ImportError</span>:
    <span style="color:#f92672">import</span> simplejson <span style="color:#f92672">as</span> json

<span style="color:#66d9ef">try</span>:
    <span style="color:#f92672">from</span> dopy.manager <span style="color:#f92672">import</span> DoError, DoManager
<span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ImportError</span> <span style="color:#66d9ef">as</span> e:
    <span style="color:#66d9ef">print</span>(<span style="color:#e6db74">&#34;dopy library required for this script&#34;</span>)
    sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)

conf <span style="color:#f92672">=</span> {}
<span style="color:#75715e">################################################################################</span>
<span style="color:#75715e"># Change these settings</span>
<span style="color:#75715e">################################################################################</span>
conf[<span style="color:#e6db74">&#39;APIkey&#39;</span>] <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;API_KEY&#39;</span>, <span style="color:#e6db74">&#39;changeme&#39;</span>)
conf[<span style="color:#e6db74">&#39;zone&#39;</span>] <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;ZONE&#39;</span>, <span style="color:#e6db74">&#39;thisiswrong.example&#39;</span>)
conf[<span style="color:#e6db74">&#39;ns&#39;</span>] <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;ns1.</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;zone&#39;</span>), <span style="color:#e6db74">&#39;ns2.</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;zone&#39;</span>)]
conf[<span style="color:#e6db74">&#39;SOA&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">. hostmaster.</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">. </span><span style="color:#e6db74">%s</span><span style="color:#e6db74"> 360 60 8640 360&#34;</span> <span style="color:#f92672">%</span> (conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;ns&#39;</span>)[<span style="color:#ae81ff">0</span>], conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;zone&#39;</span>), int(time<span style="color:#f92672">.</span>time()))

<span style="color:#75715e">################################################################################</span>
<span style="color:#75715e"># Begin script</span>
<span style="color:#75715e">################################################################################</span>
manager <span style="color:#f92672">=</span> DoManager(None, conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;APIkey&#39;</span>), api_version<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span>)
droplets <span style="color:#f92672">=</span> manager<span style="color:#f92672">.</span>all_active_droplets()

sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;$ORIGIN </span><span style="color:#e6db74">%s</span><span style="color:#e6db74">.</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">%</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;zone&#39;</span>))
sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;$TTL 120</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;@</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">IN</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">SOA</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">%s</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">%</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;SOA&#39;</span>))
<span style="color:#66d9ef">for</span> ns <span style="color:#f92672">in</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;ns&#39;</span>):
    sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;@</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">IN</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">NS</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">.</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">%</span> ns)
sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)

<span style="color:#66d9ef">for</span> droplet <span style="color:#f92672">in</span> droplets:
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> droplet[<span style="color:#e6db74">&#39;name&#39;</span>]<span style="color:#f92672">.</span>endswith(<span style="color:#e6db74">&#39;.</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> conf<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;zone&#39;</span>)):
        <span style="color:#66d9ef">continue</span>

    name <span style="color:#f92672">=</span> droplet[<span style="color:#e6db74">&#39;name&#39;</span>]<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;.&#39;</span>)[<span style="color:#ae81ff">0</span>]

    <span style="color:#66d9ef">for</span> net <span style="color:#f92672">in</span> droplet[<span style="color:#e6db74">&#39;networks&#39;</span>][<span style="color:#e6db74">&#39;v4&#39;</span>]:
        <span style="color:#66d9ef">if</span> net[<span style="color:#e6db74">&#39;type&#39;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;public&#39;</span>:
            sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">%s</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">IN</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">A</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">%s</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">%</span> (name, net[<span style="color:#e6db74">&#39;ip_address&#39;</span>]))

    <span style="color:#66d9ef">for</span> net <span style="color:#f92672">in</span> droplet[<span style="color:#e6db74">&#39;networks&#39;</span>][<span style="color:#e6db74">&#39;v6&#39;</span>]:
        <span style="color:#66d9ef">if</span> net[<span style="color:#e6db74">&#39;type&#39;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;public&#39;</span>:
            sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">%s</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">IN</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">AAAA</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">%s</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">%</span> (name, net[<span style="color:#e6db74">&#39;ip_address&#39;</span>]))
</code></pre></div><p>You&rsquo;ll need the <a href="https://pypi.python.org/pypi/dopy/">dopy</a> and
<a href="https://pypi.python.org/pypi/six/">six</a>(required by dopy) python modules
installed.</p>
<p>The easiest way to generate the zone is through cron:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json"><span style="color:#960050;background-color:#1e0010">*/</span><span style="color:#ae81ff">5</span> <span style="color:#960050;background-color:#1e0010">*</span> <span style="color:#960050;background-color:#1e0010">*</span> <span style="color:#960050;background-color:#1e0010">*</span> <span style="color:#960050;background-color:#1e0010">*</span> <span style="color:#960050;background-color:#1e0010">ZONE=myzone.example;</span> <span style="color:#960050;background-color:#1e0010">API_KEY=s</span><span style="color:#ae81ff">3</span><span style="color:#960050;background-color:#1e0010">cr</span><span style="color:#ae81ff">1</span><span style="color:#960050;background-color:#1e0010">t;</span> <span style="color:#960050;background-color:#1e0010">/usr/local/bin/do-zone-generator.py</span> <span style="color:#960050;background-color:#1e0010">&gt;</span> <span style="color:#960050;background-color:#1e0010">/tmp/$ZONE.zone</span> <span style="color:#960050;background-color:#1e0010">&amp;&amp;</span> <span style="color:#960050;background-color:#1e0010">cp</span> <span style="color:#960050;background-color:#1e0010">/tmp/$ZONE.zone</span> <span style="color:#960050;background-color:#1e0010">/var/lib/powerdns/zones</span> <span style="color:#960050;background-color:#1e0010">&amp;&amp;</span> <span style="color:#960050;background-color:#1e0010">pdns_control</span> <span style="color:#960050;background-color:#1e0010">bind-reload-now</span> <span style="color:#960050;background-color:#1e0010">$ZONE</span> <span style="color:#960050;background-color:#1e0010">&gt;/dev/</span><span style="color:#66d9ef">null</span>
</code></pre></div><p>This will generate the zone, copy it to the right place and tell PowerDNS to reload
the zone (sending out NOTIFYs).</p>
<p>Of course, the script could be extended to insert the data into a database-backed
PowerDNS using the <a href="https://doc.powerdns.com/md/httpapi/api_spec/#url-apiv1serversserver95idzoneszone95id">PowerDNS API</a>.
But this is left as an exercise to the reader.</p>
<p>*: As a sidenote, even though the PowerDNS Authoritative Server is deployed most
of the time because of its excellent <a href="https://doc.powerdns.com/md/authoritative/backend-generic-sql/">database backends</a>,
for my own small-scale deployments, I prefer zone-files over a database. But this
might change, as <a href="https://doc.powerdns.com/md/manpages/pdnsutil.1/"><code>pdnsutil</code></a>
supports an <code>edit-zone</code> command that will create a faux zonefile from the zone
in the database, open it in <code>$EDITOR</code> and will insert it into the database after
editing. See <a href="https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains/">Bert Hubert&rsquo;s blogpost</a> on this.</p>
]]></content>
		</item>
		
	</channel>
</rss>
