<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Philipp C. Heckel</title><link>https://heckel.io/blog/</link><description>Tech blog of Philipp C. Heckel</description><language>en-us</language><lastBuildDate>Tue, 19 Oct 2021 20:58:05 -0400</lastBuildDate><atom:link href="https://heckel.io/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Lossless MySQL semi-sync replication and automated failover</title><link>https://heckel.io/blog/lossless-mysql-semi-sync-replication-and-automated-failover/</link><pubDate>Tue, 19 Oct 2021 20:58:05 -0400</pubDate><guid>https://heckel.io/blog/lossless-mysql-semi-sync-replication-and-automated-failover/</guid><description>MySQL is a really mature technology. It’s been around for a quarter of a century and it’s one of the most popular DBMS in the world. As such, as an engineer, one expects basic features such as replication and failover to be fleshed out, stable and ideally even easy to set up.
And while MySQL comes …</description><content:encoded><![CDATA[<p><a href="https://www.mysql.com/">MySQL</a> is a really mature technology. It’s been around for a quarter of a century and it’s one of the most popular <a href="https://en.wikipedia.org/wiki/Database">DBMS</a> in the world. As such, as an engineer, one expects basic features such as replication and failover to be fleshed out, stable and ideally even easy to set up.</p>
<p>And while MySQL comes with replication functionality out of the box, <a href="https://severalnines.com/database-blog/introduction-failover-mysql-replication-101-blog">automated failover and topology management</a> is not part of its feature set. On top of that, it turns out that it is rather difficult to not shoot yourself in the foot when configuring replication.</p>
<p>In fact, without careful configuration and the right tools, a failover from a source to a replica server <strong>will almost certainly lose transactions</strong> that have been acknowledged as committed to the application.</p>
<p>This is a blog post about setting up <strong>lossless MySQL replication with automated failover</strong>, i.e. ensuring that <strong>not a single transaction is lost during a failover</strong>, and that failovers happen entirely without human intervention.</p>
<h2 id="terminology--disclaimer">Terminology &amp; Disclaimer</h2>
<p>This blog post uses MySQL’s new and less offensive terminology <a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-26.html">as it was introduced in version 8.0.26</a>. We refer to primary hosts as “source” and to their replication hosts as “replicas”. Configuration parameters may still use offensive terms though.</p>
<p>I am a software engineer and not a DBA. Please excuse errors and let me know. I’m happy to correct any mistakes.</p>
<h2 id="asynchronous-replication-is-best-effort-and-will-lose-transactions-during-a-failover">Asynchronous replication is best effort and will lose transactions during a failover</h2>
<p>MySQL’s default replication mechanism is <a href="https://dev.mysql.com/doc/refman/8.0/en/replication.html">asynchronous replication</a>, meaning that a transaction committed by the application is replicated by a background thread to all connected replica hosts. As long as the source host does not crash, this is perfectly fine. If it does, however, there are no guarantees that all transactions already made it to the replica host(s) before the crash.</p>
<p>Consider a topology of three hosts, with A being the source and B and C being replicas of A:</p>
<p><img src="/uploads/2022/07/0_0.png" alt=""></p>
<p>Assuming <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_sync_binlog">sync_binlog=1</a> on the source host (A), the way asynchronous replication works is like this:</p>
<ol>
<li>The application commits transaction to source (A)</li>
<li>A writes the transaction to its <a href="https://dev.mysql.com/doc/refman/8.0/en/binary-log.html">binary log</a></li>
<li>A executes the transaction in the <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html">storage engine (InnoDB)</a></li>
<li>A acknowledges transaction to the application <strong>(What if we crash here?)</strong></li>
<li>B and C retrieve the transaction from A’s binary log, write it to their relay log via the IO thread and then apply the transaction via the SQL thread</li>
</ol>
<p><img src="/uploads/2022/07/0_0-1.png" alt=""></p>
<p>This is obviously dangerous because the application gets an acknowledgement of a transaction before it has been replicated. If the source (A) crashes after that, the replication may not have been replicated to B and/or C <strong>and you lose transactions</strong>.</p>
<h2 id="semi-sync-replication-to-the-rescue-sort-of">Semi-sync replication to the rescue, sort of</h2>
<p>Clearly, asynchronous replication is not the answer if losing transactions is not acceptable for your application. <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html">Semi-sync replication</a> solves this problem, at least partially: Unlike with asynchronous replication, the source does not acknowledge the transaction to the application until the replica acknowledges the receipt (but not the execution!) of the transaction.</p>
<p>Here’s how the order of operations changes with semi-sync enabled on the source and the replicas, i.e. with <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_enabled">rpl_semi_sync_master_enabled=1</a> on A, and <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-replica.html#sysvar_rpl_semi_sync_slave_enabled">rpl_semi_sync_slave_enabled=1</a> on B and C:</p>
<ol>
<li>The application commits transaction to source (A)</li>
<li>A writes the transaction to its binary log &ndash; <strong>Crash point 1: what if A crashes here?</strong></li>
<li>B and C both retrieve the transaction and write it to their relay log</li>
<li>B and C acknowledge the receipt of the transaction &ndash; <strong>Crash point 2: what if A crashes here?</strong></li>
<li>A acknowledges transaction to the application</li>
<li>A executes transaction in the storage engine (InnoDB)</li>
<li>B and C apply the transaction in the storage engine (InnoDB)</li>
</ol>
<p>(This example flow assumes that <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_source_wait_point">rpl_semi_sync_source_wait_point=AFTER_SYNC</a> and ignores <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_timeout">rpl_semi_sync_master_timeout</a> and <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_wait_for_slave_count">rpl_semi_sync_master_wait_for_slave_count</a>. We’ll get to that later.)</p>
<p>This looks much better:</p>
<p>If the source crashes before the transaction has been shipped to B and C (<strong>crash point 1</strong>), our application is in a consistent state: A has not acknowledged the transaction to the application before it crashed, so it’s as if it had never been committed. As long as we fail over to B or C and don’t ever restart the source (more about that later!), we’re fine.</p>
<p>The other crash point (<strong>crash point 2</strong>) is more interesting: If A crashes after the transaction has been transferred to the relay log on B and C, the application will lose its database connection to A and think that the transaction failed. If we were to restart A (which we shouldn’t), the transaction would still exist in A’s binary log and it’d be rolled forward in during crash recovery. In both cases the transaction won’t be lost.</p>
<p>Assuming we fail over to B or C, <strong>the transaction will however still be executed there, despite the connection error</strong>. Lossless replication means we don&rsquo;t lose anything, so this is actually an acceptable scenario, though <strong>incredibly unexpected from a developer perspective</strong>. We have seen these throws-error-but-still-executed cases many times, so you have to be able to deal with them.</p>
<p>There are obviously more than two crash points, but I think you get the idea: semi-sync replication ensures that each transaction has been written to at least one other replica before sending an acknowledgement to the application.</p>
<h2 id="dangerous-semi-sync-pitfalls">Dangerous semi-sync pitfalls</h2>
<p>Unfortunately, setting up semi-sync is not as dead-simple as one might hope. There are a number of inconspicuous, yet quite consequential config parameters that you have to get just right.</p>
<h3 id="accidental-fallback-to-asynchronous-replication">Accidental fallback to asynchronous replication</h3>
<p>The <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_timeout">rpl_semi_sync_master_timeout</a> parameter controls how long a source will wait on the replica(s) to respond and acknowledge the receipt of a transaction <strong>before giving up and falling back to asynchronous replication</strong>. The default value is 10,000 ms (10 seconds), meaning that in its default configuration, semi-sync replication will fall back to the best effort async replication behavior if the replica(s) don’t respond in that time.</p>
<p>While 10 seconds may seem like a long time for a single transaction, we all know that there are dozens of things that can cause delays like that even on modern hardware: (temporary) network failures, bad disks, or heavy load &ndash; all are reasons for why replicas could (at least temporarily) not respond within 10 seconds.</p>
<p>Here’s an illustration of the example above, but this time we time out (3 &amp; 4) when trying to deliver/write to the replica. Note that the transaction is still acknowledged to the application (5), despite the downstream timeout, so if a failover were to occur now, we’d lose this transaction:</p>
<p><img src="/uploads/2022/07/0_0-2.png" alt=""></p>
<p>We have actually been bitten by this quite severely in one of our products: A temporary network blip caused a fallback to async replication, and a subsequent failover to a replica (also due to a temporary network failure) <strong>led to the loss of 4 seconds of transactions</strong>. That may not seem like much, but it took us multiple days to repair the affected tables.</p>
<p>If you want to make sure that you never fall back to asynchronous replication, <strong>you must set<a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_timeout">rpl_semi_sync_master_timeout</a> to something outrageously high</strong>, e.g., one hour or even 24 hours.</p>
<p>While this will guarantee that transactions won’t get written to the source without being at least received by one replica, the implication is that if your replica(s) are down (even just for maintenance), <strong>your application will block “forever”</strong>. This is a desired state if your most important requirement is not losing a single transaction, but still something that may not be entirely clear to administrators or developers when setting up semi-sync replication.</p>
<p>(Note that there is a way to avoid the forever-blocking nature of semi-sync using two replicas, and dynamic semi-sync flag management using <a href="https://github.com/openark/orchestrator">orchestrator</a>. I’ll talk about this further down.)</p>
<h3 id="promotion-confusion-due-to-incorrect-replica-count">Promotion confusion due to incorrect replica count</h3>
<p>The <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_wait_for_slave_count">rpl_semi_sync_master_wait_for_slave_count</a> config option controls how many replicas MySQL will wait for before acknowledging the transaction to the application. The default value is 1, meaning that even if you have two configured semi-sync replicas, MySQL will only wait for one of them to respond before assuming that things are fine.</p>
<p>Assuming our three host example above has <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_wait_for_slave_count">rpl_semi_sync_master_wait_for_slave_count=1</a> set, we won’t know if B or C acknowledged the transaction. As long as the source (A) doesn’t crash this is not a problem, of course. If A crashes, however, our failover script (or our topology manager) has to decide which replica to fail over to and promote to be the new source: B or C.</p>
<p>Depending on the sophistication of the failover script or the topology manager, it is of course possible to figure out which replica has received the latest transactions (using GTIDs and/or the binary log position), but it makes the whole scenario much more complex.</p>
<p>For our setup, we have chosen to enable semi-sync on only one of the replicas and never promote the second asynchronous replica. This can be achieved with <a href="https://github.com/openark/orchestrator">orchestrator’s</a> brand new <a href="https://github.com/openark/orchestrator/blob/master/docs/configuration-discovery-classifying.md#semi-sync-topology">EnforceExactSemiSyncReplicas</a> option (see below).</p>
<h3 id="re-using-a-failed-source">Re-using a failed source</h3>
<p>Even though the semi-sync documentation clearly states that you should <strong>never re-use a failed source</strong>, I feel obligated to repeat it here, because the implications of re-using a failed source are quite significant.</p>
<p>Here’s <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html">what the docs say</a>:</p>
<p><em>“With semisynchronous replication, if the source crashes and a failover to a replica is carried out, the failed source should not be reused as the replication source, and<strong>should be discarded</strong>. It could have transactions that were not acknowledged by any replica, which were therefore not committed before the failover.“</em></p>
<p>This paragraph talks about the crash point 1 scenario from above. When a transaction is written to the binary log on the source (A), but it didn’t make it to B or C, this transaction officially never happened from the application perspective and from the perspective of B and C.</p>
<p>If you now re-use A (even as a replica of B or C), there will be a renegade transaction in A. In the best case, A won’t start up properly. In the worst case, you won’t notice for a while, but the state of source and replicas won’t be identical.</p>
<p>Bottom line: <strong>Don’t ever re-use a failed source</strong>. Rebuild it using xtrabackup from the promoted new source.</p>
<h2 id="automated-failover-is-hard">Automated failover is hard</h2>
<p>While replication is part of MySQL’s core feature set, <strong>topology management and automated failover is not</strong>. What that means is that in the case a source host goes down, or has to be taken down for maintenance, MySQL won’t decide on a new source, change replication targets and it certainly won’t tell your application which of the replicas the new source host is.</p>
<p>To achieve an automated failover from a failed source to a replica, you have to employ the services of other tools. Unfortunately, the tool landscape for MySQL failover has changed over the years and it’s still a little bit of a wild wild west out there.</p>
<h3 id="keepalived-vips-and-roll-your-own-scripts-not-for-lossless-failover">keepalived, VIPs and roll-your-own scripts: not for lossless failover!</h3>
<p>For a very long time, we were using a failover mechanism that was based on <a href="https://github.com/acassen/keepalived">keepalived</a>, a floating <a href="https://en.wikipedia.org/wiki/Virtual_IP_address">virtual IP address (VIP)</a> and some custom monitoring and failover scripts.</p>
<p><img src="/uploads/2021/10/0_0.png" alt=""></p>
<p>In this setup, the application talks to the MySQL source host via a floating IP address, which is controlled and managed by keepalived. Each MySQL host runs keepalived, which regularly runs a monitoring script that performs basic checks (1). If the health check of the MySQL source fails, it moves the VIP to the replica host that will become the new source (2) and then (!) triggers a notify script which will flip the host to read/write to become the new source (3).</p>
<p>This setup actually works quite well, requiring few moving pieces and no extra hosts. However, in our experience it is much less robust than a proxy based setup and not suitable if you require lossless failover with semi-sync. Here’s a (non-exhaustive) list of issues we ran into:</p>
<p><strong>keepalived doesn’t know anything about MySQL</strong>. It just manages a VIP and isn’t aware of the MySQL topology. It doesn’t know who the source is, what the <a href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_only">read_only</a> flag is set to or if semi-sync is enabled. <strong>All of that has to be done by you in your own scripts</strong>. Not only is that a lot of work, it is also prone to error. You have to manually implement a mutex lock to make sure only one script runs at a time, you have to implement waiting for the relay logs to be processed, fencing, and you have to manually flip the read only and semi-sync state. Lots to do, lots to go wrong.</p>
<p><strong>keepalived is decentralized</strong>. It works by all participating nodes communicating with one another. What that means is that if the communication between the nodes is (even momentarily) interrupted, there is a <strong>significant risk of<a href="https://en.wikipedia.org/wiki/Split-brain">split-brain</a></strong> and with that, the risk that two nodes will try to grab the VIP and declare themselves the source with <a href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_only">read_only=0</a>. While the network layer will obviously ensure that only one host has the VIP, it could still flap back and forth between the hosts. The consequences of that are quite catastrophic, as it can lead to two MySQL hosts applying transactions. The perfect storm.</p>
<p>I could go on, but I’ll leave it at that. We had lots of trouble with this solution when we really started playing <a href="https://en.wikipedia.org/wiki/Chaos_engineering">Chaos Monkey</a> so I advise against this if you like your data.</p>
<h3 id="haproxyproxysql-orchestrator-consul-and-consul-template">HAproxy/ProxySQL, orchestrator, Consul and Consul Template</h3>
<p>Luckily, there are a few popular setups out there that work quite nicely and don’t experience the above mentioned problems. Pretty much all of them revolve around using a combination of <a href="https://proxysql.com/">ProxySQL</a> or <a href="http://www.haproxy.org/">HAproxy</a>, <a href="https://github.com/openark/orchestrator">orchestrator</a>, <a href="https://www.consul.io/">Consul</a>, and <a href="https://github.com/hashicorp/consul-template">Consul Template</a>. There are a <a href="https://blog.pythian.com/mysql-high-availability-with-proxysql-consul-and-orchestrator/">number</a> <a href="https://github.blog/2018-06-20-mysql-high-availability-at-github/">of</a> <a href="https://www.youtube.com/watch?v=dYlv-YGtkEE">great</a> <a href="https://blog.pythian.com/state-mysql-high-availability-going-2018/">resources</a> (4 links!) out there describing them, so I’ll be brief.</p>
<p><img src="/uploads/2021/10/0_0-1.png" alt=""></p>
<p><em>(Note that in this illustration, “proxy” can be HAproxy or ProxySQL. See below for details.)</em></p>
<p>All of the linked setups rely on monitoring the MySQL hosts for their health (responsiveness, replication, lag, …) using the topology manager <strong>orchestrator</strong>, and proxying SQL traffic through either <strong>HAproxy</strong> (TCP-level proxy) or <strong>ProxySQL</strong> (application-level proxy) to the current MySQL source host.</p>
<p>When orchestrator detects a failure on the source host (A), it first determines which of the replicas will become the new source, either B or C. Once it has figured that out, it repoints MySQL replication (<code>stop slave; change master to …; start slave</code>) of the remaining hosts and isolates the failed source (A). After that, it tells the proxy (HAproxy or ProxySQL) to repoint to a different host. In the most popular setup, this happens by dynamically updating the configuration through Consul and Consul Template.</p>
<h2 id="dangerous-failover-pitfalls">Dangerous failover pitfalls</h2>
<p>Even more so than setting up MySQL semi-sync replication, setting up failover as described above is quite tricky. There are lots of tools involved, and naturally each of them have tons of configuration options. Many things to do wrong.</p>
<h3 id="always-wait-for-the-relay-log-before-accepting-write-traffic">Always wait for the relay log before accepting write traffic</h3>
<p>Semi-sync replication <strong>guarantees the delivery of a transaction to the replica(s), but not the execution</strong>. It makes sure that the relay log on the replica(s) has a copy of every transaction, but still handles the execution asynchronously.</p>
<p>That means that it is very easy for MySQL replicas to fall behind on the execution of transactions and <strong>create a replication lag</strong>, if the SQL thread cannot execute transactions fast enough in the storage engine (7).</p>
<p>That’s why it’s called semi-sync replication, and not synchronous replication. (It sure would be nice if it was fully synchronous, though.)</p>
<p><img src="/uploads/2022/07/0_0-3.png" alt=""></p>
<p>If you develop your own failover solution, it is vital that you <strong>ensure that you fully process all existing relay logs</strong> on the replica that has been designated the new source <strong>before turning on write traffic</strong>, i.e. before setting <a href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_only">read_only=0</a>. If you don’t, you’ll start writing transactions to the storage engine out of order. In the best case scenario they will be unrelated, but most likely you’ll get pretty horrible duplicate key exceptions and will spend hours or days trying to repair the state.</p>
<p>When using orchestrator, waiting for the relay logs can be enabled by setting <a href="https://github.com/openark/orchestrator/blob/de1b1ecd3f65cac447b24067d99dc56a8109fd82/docs/configuration-recovery.md#promotion-actions">DelayMasterPromotionIfSQLThreadNotUpToDate=true</a>. Since orchestrator manages the read-only state, you don’t have to worry about that separately.</p>
<h3 id="be-sure-to-shoot-the-other-node-in-the-head">Be sure to shoot the other node in the head</h3>
<p>There are many different failure modes that may lead to the decision to fail over to a replica. For the sake of simplicity, we as engineers often only talk about a hard crash of a source and sometimes forget that temporary failures such as network blips, a hanging process or even just a service restart. Not considering temporary failures like this can be a huge mistake, because we always want to make sure that <strong>our application is only ever talking to the correct source</strong>, and not to the back-from-the-dead host that we failed over from.</p>
<p>When the decision is made to fail over from a failed source to a newly promoted replica, <strong>it is important to fence off the failed node from the application before appointing a replica to be the new source</strong>. This process is called <a href="https://en.wikipedia.org/wiki/STONITH">Shoot The Other Node in the Head (STONITH)</a>.</p>
<p><img src="/uploads/2022/07/0_0-4.png" alt=""></p>
<p>Here’s an example of a failover with orchestrator that includes a STONITH pre-hook:</p>
<p>In this illustration, we see orchestrator has discovered a failure on the source (A) and decides to fail over to B. Using orchestrator’s pre-failover hook <a href="https://github.com/openark/orchestrator/blob/de1b1ecd3f65cac447b24067d99dc56a8109fd82/docs/configuration-recovery.md#hooks">PreFailoverProcesses</a>, we fence off A by pointing the proxy to “nowhere” while the failover is in progress. After orchestrator has completed the failover to the new source B (3), and repointed the other replica C to the new source (4), it updates the proxy configuration again (5) to send traffic to the new source (6).</p>
<p>Without the STONITH step (2), the proxy would point to the failed source until orchestrator is done with the failover. If the host comes back online in the meantime, you will have written to the wrong source host, and in the worst case you’ll lose those transactions.</p>
<p>Unfortunately, orchestrator <a href="https://github.com/openark/orchestrator/issues/1275">does not support STONITH out of the box</a>, so you’ll have to write your own pre-hook script to accomplish it. In HAproxy, you can simply update a <code>listen</code> block to point to <code>127.0.0.1:1337</code> (a non-existing target). In ProxySQL, you can achieve this by marking the host as OFFLINE <a href="https://www.percona.com/blog/2019/04/02/simple-stonith-proxysql-orchestrator/">as this blog post describes</a>.</p>
<p>(Please note that the STONITH approach is not without controversy. You may read more in this <a href="https://planetscale.com/blog/mysql-semi-sync-replication-durability-consistency-and-split-brains">blog post</a>.)</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>As of today, we’ve deployed our lossless semi-sync setup and the automated failover solution on hundreds of hosts, managing hundreds of millions of tables. Despite a few (quite severe) hiccups and the hundreds of hours we’ve spent optimizing and automating things, I’d say overall we’re pretty happy with the setup.</p>
<p>We’re using the exact setup I already talked about, namely HAproxy, orchestrator, Consul and Consul Template. I’ve uploaded a representative set of <a href="https://github.com/binwiederhier/mysql-failover">configuration files to GitHub</a>, so you don’t have to start from zero when setting this up yourself.</p>
<p><img src="/uploads/2022/07/0_0-5.png" alt=""></p>
<p>Here are some details and important configurations:</p>
<p><strong>MySQL:</strong></p>
<p>We use <a href="https://www.percona.com/software/mysql-database/percona-server">Percona Server 8</a>, which works pretty well, despite the extreme scale that we use it with. We’ve seen a number of horrible crashes related to too many replication restarts (which we fixed by telling orchestrator not to restart replication so often through a higher <a href="https://github.com/outbrain/orchestrator/blob/84d516bdaa12834b87d8431ce18d3d200a907964/go/config/config.go#L92">ReasonableReplicationLagSeconds</a> setting), but other than that I’d say it’s been solid.</p>
<p>The semi-sync settings in our <a href="https://github.com/binwiederhier/mysql-failover/blob/main/mysql/mysqld.cnf">mysqld.cnf</a> file match what was discussed above. Long source timeout to prevent async fallback, and semi-sync enabled by default:</p>
<pre tabindex="0"><code>loose-rpl_semi_sync_master_enabled              = 1
loose-rpl_semi_sync_master_timeout              = 3600000
loose-rpl_semi_sync_slave_enabled               = 1
loose-rpl_semi_sync_master_wait_for_slave_count = 1
</code></pre><p>It is worth noting that if you enable the semi-sync source setting on all hosts (including replicas) at startup, you may run into oddities in your stats. We solved this with a custom script (not in the repository) that disables the setting on replicas dynamically (<a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_enabled">rpl_semi_sync_master_enabled=0</a>).</p>
<p><strong>HAproxy:</strong></p>
<p>After an unsuccessful attempt to use ProxySQL (it kept crashing constantly), we decided to use HAproxy for proxying SQL traffic to the currently active source host(s). The main <a href="https://github.com/binwiederhier/mysql-failover/blob/main/haproxy/conf.d/haproxy.cfg">haproxy.cfg</a> file is pretty basic and not really worth talking about.</p>
<p>The <a href="https://github.com/binwiederhier/mysql-failover/blob/main/haproxy/conf.d/mysql.cfg">mysql.cfg</a> file, however, contains <code>listen</code> blocks that route to the currently active source hosts. This file is autogenerated and kept up-to-date by Consul Template using the template file <a href="https://github.com/binwiederhier/mysql-failover/blob/main/haproxy/haproxy_mysql.cfg.tpl">haproxy_mysql.cfg.tpl</a>. The template will generate a listen block like this for every MySQL source host:</p>
<pre tabindex="0"><code>listen mysql-g0
   bind 10.0.13.1:3400
   server db-g0-1.example.com 10.0.14.1:3306
   mode tcp
   option tcplog
</code></pre><p>It also implements one part of our STONITH approach: If the Consul key <code>mysql/master/$cluster/failed</code> exists, it will black-hole all traffic to this cluster by pointing it to 127.0.0.1:1337, a non-existing host.</p>
<p><strong>Consul Template:</strong></p>
<p>The template above is updated and re-rendered by Consul Template using the config file <a href="https://github.com/binwiederhier/mysql-failover/blob/main/consul-template/haproxy.hcl">haproxy.hcl</a>. After it re-renders the file, we reload HAproxy to refresh the configuration.</p>
<p>In our real production environment, we don’t just call out to systemctl reload haproxy. Instead we have a tiny Python script (not included in the repository) that kills all active connections to the cluster and then reloads HAproxy.</p>
<p><strong>Consul:</strong></p>
<p>Consul is the single source of truth for which MySQL host(s) are the source. The installation is pretty straightforward (see <a href="https://github.com/binwiederhier/mysql-failover/tree/main/consul">configuration files</a>).</p>
<p><strong>orchestrator:</strong></p>
<p>We run orchestrator in a cluster of three with a MySQL backend and with <a href="https://en.wikipedia.org/wiki/Raft_(algorithm)">Raft support</a> (see <a href="https://github.com/binwiederhier/mysql-failover/tree/main/orchestrator">configuration files</a>).</p>
<p>Most importantly, we enabled <a href="https://github.com/openark/orchestrator/blob/de1b1ecd3f65cac447b24067d99dc56a8109fd82/docs/configuration-recovery.md#promotion-actions">DelayMasterPromotionIfSQLThreadNotUpToDate=true</a>, which makes sure that orchestrator waits for the relay logs before promoting a replica and turning read-write on. See above for a detailed discussion on this.</p>
<p>We also enabled the brand new <a href="https://github.com/openark/orchestrator/blob/master/docs/configuration-discovery-classifying.md#semi-sync-topology">EnforceExactSemiSyncReplica=true</a> setting (which I am <a href="https://github.com/openark/orchestrator/releases/tag/v3.2.6">very proud to have contributed to orchestrator</a>), which completely manages the semi-sync replica state: with this setting, orchestrator will enforce the correct semi-sync flag on the replicas during failover (i.e. enabling and disabling it), matching the wait count set in <a href="https://dev.mysql.com/doc/refman/8.0/en/replication-options-source.html#sysvar_rpl_semi_sync_master_wait_for_slave_count">rpl_semi_sync_master_wait_for_slave_count</a>. For us this is especially important, because we’d like to only ever have one semi-sync replica, even though we have two replicas, so that we know which host to fail over to, and so can do maintenance or one of them can crash.</p>
<p>Looking at the <a href="https://github.com/openark/orchestrator/blob/master/docs/configuration-recovery.md#hooks">PreFailoverProcesses</a> in the <a href="https://github.com/binwiederhier/mysql-failover/blob/main/orchestrator/orchestrator.json">orchestrator.json</a> file, you can see that we’re calling a script called <code>orchestrator-pre-failover</code> (not included in the repository). This script sets the <code>mysql/master/$cluster/failed</code> key, which triggers Consul Template to update the HAproxy config and back-hole the cluster for new traffic. This is the other side of the STONITH mechanism I talked about above.</p>
<h2 id="wrapping-it-up">Wrapping it up</h2>
<p>As you can tell from the length of this post, our journey to lossless MySQL replication and failover has been quite interesting and we’ve learned a lot. If you’ve read the whole thing, I applaud you and thank you for staying with me.</p>
<p>In this post, I discussed all the technologies and tools that are necessary to provide a MySQL replication environment with lossless automated failover. I compared async replication to semi-sync replication and discussed some gotchas. I then presented different approaches for automated failover and highlighted the pieces that are relevant for a lossless setup, concluding in a section showing our own fully functional setup.</p>
<p>MySQL replication and failover is complicated. It’s really easy to mess it up and lose data. I hope this post helped prevent data loss in your organization.</p>]]></content:encoded></item><item><title>elastictl: Import, export, re-shard and performance-test Elasticsearch indices</title><link>https://heckel.io/blog/elastictl-import-export-re-shard-and-performance-test-elasticsearch-indices/</link><pubDate>Sun, 20 Jun 2021 20:41:03 -0400</pubDate><guid>https://heckel.io/blog/elastictl-import-export-re-shard-and-performance-test-elasticsearch-indices/</guid><description>For my work, I work a lot with Elasticsearch. Elasticsearch is pretty famous by now, so I doubt that it needs an introduction. But if you happen to not know what it is: it&amp;rsquo;s a document store with unique search capabilities, and incredible scalability.
Despite its incredible features though, it …</description><content:encoded><![CDATA[<p>For my work, I work a lot with <a href="https://www.elastic.co/">Elasticsearch</a>. Elasticsearch is pretty famous by now, so I doubt that it needs an introduction. But if you happen to not know what it is: it&rsquo;s a document store with unique search capabilities, and incredible scalability.</p>
<p>Despite its incredible features though, it has its rough edges. And no, I don&rsquo;t mean the horrific query language (honestly, who thought that was a good idea?). I mean the fact that without external tools it&rsquo;s quite impossible to import, export, copy, move or re-shard an Elasticsearch index. Indices are very final, unfortunately.</p>
<p>This is quite often very inconvenient if you have a growing index for which each Elasticsearch shard is outgrowing its recommended size (2 billion documents). Or even if you have the opposite problem: if you have an ES cluster that has too many shards (~800 shards per host is the recommendation I think), because you have too many indices.</p>
<p>This is why I wrote <a href="https://github.com/binwiederhier/elastictl">elastictl</a>: <strong>elastictl is a simple tool to import/export Elasticsearch indices into a file, and/or reshard an index</strong>. In this short post, I&rsquo;ll show a few examples of how it can be used.</p>
<h2 id="usage">Usage</h2>
<p>elastictl can be used for:</p>
<ul>
<li>Backup/restore of an Elasticsearch index</li>
<li>Performance test an Elasticsearch cluster (import with high concurrency, see <code>--workers</code>)</li>
<li>Change the shard/replica count of an index (see <code>elastictl reshard</code> command)</li>
</ul>
<p>It&rsquo;s a tiny utility, so don&rsquo;t expect too much, but it&rsquo;s helped our work quite a bit. It allows you to easily copy an index, or move it, or test the index concurrency supported by your cluster. In my local cluster, I was able to import ~10k documents per second.</p>
<p>Here&rsquo;s a short usage overview:</p>
<pre tabindex="0"><code>$ elastictl
NAME:
   elastictl - Elasticsearch toolkit

USAGE:
   elastictl COMMAND [OPTION..] [ARG..]

COMMANDS:
   export, e   Export an entire index to STDOUT
   import, i   Write to ES index from STDIN
   reshard, r  Reshard index using different shard/replica counts

Try &#39;elastictl COMMAND --help&#39; for more information.

elastictl 0.0.5 (e645803), runtime go1.16, built at 2021-04-14T15:05:42Z
Copyright (C) 2021 Philipp C. Heckel, distributed under the Apache License 2.0
</code></pre><h2 id="exportdump-an-index-to-a-file">Export/dump an index to a file</h2>
<p>To back up an index into a file, including its mapping and all the documents, you can use the <code>elastictl export</code> command. It will write out JSON to STDOUT. The file format is pretty simple: the first line of the output format is the mapping, the rest are the documents. You can even export only a subset of an index using the <code>--search/-q</code> option (<em>that is, if you can master the query language</em>).</p>
<pre tabindex="0"><code># Entire index (assumes that ES is running at localhost:9200)
elastictl export my-index | gzip &gt; my-index.json.gz

# Only a subset of documents
elastictl export \
  --host 10.0.1.2:9200 \
  --search &#39;{&#34;query&#34;:{&#34;bool&#34;:{&#34;must_not&#34;:{&#34;match&#34;:{&#34;eventType&#34;:&#34;Success&#34;}}}}}&#39; \
  my-index &gt; my-index-no-successes.json
</code></pre><p>If you&rsquo;re wondering &ldquo;isn&rsquo;t this just like <a href="https://github.com/elasticsearch-dump/elasticsearch-dump">elasticdump</a>&rdquo;? The answer is yes and no. I naturally tried <code>elasticdump</code> first, but it didn&rsquo;t really work for me: I had issues installing it via npm, and it was quite frankly rather slow. <code>elasticdump</code> also doesn&rsquo;t support resharding, though it has many other cool features.</p>
<h2 id="import-to-new-index">Import to new index</h2>
<p>The <code>elastictl import</code> command will read from STDIN and write a previously exported file to a new or existing index with configurable concurrency. Using a high number of <code>--workers</code>, you can really hammer the ES cluster. It&rsquo;s actually quite easy to make even large clusters fall over like this (assuming of course that you&rsquo;re pointing <code>--host</code> to a load balancer):</p>
<pre tabindex="0"><code># With high concurrency
zcat my-index.json.gz | elastictl import --workers 100 my-index-copy

# Just copy and index
elastictl export my-index | elastictl import my-index-copy2
</code></pre><p>There are other options you can pass to the <code>elastictl import</code> command to modify the mapping slightly (mostly the number of replicas and the number of shards):</p>
<pre tabindex="0"><code>$ elastictl import --help
NAME:
   elastictl import - Write to ES index from STDIN

USAGE:
   elastictl import INDEX

OPTIONS:
   --host value, -H value      override default host (default: localhost:9200)
   --workers value, -w value   number of concurrent workers (default: 50)
   --shards value, -s value    override the number of shards on index creation (default: no change)
   --replicas value, -r value  override the number of replicas on index creation (default: no change)
   --no-create, -N             do not create index (default: false)
   --help, -h                  show help (default: false)
</code></pre><h2 id="re-shard-an-index">Re-shard an index</h2>
<p>The <code>elastictl reshard</code> command is a combination of the two above commands: it first exports an index into a file and then re-imports it with a different number of shards and/or replicas.</p>
<pre tabindex="0"><code># Set number of shards of the &#34;my-index&#34; index to 10 and the number of replicas to 1
elastictl reshard \
  --shards 10 \
  --replicas 1 \
  my-index

# Export a subset of the &#34;my-index&#34; index and re-import it with a smaller number of shards/replicas
elastictl reshard \
  --search &#39;{&#34;query&#34;:{&#34;bool&#34;:{&#34;must_not&#34;:{&#34;match&#34;:{&#34;eventType&#34;:&#34;Success&#34;}}}}}&#39; \
  --shards 1 \
  --replicas 1 \
  my-index
</code></pre><p><strong>Note</strong>: Similar to the <code>_reindex</code> API in Elasticsearch, this command should be used while the index is not being written to, because documents coming in after the command was kicked off will otherwise be lost. Please also note that the command does <code>DELETE</code> the index after exporting it. A copy will be available on disk though.</p>
<h2 id="feedback-is-welcome">Feedback is welcome</h2>
<p><a href="https://github.com/binwiederhier/elastictl">elastictl</a> is a tiny little tool, and I&rsquo;m sure there are others that do a similar job. The tool is open source and available under the Apache 2.0 license, so please feel free to send contributions via pull a request on GitHub.</p>]]></content:encoded></item><item><title>Snippet 0x0F: Recursive search/replace tool "re"</title><link>https://heckel.io/blog/recursive-search-replace-tool-re/</link><pubDate>Thu, 17 Dec 2020 02:44:14 -0500</pubDate><guid>https://heckel.io/blog/recursive-search-replace-tool-re/</guid><description>Two and a half years ago, I wrote my first Go program. I wanted to learn another language, and Go looked like a ton of fun: straight forward, easy to learn, and a static binary with no runtime shenanigans. I picked a project and I started hacking. Looking back, the code I wrote is a little cringy, …</description><content:encoded><![CDATA[<p>Two and a half years ago, I wrote my first Go program. I wanted to learn another language, and Go looked like a ton of fun: straight forward, easy to learn, and a static binary with no runtime shenanigans. I picked a project and I started hacking. Looking back, the code I wrote is a little cringy, but not terrible. I&rsquo;d surely do things differently these days, now that I have more Go experience. But we all start somewhere.</p>
<p>However, the tool that I wrote, <strong>a recursive search/replace tool</strong> which I intelligently dubbed <a href="https://github.com/binwiederhier/re">re</a>, is actually incredibly useful: to my own surprise, I use it every day. I haven&rsquo;t made a single modification to it in all that time (until today for this post). And since I&rsquo;m in the sharing mood today, I thought I&rsquo;d share it with the millions of people (<em>cough</em>) that come here every day. Ha!</p>
<h2 id="why-is-there-no-recursive-sed-out-there">Why is there no recursive <code>sed</code> out there?</h2>
<p>It&rsquo;s a little odd, isn&rsquo;t it? When you search for <a href="https://www.google.com/search?q=recursive+replace+tool+linux">recursive search replace linux</a> in your favorite search engine, you get all sorts of <code>find | xarg sed</code>-type solutions (like <a href="https://superuser.com/questions/428493/how-can-i-do-a-recursive-find-and-replace-from-the-command-line">here</a>), or <code>find -exec sed ...</code> (like <a href="https://stackoverflow.com/questions/11392478/how-to-replace-a-string-in-multiple-files-in-linux-command-line">here</a>). And yes, of course, that&rsquo;s the Linux way: combine the tools that you already have to do what you want. Still, searching and replacing seems like a pretty basic thing to me.</p>
<p>So anyway, for some reason, there is no standard Linux utility that can do it out of the box &ndash; something where you can just do <code>re oldvalue newvalue</code> to replace and old value with a new value in all files in the current folder (and all its subfolders).</p>
<h2 id="introducing-the-fabulous-re">Introducing the fabulous <code>re</code></h2>
<p>Well you&rsquo;re in luck. That&rsquo;s what <a href="https://github.com/binwiederhier/re">re</a> is. A tool that can <strong>recursively replace strings in one or many directories.</strong> By default, it will walk the entire directory tree and replace the search string in all files recursively. You can provide include/exclude wildcards for the filename, e.g. to only replace strings in &ldquo;*.java&rdquo; or &ldquo;.sh&rdquo; files.</p>
<p>Here&rsquo;s the basic syntax:</p>
<pre tabindex="0"><code>Syntax: re [options] SEARCH REPLACEMENT [DIR ...]
Options:
  -e string
    	Comma-separated list of excluded files, wildcards supported (default &#34;.bzr,CVS,.git,.hg,.svn&#34;)
  -f	Apply changes
  -i string
    	Comma-separated list of included files, wildcards supported, e.g &#34;*.js,*.html,*index.*&#34;
</code></pre><p>And here are a few examples of how I used it in the past (straight from my <code>history</code>):</p>
<pre tabindex="0"><code># Shows files that would have been replaced. Without the -f flag, 
# nothing is actually replaced.
$ re CacheDir ClipboardDir

# Replace CacheDir with ClipboardDir recursively
$ re -f CacheDir ClipboardDir

# Replace &#34;Foo(Bar)&#34; with &#34;Foo(Baz)&#34; recursively in all scala files
$ re -f -i &#34;*.scala&#34; &#34;Foo(Bar)&#34; &#34;Foo(Baz)&#34; 
</code></pre><p>Here&rsquo;s what the output will look like. In this example, I&rsquo;m not passing the <code>-f</code> flag, so no change will be applied:</p>
<pre tabindex="0"><code>$ re -i &#34;*.go&#34; var const
No changes will be applied unless -f is given.
Skipping excluded directory .git
+ LEGACY/client.go
+ LEGACY/udp_client.go
+ LEGACY/udp_server.go
+ build/lib/_obj/_cgo_gotypes.go
+ client_conn.go
+ client_forward.go
+ cmd/natter/main.go
+ internal/natter.pb.go
+ protocol.go
9 file(s) WOULD have been updated.
</code></pre><p>You get the idea. It&rsquo;s simple. That&rsquo;s it.</p>
<p>If you like it, you can get it from the <a href="https://github.com/binwiederhier/re">Github page</a>. Either build it yourself (<code>go build</code>) or check out the <a href="https://github.com/binwiederhier/re/releases">releases page</a>.</p>
<h2 id="a-about-this-post">A. About this post</h2>
<p>This post is super short because it&rsquo;s just a <a href="/blog/categories/code-snippets/">Code Snippet</a>. You can find other short, code-focused posts like this in that section.</p>]]></content:encoded></item><item><title>Go: Calculating public key hashes for public key pinning in curl</title><link>https://heckel.io/blog/calculating-public-key-hashes-for-public-key-pinning-in-curl/</link><pubDate>Sun, 13 Dec 2020 19:30:32 -0500</pubDate><guid>https://heckel.io/blog/calculating-public-key-hashes-for-public-key-pinning-in-curl/</guid><description>Something occurred to me the other day. This is my blog, and that means I can write about whatever I want. Now you may think that&amp;rsquo;s totally obvious, but it&amp;rsquo;s not. For the longest time I wouldn&amp;rsquo;t blog about anything that I didn&amp;rsquo;t deem blog-worthy. Small things, like …</description><content:encoded><![CDATA[<p>Something occurred to me the other day. This is my blog, and that means I can write about whatever I want. Now you may think that&rsquo;s totally obvious, but it&rsquo;s not. For the longest time I wouldn&rsquo;t blog about anything that I didn&rsquo;t deem blog-worthy. Small things, like &ldquo;this is a cool function I found&rdquo; or &ldquo;I learned this thing today&rdquo;, were not blog-worthy in my mind for some reason.</p>
<p>Well today I am changing that. I like writing, but not necessarily so much that I always want to write a super long post. Sometimes, things should be short. Like this one.</p>
<p>So in this super short post I&rsquo;m gonna show you a cool thing I figured out: How to calculate the the value that <code>curl</code>s <code>--pinnedpubkey</code> option needs in Go.</p>
<h2 id="curls---insecure-flag-is-insecure--shocker">curl&rsquo;s <code>--insecure</code> flag is insecure &ndash; shocker!</h2>
<p>For my tiny side project <a href="https://github.com/binwiederhier/pcopy">pcopy</a>, I wanted to be able to be able to allow people to easily install/join a remote clipboard using a <code>curl | sh</code>-type install, even when the shared clipboard is internal to a company network and doesn&rsquo;t have a proper SSL certificate, i.e. when the cert is self-signed.</p>
<p>The only way to make <code>curl</code> work with self-signed certs is with the <code>-k</code> flag (<code>--insecure</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ pcopy invite
</span></span><span class="line"><span class="cl"><span class="c1"># Instructions for clipboard &#39;default&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install pcopy on other computers (as root):</span>
</span></span><span class="line"><span class="cl">curl -sSLk https://10.0.160.67:1986/install <span class="p">|</span> sudo sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Join this clipboard on other computers:</span>
</span></span><span class="line"><span class="cl">curl -sSLk https://10.0.160.67:1986/join <span class="p">|</span> sh
</span></span></code></pre></div><p>And that&rsquo;s obviously not cool, because that flag allows replacing the cert entirely in man-in-the-middle attacks (see <a href="/blog/how-to-use-mitmproxy-to-read-and-modify-https-traffic-of-your-phone/">my post on mitmproxy</a> for a practical example; wow that post is from 7 years ago &ndash; I&rsquo;m telling you time flies &hellip;).</p>
<h2 id="--pinnedpubkey-to-the-rescue"><code>--pinnedpubkey</code> to the rescue</h2>
<p>So what to do, what to do? Should we just not care about that? No of course not! I care deeply about security, so let&rsquo;s figure out if we can use <a href="https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning">public key pinning</a> in curl (note that this link points to &ldquo;HTTP public key pinning&rdquo; not the concept of public key pinning in general; there is strangely no Wikipedia article about that).</p>
<p>Public key pinning allows us to pin a specific public key for a given request, so that even if a certificate is self-signed it can&rsquo;t be replaced without raising an exception. This technique is pretty useful if you want to support self-signed certs, but still be secure.</p>
<p>And surely enough, <code>curl</code> has a <code>--pinnedpubkey</code> option. From the <a href="https://curl.se/docs/manpage.html">man page</a>:</p>
<pre tabindex="0"><code>--pinnedpubkey &lt;hashes&gt;
  (TLS)  Tells  curl to use the specified public key file (or hashes) to verify the peer. 
  This can be a path to a file which contains a single public key in PEM or DER format, 
  or any number of base64 encoded sha256 hashes preceded by ´sha256//´ and separated by ´;´

  When negotiating a TLS or SSL connection, the server sends a certificate indicating its
  identity. A public key is extracted from this certificate and if it does not exactly match
  the public key provided to this option, curl will abort the connection before sending or
  receiving any data.
&lt;/hashes&gt;
</code></pre><p>Now this description wasn&rsquo;t really helpful when I was trying to figure out what exactly curl was expecting here. Public keys can be encoded as PEM or DER, and then there&rsquo;s also PKIX and PKCS1. Plus somehow ASN.1 is involved in the whole thing. It&rsquo;s all pretty confusing, and of course, none of this is mentioned in the tiny man page entry. So it took a while to figure it out.</p>
<p>So with the help of the curl docs on <a href="https://curl.se/libcurl/c/CURLOPT_PINNEDPUBLICKEY.html">CURLOPT_PINNEDPUBLICKEY</a> and the lovely <a href="https://invite.slack.golangbridge.org/">#security channel on the Go Slack</a>, I figured out that curl is expecting <strong>the base64-encoded SHA-256 checksum of the PKIX, ASN.1 DER encoded public key</strong>. That&rsquo;s a mouthful, isn&rsquo;t it?</p>
<p>In bash (using openssl), that looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Assuming default.crt is a PEM-encoded cert, this extracts the public key</span>
</span></span><span class="line"><span class="cl"><span class="c1"># converts it to DER form, hashes it with SHA-256, then base64-encodes it</span>
</span></span><span class="line"><span class="cl"><span class="c1"># and prepends &#34;sha256//&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> sha256//<span class="k">$(</span>openssl x509 -in default.crt -pubkey -noout <span class="se">\
</span></span></span><span class="line"><span class="cl">   <span class="p">|</span> openssl asn1parse -inform PEM -in - -noout -out - <span class="se">\
</span></span></span><span class="line"><span class="cl">   <span class="p">|</span> openssl dgst -sha256 -binary - <span class="se">\
</span></span></span><span class="line"><span class="cl">   <span class="p">|</span> openssl base64<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Outputs something like sha256//Y/CGGnkaoZwUgOqArQs12llyoaX0bkjSIgHCPtXba+c=</span>
</span></span></code></pre></div><p>In Go, it looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">calculatePublicKeyHashes</span><span class="p">(</span><span class="nx">certs</span><span class="w"> </span><span class="p">[]</span><span class="o">*</span><span class="nx">x509</span><span class="p">.</span><span class="nx">Certificate</span><span class="p">)</span><span class="w"> </span><span class="p">([]</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nx">hashes</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">certs</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">cert</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">certs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">derCert</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">x509</span><span class="p">.</span><span class="nf">MarshalPKIXPublicKey</span><span class="p">(</span><span class="nx">cert</span><span class="p">.</span><span class="nx">PublicKey</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">hash</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sha256</span><span class="p">.</span><span class="nf">New</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">hash</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">derCert</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">hashes</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;sha256//%s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">base64</span><span class="p">.</span><span class="nx">StdEncoding</span><span class="p">.</span><span class="nf">EncodeToString</span><span class="p">(</span><span class="nx">hash</span><span class="p">.</span><span class="nf">Sum</span><span class="p">(</span><span class="kc">nil</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="nx">hashes</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>It took me a while to figure out that I needed to use <code>x509.MarshalPKIXPublicKey</code> (PKIX, ASN.1 DER form), and not <code>x509.MarshalPKCS1PublicKey</code> (PKCS#1, ASN.1 DER form). Apparently, the PKIX format also includes the public key algorithm (RSA, EC), and not just the raw bytes, and curl expects this form. There is a good explanation of <a href="https://stackoverflow.com/a/49878687/1440785">Stack Overflow</a>.</p>
<p>Long story short, now <code>pcopy invite</code> (see <a href="https://github.com/binwiederhier/pcopy/blob/master/cmd/pcopy/invite.go">source code</a>) can output a secure curl command, even for self-signed certs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ pcopy invite
</span></span><span class="line"><span class="cl"><span class="c1"># Instructions for clipboard &#39;default&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install pcopy on other computers (as root):</span>
</span></span><span class="line"><span class="cl">curl -sSLk --pinnedpubkey sha256//Y/CGGnkaoZwUgOqArQs12llyoaX0bkjSIgHCPtXba+c<span class="o">=</span> https://10.0.160.67:1986/install <span class="p">|</span> sudo sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Join this clipboard on other computers:</span>
</span></span><span class="line"><span class="cl">curl -sSLk --pinnedpubkey sha256//Y/CGGnkaoZwUgOqArQs12llyoaX0bkjSIgHCPtXba+c<span class="o">=</span> https://10.0.160.67:1986/join <span class="p">|</span> sh
</span></span></code></pre></div><p>Note that despite the <code>-k</code> flag still being there, the command cannot be intercepted without curl erroring, because the pinned public key hash won&rsquo;t match. However, removing the flag will make curl complain with <code>curl: (60) SSL certificate problem: self signed certificate</code>. All that means is that the <code>--pinnedpubkey</code> verification happens after the self-signed cert verification.</p>]]></content:encoded></item><item><title>Reliably rebooting Ubuntu using watchdogs</title><link>https://heckel.io/blog/reliably-rebooting-ubuntu-using-watchdogs/</link><pubDate>Thu, 08 Oct 2020 18:05:48 -0400</pubDate><guid>https://heckel.io/blog/reliably-rebooting-ubuntu-using-watchdogs/</guid><description>Rebooting Ubuntu is hard. I don’t really know why, but in my twelve years as an Ubuntu user, I’ve encountered countless “stuck at reboot” scenarios. Somehow, typing reboot always comes with that extra special feeling of uncertainty and the thrill of danger &amp;ndash; Will it come back? Where will it …</description><content:encoded><![CDATA[<p>Rebooting Ubuntu is hard. I don’t really know why, but in my twelve years as an Ubuntu user, I’ve encountered countless “stuck at reboot” scenarios. Somehow, typing <code>reboot</code> always comes with that extra special feeling of uncertainty and the thrill of danger &ndash; Will it come back? Where will it get stuck this time? If it’s your home computer or your laptop, that’s fine, because you can always manually hard reset. If it’s a remote computer to which you have IPMI access, it’s a little bit annoying, but not tragic. But if you’re attempting to reboot tens of thousands of devices across the globe, that level of uncertainty is nothing short of terrifying.</p>
<p>I know I’m being unfair, because more often than not, rebooting Ubuntu actually completes successfully. However, my incredibly unscientific estimate of how often things get stuck forever on shutdown or reboot is this: 1-3%. That’s how often I believe reboots hang. That’s shockingly high, right? Well, I pulled that out of my hat, but that estimate is based on many hundred thousands of reboots I’ve witnessed in our fleet of backup devices. That number is not too terrible when you deal with a handful of machines that you rarely ever reboot. It is, however, incredibly terrible if you reboot tens of thousands of devices running Ubuntu every two weeks as part of an upgrade process (I wrote about our <a href="/blog/image-based-upgrades-upgrading-software-and-os-of-80k-servers-every-two-weeks/">image based upgrade mechanism in another post</a>).</p>
<p>This post describes the short story of <strong>how we managed to make Ubuntu machines reliably reboot</strong>.</p>
<h2 id="1-whats-the-problem">1. What’s the problem?</h2>
<p>When we first rolled out <a href="/blog/image-based-upgrades-upgrading-software-and-os-of-80k-servers-every-two-weeks/">Image Based Upgrades</a>, we encountered tons of stuck reboots. The first few thousand device reboots were terrifying; so many devices just didn’t come back up. For our customers, that meant driving on-site or using a remote power strip &ndash; both of which are annoying and costly.</p>
<p>Either things didn’t even begin the reboot process, like here:</p>
<p><img src="/uploads/2020/10/reboot-connection-timeout.png" alt="connection timeout"></p>
<p>Or they got stuck at random points during the shutdown procedure:</p>
<p><img src="/uploads/2020/10/reboot-stuck-1.png" alt="reboot stuck"></p>
<p>Think about how hard it is to debug shutdown problems like this when you have IPMI access in your own datacenter. Now imagine how hard it is to debug without physical access to the machines, and having to rely on the good grace of your own customers and support team to work with you. We all worked very closely together with our customers, taking hundreds of pictures of hung machines trying to pinpoint the root cause. We spent days trying to reproduce the problems in-house, upgrading devices over and over again many times a day to force the issue. We analyzed ticket after ticket, but in the end we were none the wiser: Ubuntu seemed to have gotten stuck just at random positions in the shutdown process. There was no pattern.</p>
<p>So what now? Give up? Of course not. We didn’t give up. We just merely chose to give up diagnosing the root cause and opted for the nuclear option instead: hard resetting.</p>
<h2 id="2-how-about-sysrq">2. How about Sysrq?</h2>
<p>We first thought about using the <a href="https://www.kernel.org/doc/html/v4.11/admin-guide/sysrq.html">Sysrq triggers</a> that the Linux kernel provides to immediately trigger a hard-reset. Instead of calling <code>reboot</code>, we’d call this magical command to instruct the kernel to hard-reset the computer.</p>
<pre tabindex="0"><code>echo b &gt; /proc/sysrq-trigger   # Trigger hard-reset
</code></pre><p>This works wonderfully and actually is a viable option if you are 100% sure that you’ve written everything to disk that you need to, and that all the buffers are flushed to disk. So if you run a read-only Linux, this option may be for you. In all other cases, using just the <code>echo b</code> trigger is not enough, because you run the risk of corrupting random files, your file system, or other in-flight operations.</p>
<p>So what about this then?</p>
<pre tabindex="0"><code>echo s &gt; /proc/sysrq-trigger   # Sync all mounted filesystems to disk
echo b &gt; /proc/sysrq-trigger   # Trigger hard-reset
</code></pre><p>Well, yes, that’s better, because it flushes all the buffers to disk before pulling the cord. The chance of losing or corrupting data is very slim with this one. It’s still pretty nuclear if you ask me, and doesn’t make me feel warm and fuzzy inside. And as it turns out, <code>echo s</code> can hang indefinitely (much like <code>sync</code>) if the hardware doesn’t respond &ndash; the infamous <a href="https://stackoverflow.com/a/223727/1440785">uninterruptible sleep</a> (<code>D</code>) strikes again. (Side note: I am far from an expert in this field, but the amount of times TASK_UNINTERRUPTIBLE has bitten us left and right leaves me to believe that there is something wrong with having a state like that in the kernel at all. That’s a story for another time, though.)</p>
<p>So because <code>echo s</code> can hang, we briefly experimented with something like this:</p>
<pre tabindex="0"><code>timeout 10s bash -c ‘echo s &gt; /proc/sysrq-trigger’  # Wait max 10s for sync
echo b &gt; /proc/sysrq-trigger 
</code></pre><p>We tested this for a while, but saw enough corrupt file systems (despite the <code>echo s</code>) that it scared us away from this approach. It also felt wrong to punish the 98% of devices that were rebooting just fine using <code>reboot</code>. What we really needed was a reboot mechanism that first tried to run a proper <code>reboot</code>, and only triggered a hard-reset when things got stuck for too long.</p>
<p>We reviewed how <code>reboot</code> is actually implemented and then thought about implementing our own reboot mechanism with a hard-reset fallback. How hard can it be? It’s just sending SIGTERM to all processes, and then waiting a while for things to gracefully shut down before triggering a hard-reset, right?</p>
<p>Before you raise your eyebrows too much, I’m kidding. We realized pretty quickly that if Ubuntu can’t get it right, we’d probably have a hard time too. So on to the next idea.</p>
<h2 id="3-introducing-watchdogs">3. Introducing watchdogs</h2>
<p>The Linux kernel provides a <a href="https://www.kernel.org/doc/Documentation/watchdog/watchdog-api.txt">watchdog API</a> whose entire purpose in life is to hard-reset systems that get stuck or hang due to unrecoverable errors such as a kernel panic. Turns out that many modern CPUs and some <a href="https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface">IPMI/BMC systems</a> ship with a hardware-level watchdog implementation that hard-resets the host system unless a heartbeat is received regularly. When a watchdog is enabled, and a system freezes or otherwise hangs so that it cannot send a heartbeat, the watchdog resets the computer.</p>
<p>This is exactly what we needed!</p>
<p>Even though watchdogs certainly aren’t meant to be used to ensure a proper reboot, they absolutely can be used for what we need: enable a hardware-level watchdog that hard resets the machine in case the soft reboot via <code>reboot</code> fails.</p>
<p>There are multiple watchdog implementations, each of which comes with its own quirks. We have found the <strong>Intel CPU-based<a href="https://github.com/torvalds/linux/blob/afd2ff9b7e1b367172f18ba7f693dfb62bdcb2dc/drivers/watchdog/iTCO_wdt.c">iTCO watchdog</a></strong> to be the most reliable, so we use that as a default. Unfortunately, some motherboard vendors disable this functionality in hardware. In that case, and on non-Intel devices, we have two fallback options:</p>
<ul>
<li>
<p><strong>IPMI/BMC based watchdog</strong>: aside from the iTCO watchdog, we have found the <a href="https://github.com/torvalds/linux/blob/afd2ff9b7e1b367172f18ba7f693dfb62bdcb2dc/drivers/char/ipmi/ipmi_watchdog.c">ipmi_watchdog</a> kernel module to be very reliable. Since it’s IPMI-based, you obviously need to have a BMC that supports it and make sure that it actually works. We have found some mainboards have faulty watchdogs, so beware of that and do extensive testing before using it.</p>
</li>
<li>
<p><strong>Software-based watchdog</strong>: the kernel provides a <a href="https://github.com/torvalds/linux/blob/afd2ff9b7e1b367172f18ba7f693dfb62bdcb2dc/drivers/watchdog/softdog.c">softdog</a> module, which can be used if no hardware supported watchdog is available. The softdog is obviously not as reliable because it relies on the kernel to not have crashed entirely. It’s better than nothing though, that’s for sure.</p>
</li>
</ul>
<p>The watchdog driver provides a <code>/dev/watchdog</code> device that needs to be written to every X seconds. If nothing writes to it until it times out, the system is hard reset. Usually some process holds a file handle open and regularly writes to it to pet the watchdog (there is a <a href="https://linux.die.net/man/8/watchdog">watchdog daemon</a> for this purpose). In our case, we simply want to ensure that a hard reset is triggered after X seconds, so all we do is pet the watchdog once (open file handle, and close it) before calling <code>reboot</code>.</p>
<p>The timeout is specified via the <code>heartbeat=..</code> (iTCO), <code>timeout=</code> (IPMI) or <code>soft_margin=..</code> (softdog) parameters when loading the module via <code>modprobe</code>. The <code>nowayout=1</code> parameter is necessary to ensure that the watchdog timer cannot be stopped by any means once it is started.</p>
<h2 id="4-reliably-rebooting-with-watchdogs">4. Reliably rebooting with watchdogs</h2>
<p>In short, here’s what we do in our BCDR devices to reliably reboot them as part of the OS upgrade:</p>
<ul>
<li>
<p>Load watchdog kernel module with a 10-minute timeout</p>
</li>
<li>
<p>Access the watchdog device to start the heartbeat timer</p>
</li>
<li>
<p><a href="https://utcc.utoronto.ca/~cks/space/blog/unix/TheLegendOfSync">Sync, sync, sync</a> the root partition (I know we don’t have to &hellip;)</p>
</li>
<li>
<p>Reboot</p>
</li>
</ul>
<p>If any of the steps after loading the watchdog module hangs or fails, the watchdog will trigger a hard-reset after 10 minutes. The actual code we use to execute these steps is part of our image-based upgrade tool <code>upgradectl</code>. Here’s a shorter version in Bash that is obviously not as well-tested, but it should work:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">WATCHDOGS</span><span class="o">=(</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;iTCO_wdt nowayout=1 heartbeat=600&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;ipmi_watchdog nowayout=1 action=reset timeout=600&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;softdog nowayout=1 soft_margin=600&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">BAD_BOARDS</span><span class="o">=(</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;A1SRi&#34;</span> <span class="s2">&#34;X9DRD-7LN4F&#34;</span> <span class="s2">&#34;X9DBL-3F/X9DBL-iF&#34;</span> <span class="s2">&#34;X9SRE/X9SRE-3F/X9SRi/X9SRi-3F&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;X10SLH-F/X10SLM+F&#34;</span> <span class="s2">&#34;X10SLH-F/X10SLM+-F&#34;</span> <span class="s2">&#34;X10SLM-F&#34;</span> <span class="s2">&#34;X11SSH-F&#34;</span> <span class="s2">&#34;DCS&#34;</span> <span class="s2">&#34;0PM2CW&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">board</span><span class="o">=</span><span class="k">$(</span>dmidecode -s baseboard-product-name <span class="p">|</span> head -n 1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> watchdog in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WATCHDOGS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$watchdog</span> <span class="o">=</span>~ <span class="s2">&#34;ipmi&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34; </span><span class="si">${</span><span class="nv">BAD_BOARDS</span><span class="p">[@]</span><span class="si">}</span><span class="s2"> &#34;</span> <span class="o">=</span>~ <span class="s2">&#34; </span><span class="si">${</span><span class="nv">board</span><span class="si">}</span><span class="s2"> &#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Skipping IPMI watchdog due to bad board </span><span class="nv">$board</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">continue</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Attempting to load watchdog </span><span class="nv">$watchdog</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  modprobe <span class="nv">$watchdog</span>
</span></span><span class="line"><span class="cl">  udevadm settle
</span></span><span class="line"><span class="cl">  sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -e /dev/watchdog <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Starting watchdog timer&#34;</span>   
</span></span><span class="line"><span class="cl">    touch /dev/watchdog
</span></span><span class="line"><span class="cl">    <span class="nb">break</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">rootpart</span><span class="o">=</span><span class="k">$(</span>awk -v <span class="nv">needle</span><span class="o">=</span><span class="s2">&#34;/&#34;</span> <span class="s1">&#39;$2==needle {print $1}&#39;</span> /proc/mounts <span class="p">|</span> grep -v ^rootfs<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Syncing root partition </span><span class="nv">$rootpart</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> i in <span class="m">1</span> <span class="m">2</span> 3<span class="p">;</span> <span class="k">do</span> timeout 60s blockdev --flushbufs <span class="nv">$rootpart</span><span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Waiting 25 seconds&#34;</span>
</span></span><span class="line"><span class="cl">sleep <span class="m">25</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Reboot&#34;</span>
</span></span><span class="line"><span class="cl">reboot
</span></span></code></pre></div><p>It’s not the prettiest script in the world, and yes, it’s got some magical <code>sleep</code>s in there that we would have rather avoided. But we have found that there are race conditions and slow background tasks that are difficult to pinpoint, so <code>sleep</code> is the next best thing. The <code>sleep</code> after loading the watchdog module is to ensure that the watchdog device has time to be put in place by the kernel. The 25 second <code>sleep</code> after the triple-sync is because we have found file corruption on the root disk in the field without it. It’s ugly, but it works.</p>
<p>You may have also noticed some special logic for the IPMI watchdog: as it turns out, quite a few mainboards have either bad or faulty IPMI watchdog implementations. We have found boards that would continuously reboot devices (devices were stuck in reboot loops, so much fun &hellip;), or watchdogs that simply didn’t behave like they were supposed to. For that reason we’ve chosen not to use the IPMI watchdog for these boards.</p>
<p>Another thing worth noting is that if you already have a watchdog daemon running, this script will likely need some changes: instead of loading the watchdog module, you may want to reconfigure it. For IPMI, you can do this by writing to <code>/sys/module/ipmi_watchdog/parameters</code>, and I’m sure the other modules have similar toggles.</p>
<h2 id="5-verdict">5. Verdict</h2>
<p>Rebooting reliably is more complicated than I would have ever imagined.</p>
<p>I don’t think the option we have chosen here is the best there is, but it has been working reliably for over 3 years. Our tens of thousands of appliances upgrade and reboot every 2-3 weeks, and the number of devices that don’t come back from a reboot is incredibly low &ndash; much lower than before we started using the watchdog-assisted reboots. Nowadays, if a device doesn’t come back from a reboot, it’s typically because of bad hardware (e.g., bad board or bad RAM), and not because it got stuck in the shutdown process somewhere.</p>
<p>So all in all, I’d say we’re pretty happy with this approach, and that extra special thrill that I was talking about when we reboot our devices has definitely gone away.</p>]]></content:encoded></item><item><title>Providing remote access to devices via SSH tunnels</title><link>https://heckel.io/blog/providing-remote-access-to-devices-via-ssh-tunnels/</link><pubDate>Tue, 19 Nov 2019 19:49:49 -0500</pubDate><guid>https://heckel.io/blog/providing-remote-access-to-devices-via-ssh-tunnels/</guid><description>At my work, the backup appliances are typically physically located inside the LAN of our end users &amp;ndash; much like other appliances such as routers, NAS devices or switches. Under normal circumstances that means that they are behind a NAT and are not reachable from the public Internet without a …</description><content:encoded><![CDATA[<p>At my work, the backup appliances are typically physically located inside the LAN of our end users &ndash; much like other appliances such as routers, NAS devices or switches. Under normal circumstances that means that they are behind a NAT and are not reachable from the public Internet without a VPN or other tunneling mechanisms. For my employer&rsquo;s customers, the Managed Service Provider (MSP), only being able to access their devices with direct physical access would be a major inconvenience.</p>
<p>Fortunately we’ve always provided a remote management feature called “Remote Web” for our customers: Remote Web lets them <strong>remotely access</strong> the device’s web interface as well as other services (mainly RDP, VNC, SSH), even when the device is behind a NAT.</p>
<p>Internally we call this feature <strong>RLY</strong> (pronounced: “relay”, like the owl, get it?). In this post, I’d like to talk about how we implemented the feature, what challenges we faced and what lessons we learned.</p>
<h2 id="why-access-devices-remotely">Why access devices remotely?</h2>
<p>Not having to drive to a site and physically manage a device is as important for our customers as it is for us:</p>
<p>When a customer calls with an issue on a device, our support team needs to be able to investigate what’s wrong. They need to be able to SSH into the device, monitor backups and change the configs. Being able to do that is vital for providing great customer support. Similarly, our customers want to be able to do the same if they get calls from their customers, or to simply set up and manage the appliance.</p>
<p>The primary use case for MSPs is accessing the device&rsquo;s web interface. However, other use cases for this feature are not as obvious: The same technology that provides a remote web interface in the browser is also used in many small features. For instance, when a restore VM needs to be accessed from outside the LAN, we provide a way to do that with the “Relay”:</p>
<p><img src="/uploads/2022/07/device_ui.png" alt=""></p>
<h2 id="reverse-tunnels-for-the-win">Reverse tunnels for the win!</h2>
<p>So what is this magical technology that fuels these wonderful features? I’m sorry to disappoint, but it’s just plain old reverse tunnels using SSH. In short, it’s this: <code>$ ssh -R</code></p>
<p>If you are now incredibly disappointed because you expected more, don’t be! We’ll get into interesting things about scaling this service to tens of thousands of devices.</p>
<p>In case you’re not familiar with what <code>ssh -R</code> does, let me briefly remind you:</p>
<p><code>ssh -R</code> creates a reverse tunnel by connecting to a relay server, binding a port on that server and forwarding all incoming traffic on that port to the initiating client (in our case: the appliance):</p>
<p><img src="/uploads/2022/07/reverse_tunnel.png" alt=""></p>
<p>It’s a port forwarding mechanism that typically breaks through firewalls and NATs because it is initiated by the client that provides the service. In the picture above, the service being provided listens on port 80/HTTP &ndash; the web interface on the appliance. Just like TLS/SSL wraps HTTP (= HTTPS), using SSH has a similar effect here: We’re encrypting the HTTP traffic over the secure SSH channel.</p>
<p>When instructed to forward port 80, the device creates a reverse tunnel using ssh -R as follows (step 1):</p>
<pre tabindex="0"><code>mydevice&gt; ssh \
   -R 12345:localhost:80 \
   -o ConnectTimeout=10 \
   -o IdentityFile=... \
   -o ExitOnForwardFailure=yes \
   -o ServerAliveInterval=15 \
   -o ServerAliveCountMax=2 \
   -vvv \
   -N \
   ... \
   user@rly.company.com
</code></pre><p>Assuming a valid private key is provided, the SSH daemon on the relay server will bind the port 12345 and forward all incoming traffic on from <code>rly.company.com:12345</code> to port 80 on the device. So a user could browse to <code>http://rly.company.com:12345</code>, and they would see this particular device’s web interface.</p>
<p>While that will work, there are two pretty obvious problems with this:</p>
<ul>
<li>First, the connection to the relay server is done over HTTP (as seen in the URL http://&hellip;), which these days is not acceptable under any circumstances.</li>
<li>And second, given that there are only 65k ports, it is incredibly easy to discover valid URLs by just counting up.</li>
</ul>
<p>Both of these can be easily fixed by putting a proxy server in between the SSH tunnel and the browser client. Equipped with a valid SSL certificate and a little bit of rewrite magic, it’s easy to turn <code>http://rly.company.com:12345</code> into <code>https://aabbccddeeff.rly.company.com</code>. We use Apache for this (shortened excerpt):</p>
<pre tabindex="0"><code>&lt;VirtualHost *:443&gt;
  # Set up SSL
  SSLEngine on
  SSLCertificateFile      &#34;/etc/apache2/ssl/rly.company.com.crt&#34;
  SSLCertificateKeyFile   &#34;/etc/apache2/ssl/rly.company.com.key&#34;
  SSLCertificateChainFile &#34;/etc/apache2/ssl/rly.company.com.chain.crt”

  # Set up rewriting &amp; proxying
  # e.g. translates &#34;aabbccddeeff.rly.company.com&#34; to &#34;localhost:12345&#34;
  RewriteEngine on
  RewriteMap relaymap &#34;prg:/usr/local/bin/relayrewrite&#34;

  &lt;Directory &#34;/var/www&#34;&gt;
    RewriteRule ^ ${relaymap:%{REMOTE_ADDR}:%{REQUEST_SCHEME}://%{SERVER_NAME}%{REQUEST_URI}} [L,P]
  &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre><p>To solve our first problem (HTTP), we listen on 443 and provide the proper wildcard certificates for our domain (here: <code>*.rly.company.com</code>). This will terminate SSL at the relay host, meaning all requests will be handled by Apache. That takes care of problem one.</p>
<p>In order to solve the second problem, we have to forward the request to the correct SSH connection (in our example the SSH daemon is bound to *:12345 on the relay host). To achieve that, we can use the <code>RewriteMap</code> stanza in Apache’s mod_rewrite module. Using the prg: option, each request can be rewritten dynamically by an external program (here: <code>/usr/local/bin/relayrewrite</code>). This program looks up the hostname, maps it to the correct SSH connection and finally rewrites the request. In the example above we are effectively proxying requests originally targeted to <code>aabbccddeeff.rly.company.com</code> to <code>127.0.0.1:12345</code>, thereby connecting the Apache request to the SSH tunnel.</p>
<p>That’s it. Simple right?</p>
<h2 id="horizontally-scaling-reverse-tunnels">Horizontally scaling reverse tunnels</h2>
<p>Believe it or not it actually is that simple. For a very long time, we’ve pretty much done this without a problem for thousands of connections. For the first few years of my company’s existence, we had exactly one server that handled all of our reverse tunnel relay traffic. Occasionally we added RAM or upgraded to a bigger box, but eventually, as it grew, we needed a more reliable balanced system without a single point of failure. And of course, as we grew internationally, our partners in Europe and Australia started complaining about abysmal speeds. No wonder since traffic was bounced around the globe twice for every packet.</p>
<p>On top of that, we were facing other issues with the existing system, such as slow connection times, limited connection lifecycle management (no repairs of broken connections) as well as odd outgoing port requirements for our devices.<br>
Requirements</p>
<p>Faced with these growing issues, we decided to take a step back and rethink how to scale the application from the ground up. We identified the following requirements:</p>
<ul>
<li>Horizontal scaling &amp; load balancing, no single point of failure</li>
<li>Allow maintenance of any component without customer impact</li>
<li>All outgoing connections must work on port 80/443</li>
<li>Connection time of under 5 seconds</li>
<li>Geographically distributed relay servers to address slow international connections</li>
<li>Auditable SSH sessions for support staff</li>
<li>Time-limited key-based authentication/authorization</li>
<li>Automated deployment via Puppet</li>
</ul>
<p>The most obvious (and arguably the hardest) problem to solve is the first one in the list, i.e. avoiding a single point of failure. If you’ve ever built a distributed system you know what I’m talking about. Keeping things in sync is hard if you don’t have a single point in the application to keep state. But as with many things in computing, the hardest problems are also the most interesting problems to solve.</p>
<p>After a little bit of weekend-tinkering followed by many weeks of hard work, we came up with version two of our remote management system which we called RLY.</p>
<h2 id="introducing-rly">Introducing RLY</h2>
<p>RLY is a highly scalable, distributed, high availability relay server application. While that certainly sounds impressive, at its core it still uses SSH tunnels to forward traffic between systems. The things we added along the way were merely to meet our scaling and deployment requirements.</p>
<p><img src="/uploads/2022/07/orly.png" alt=""></p>
<p>To accomplish that, RLY is split into four components that closely interact with one another:</p>
<p><img src="/uploads/2022/07/architecture2.png" alt=""></p>
<p>The RLY <strong>client</strong> is a command line client used by the behind-a-NAT device (here: our SIRIS/ALTO device). It provides a daemon waiting for incoming connections and a CLI to manually open time-limited tunnels if need be (<code>rly open|close|...</code>).</p>
<p>The <strong>tracker</strong> component is the brain of the system. It currently consists of a handful of physical servers in one of our US data centers. The trackers are the central hub to manage connections. They keep track of what connections are open, what ports to forward to and which relay servers to use. They also generate per-connection keypairs which are used to access a relay server. The component has a load-balanced API that is used by our portals and the jump hosts to open new connections as well as by the client devices to check for new connections. The tracker keeps state in a tiny Cassandra cluster.</p>
<p>The <strong>relay</strong> component is the one doing the heavy lifting. It consists of dozens of virtual machines in different data centers around the globe, close to where our partners devices are. Its main responsibility is to forward traffic from an end user to a behind-the-NAT device (via reverse tunnels as described above). Each relay host is isolated from its peers. The tracker is the only other component that talks to its API.</p>
<p>The <strong>jump</strong> component provides interactive SSH sessions to our devices for our support team and partners. Using one of these jump hosts, it is possible to SSH into a behind-the-NAT device by forwarding traffic to a local SSH daemon. The jump hosts are also located close to our partners and support teams to allow for a snappy SSH session. For audit purposes, all hosts monitor all interactive sessions via <code>script</code>.</p>
<h2 id="opening-a-connection">Opening a connection</h2>
<p>So how do the component work together? Let’s look at a simple example: Assume a partner wants to access their device’s web interface. Here’s a brief sequence diagram that shows the communication between the systems:</p>
<p><img src="/uploads/2022/07/sequence_diagram2.png" alt=""></p>
<ul>
<li>When the user clicks on the “connect” button in our partner portal, the portal backend requests a new reverse tunnel to port 80 from one of the trackers. The tracker creates a new connection ID and subdomain, generates an SSH keypair and intelligently picks the relay host. The selection is based on the GeoIP-proximity between client and relay host IP addresses and load of the relay servers. This evenly spreads the load while ensuring connection speed.</li>
<li>The tracker then tells the relay host to grant access to the generated keypair. The relay host saves this connection information, assigns a random relay port and waits for the incoming SSH connection.</li>
<li>The client regularly checks for changes using a heartbeat mechanism that will trigger a checkin call if there are any changes. Checkin returns the new connection information, including the relay host, port and the ephemeral private key needed for auth.</li>
<li>It then uses this information to open the reverse tunnel.</li>
</ul>
<p>Now that the connection is open, end users can access the local web interface on the client via the browser, e.g. at <code>ttps://xhaz32dhjmndf.rly.company.com</code>.</p>
<p>I&rsquo;d love to talk about how the dynamic DNS is implemented (hint: it’s similar to what I described <a href="/blog/your-own-dynamic-dns-server-powerdns-mysql/">in this post</a>, but with a Cassandra backend), but that would probably blow up this post even more. In short: The trackers serve as authoritative name servers for the <code>rly.company.com</code> zone and respond with a CNAME pointing to one of the relay hosts when asked. The relay host then simply applies the same rewrite magic I described above to map the hostname to the connection-specific local port</p>
<h2 id="scalability-and-resilience">Scalability and resilience</h2>
<p>Using this architecture, scaling the system is really easy, because it merely consists of adding tracker hosts or relay hosts and making them known in the configuration. Adding relay hosts in particular is incredibly simple, because it just means deploying a new VM and making it known to the trackers. Adding a tracker, on the other hand, is a little more manual work, because it means adding a node to Cassandra and making sure the consistency guarantees still work out as expected. Since the clients use SRV DNS records to discover trackers, they will eventually pick up the new trackers and add them to their round-robin list.</p>
<p>So it certainly looks like the system scales, but does it fall over if parts of it fail? Let’s discuss some scenarios to show what would happen if individual components/servers fail:</p>
<p><strong>Tracker host failure:</strong> If a tracker host goes down (or is taken down for maintenance), the Cassandra backend will continue to work, because it was configured with quorum read/write consistency, meaning as long as a majority of the cluster is alive, it will remain up and functioning. The RLY clients typically round-robin through the trackers so they’ll just mark it as failed and try again a couple of minutes later. So yeyy! We can survive a tracker going down. But what if more than half of them fail? As with any quorum based system, if more than half of the hosts go down, then we’re out of luck. In our case, that’d only happen if there is a row-wide outage in the data center. That’s a choice we’ve made consciously and a risk we’ve accepted.</p>
<p><strong>Relay host failure:</strong> If a relay host fails, all SSH tunnels to that host break. While that is unfortunate, it only affects a small subset of tunnels and is automatically repaired within a couple of seconds: If a client detects that a tunnel broke it tries to re-establish the connection a couple of times (to account for connection blips). If that doesn’t work, e.g. because the host is down for good, it will ask the tracker to repair the connection. The tracker will assign a new relay host and the connection is up again.</p>
<h2 id="lessons-learned">Lessons learned</h2>
<p>Developing RLY was a learning process. We kept the things that worked for us in the past, and we tried different ways to overcome the problems we had. RLY works and we’re proud of it, but we’ve learned many things along the way. In this section, we’d like to share some of our learnings.</p>
<p>Please take what we learned with a grain of salt: The fact that something didn’t work for us doesn’t mean it’s wrong or bad. It merely means that we either did it wrong, or that we used the wrong tool.</p>
<h2 id="distributed-datastores-across-continents">Distributed datastores across continents</h2>
<p>RLY started as a pet project of mine. It was meant to work on a single host as well as on many hosts as a distributed system. Once we started experimenting with distributed datastores as a backend for the RLY trackers, we learned quite a few things really quickly.</p>
<p>In the beginning I had this idea that I could split the datastore that manages connections across different data centers and different continents &ndash; you know, for maximum fault tolerance and resiliency. I started with <a href="https://github.com/coreos/etcd">etcd</a> as a backend store and three trackers in Frankfurt, Sydney and on the east coast of the US. I absolutely loved etcd. It was simple and exactly what I needed. On top of that it worked beautifully in my tests. Once I deployed it in my three tracker test setup though I realized pretty quickly that it wasn’t going to work. The latency was horrendous, and even with a lot of tweaking etcd couldn’t keep up with the amount of new/closing connections of RLY. I know what you are thinking: Why would anyone try etcd across continents? Honestly I don’t know what I was thinking. It’s pretty obvious now that that’s a dumb idea. Lesson learned!</p>
<p>After abandoning the idea of a datastore across data centers, we experimented with <a href="https://www.gluster.org/">GlusterFS</a>, <a href="https://redis.io/topics/cluster-tutorial">Redis Cluster</a> and a few others, but ultimately settled on <a href="http://cassandra.apache.org/">Cassandra</a>. In my test setup, Gluster was not dealing well with lots of small file changes. I bet there’s someone out there that knows how to tune it, but I gave up pretty quickly. Redis Cluster support was still pretty new at the time, and while it worked nicely I wasn’t happy using a brand new technology for a vital service like this.</p>
<p>We ended up with Cassandra in one data center and accepted the data center downtime risk. Cassandra works very well if configured properly. The topology matters a lot though. We decided to go with six tracker nodes and split them across three racks. Due to the low amount of data, our replication factor is six (all nodes have 100% of the data), our read consistency is three (read from three nodes) and our write consistency is quorum (write to four nodes). This gives us full read consistency (all trackers will respond with the same data). For anyone interested, there is a nice <a href="https://www.ecyrd.com/cassandracalculator/">Cassandra consistency calculator</a> that helps defining what you need.</p>
<p>With this consistency configuration and Cassandra’s rack-awareness, we can lose one rack (two servers) without the RLY system being affected. We tested this extensively before going live, and even in production when we moved all RLY trackers (two at a time) to a different data center.</p>
<h2 id="they-said-https-is-fast">They said HTTPS is fast</h2>
<p>People on the Internet™ say that adding TLS to HTTP (= HTTPS) doesn&rsquo;t make a big difference in performance. While that is probably very true for many scenarios, it wasn’t true for us: In our case, Apache cared very much about thousands of SSL handshakes per second, so much so in fact that all processors on all trackers were busy shaking hands with all of the RLY clients all the time.</p>
<p>The problem here of course lies in the way we designed the system: RLY has a heartbeat mechanism that calls out to the trackers every three seconds to see if there any new connections. While that works really well, it leads to pretty bad results if combined with Apache’s HTTPS implementation: The trackers pretty much died. The load was insane. Load averages of &gt;1000 were common. All that TLS handshaking killed the tracker servers. After switching heartbeat to HTTP (don&rsquo;t freak out, it just returns a timestamp!), the load was much better.</p>
<p>Obviously the heartbeat/checkin architecture is a little off and we could/should have used server side events (SSE) or something similar. We’d probably explore this option in more detail if we were to design the system again. However, given that we have other products for which the design has proven to work really well for years, we stuck with it.</p>
<h2 id="bcrypt-with-a-cost-of-10">bcrypt with a cost of 10</h2>
<p>After solving the HTTPS handshake problem we still had load problems on the trackers, which turned out to be authentication related: To authenticate with the RLY tracker, the client uses a per-client secret key. In our database (think: on the trackers), we store a <a href="https://en.wikipedia.org/wiki/Bcrypt">bcrypt hash</a> of that secret key with the cost of 10 (i.e. 2^10 = 1024 rounds). bcrypt is of course designed to be slow and consume lots of CPU on purpose. After lots of analysis, we found that that was the cause for the load: The trackers were consuming a lot of CPU because they were constantly verifying bcrypt hashes.</p>
<p>Of course it took a bit of time to actually determine that as the root cause, but finding a solution was fairly straight forward: Instead of constantly verifying the bcrypt-2^10-hash, we would only verify it once every few hours, and generate a new bcrypt-2^4-hash upon first auth to use for subsequent requests. We store that weaker hash only in memory of course, but verifying that is much faster.</p>
<p>On my laptop, for instance, verifying 100 bcrypt hashes with the cost of 10 takes ~6 seconds, whereas a cost 4 comparison of 100 hashes takes only .01 seconds.</p>
<p>After that change the load was much much better.</p>
<h2 id="limits-limits-limits">Limits, Limits, Limits!</h2>
<p>Another thing we noticed is that high traffic servers like the trackers need a few Linux kernel parameters set to function properly. This may be obvious to some, but it&rsquo;s not obvious if you&rsquo;ve never done this before (like me). Luckily we have some smart people in our systems engineering team that know exactly how to do that.</p>
<p>The default limits on a Linux system are too restrictive for high volume servers to perform well under load. Most notably the number of open files (<code>nofile</code>), the number of running processes (<code>nproc</code>) and the maximum number of queued connections (<code>net.core.somaxconn</code>) need to be increased. This is mostly so nginx and Cassandra are allowed to accept more connections.</p>
<p>Here’s our <code>limits.conf</code> configuration. It increases the hard and soft limits for the number of open files:</p>
<pre tabindex="0"><code># &lt;domain&gt; &lt;type&gt; &lt;item&gt; &lt;value&gt;
* soft nofile 1024000
* hard nofile 1024000
* soft nproc 10240
* hard nproc 10240
root soft nproc unlimited
cassandra - memlock unlimited
cassandra - nofile 100000
cassandra - nproc 32768
cassandra - as unlimited
</code></pre><p>And here’s how we increase the number of allowed queued connections in <code>sysctl.conf</code>:</p>
<pre tabindex="0"><code>net.core.somaxconn=10000
</code></pre><p>There are much more details on how to vertically scale a Linux server for high load. Here’s a <a href="https://medium.com/@pawilon/tuning-your-linux-kernel-and-haproxy-instance-for-high-loads-1a2105ea553e">great article</a> explaining the details.</p>
<h2 id="versioning-and-logging-makes-things-easy">Versioning and logging makes things easy</h2>
<p>Something we actually did right from the beginning is to add a version string to everything. Every API call, every serialized object, and even every running process has a the RLY version number attached to it. That means we can easily identify old clients and always be backwards compatible. So far we only had to use the RLY version number once to change the <code>relayPort</code> field type to an array (to support multiple ports) and ensure that old clients don&rsquo;t break. It worked very nicely. Versioning things FTW!</p>
<p>We also log pretty aggressively in RLY, which helps when debugging. All components can enable <code>LogLevel DEBUG</code>, which then logs everything, even the output of <code>ssh</code> and <code>sshd</code>. Log all the things, people! It helps!</p>
<h2 id="openssh-does-not-scale-well">OpenSSH does not scale well</h2>
<p>Believe it or not, OpenSSH’s <code>sshd</code> is not meant to handle thousands or even tens of thousands of connections/tunnels. We realized this only about 1.5 years after the RLY system had been live when we were thinking about persistent connections instead of on demand tunnels.</p>
<p>In my tests, on a VM with 16 GB of RAM and 4 CPUs, OpenSSH was really hammering the system when I opened 1,100 connections to it. It consumed about half of the RAM and the CPUs were pegged:</p>
<p><img src="/uploads/2022/07/openssh2.png" alt=""></p>
<p>After researching alternatives to OpenSSH with the same feature set, we landed on using the Golang built-in <a href="https://godoc.org/golang.org/x/crypto/ssh">crypto/ssh</a> package with great success. The same VM was easily handling 16,000 connections with the CPU almost idle and 1.1 GB of RAM:</p>
<p><img src="/uploads/2022/07/gossh.png" alt=""></p>
<p>It would most likely be able to handle a lot more connections, but my laptop wasn’t able to handle more outgoing SSH tunnels so I stopped and called this “good enough”. Pretty cool right? Instead of adding dozens of new VMs to handle the traffic, we simply used a more appropriate tool.</p>
<h2 id="monitor-and-automated-end-to-end-testing-pays-off">Monitor and automated end-to-end testing pays off!</h2>
<p>With dozens of servers around the globe, it&rsquo;s important to be able to keep track of all of them in a centralized way and automatically end-to-end test the system regularly enough to detect potential problems before customers do. We put a lot of work into testing various parts of the system (opening tunnels, using the jump servers, …) and mapping the overall health of the system to a single number (0 = error, 1 = warning, 2 = ok).</p>
<p><img src="/uploads/2022/07/datadog2.png" alt=""></p>
<p>We then use this number to trigger alerts for our operations group and for visualizations in our RLY dashboard. Warnings include things that can be dealt with in the morning (relay host down, tracker down, …) and errors must be dealt with as soon as possible, because they have customer impact. We have caught quite a few almost-outages like this before anyone noticed!</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>In this post I talked about how we use SSH reverse tunnels to access behind-the-NAT devices, and how we created a horizontally scalable application to support the fleet of all our backup appliances. I introduced our RLY application and its architecture with all its components. And finally I briefly talked about a few of our challenges and the lessons we learned.</p>
<p>RLY has been working really well for over two years now and for the most part, all users (internal and external) have been happy. We’ve had no significant outages and we’ve even moved some of the relay hosts and all trackers to a different data center without downtime. We have added more device types over time, starting from the BMC/IPMI in our backup appliances, to our DNA devices and most recently we’ve started work on integrating our other networking appliances.</p>
<p>Naturally, as with most systems, there are things we’d do differently now: We probably wouldn’t do a polling based system to check for updates on the client again (heartbeat/checkin). We’d also probably not use SSH for tunneling as something more lightweight would have also done the trick.</p>
<p>Overall, it’s not really worth changing any of these, because the system works wonderfully, scales great, and is pretty low maintenance. Thanks to all of the smart engineers that made RLY happen. It&rsquo;s a pleasure to work with you every day.</p>]]></content:encoded></item><item><title>Image based upgrades: Upgrading software and OS of 80k servers every two weeks</title><link>https://heckel.io/blog/image-based-upgrades-upgrading-software-and-os-of-80k-servers-every-two-weeks/</link><pubDate>Wed, 18 Sep 2019 18:11:29 -0400</pubDate><guid>https://heckel.io/blog/image-based-upgrades-upgrading-software-and-os-of-80k-servers-every-two-weeks/</guid><description>Anyone that’s ever managed a few dozen or hundreds of physical servers knows how hard it can become to keep all of them up-to-date with security updates, or in general to keep them in sync with their configuration and state. Sysadmins typically solve this problem with Puppet, or Salt or by putting …</description><content:encoded><![CDATA[<p>Anyone that’s ever managed a few dozen or hundreds of physical servers knows how hard it can become to keep all of them up-to-date with security updates, or in general to keep them in sync with their configuration and state. Sysadmins typically solve this problem with Puppet, or Salt or by putting applications in a container. While those are great options if you control your environment, they are less applicable when you think about other cases (such as appliance/server that doesn’t reside in your infrastructure). On top of that, replacing the kernel, major distribution upgrades or any larger upgrades that require a reboot are not covered by these solutions.</p>
<p>Being faced with this problem for work, we started exploring alternative options and came up with something that has worked reliably for almost two years for a fleet of now over 80,000 devices. In this blog post, I&rsquo;d like to talk about how we solved this problem using images, loop devices and lots of Grub-magic. If you’d like to know more, keep reading.</p>
<h2 id="1-debian-packages-all-the-way">1. Debian packages all the way?</h2>
<p>Our BCDR appliance has always been Ubuntu-based, so the natural way to update our software was through Debian packages. That’s what we did for a long time: every two weeks, we’d build releases for Ubuntu 10.04/12.04 (yes, we know, read on!) and after thoroughly testing them, we’d ship those to the fleet.</p>
<p>That worked for a long time, but it had some significant drawbacks:</p>
<ul>
<li><strong>Updating third party dependencies:</strong> Having a few Debian packages implies that you are really just taking responsibility for your own software and not for other software on the appliance. Managing updates for Apache, Samba, libc or even PHP is non-trivial when all you have available is your own something.deb. Given that my employer sells the entire appliance, we of course have to take responsibility for the entire stack, especially when you think about security patches for third party software.</li>
<li><strong>Service restarts / reboots:</strong> The dependency problem becomes particularly tricky with major updates that require service restarts or even reboots. Of course, Debian packages are supposed to handle service restarts themselves, but in reality not all of them do that gracefully. And as soon as you get into reboot-territory (for things like kernel upgrades), you need to make sure that you’re not interrupting important device tasks (backups, virtualizations, &hellip;), and you have to make sure that your device actually comes back up (not as easy as you think, see below!).</li>
<li><strong>Distribution upgrades:</strong> Now when it really becomes tricky is when the entire OS version needs to be moved forward. Upgrading from Ubuntu 10.04 to 16.04 using just <code>apt-get dist-upgrade</code> and the occasional <code>reboot</code>, for instance, will take incredibly long and many times will straight up not work (if you’ve ever used <code>apt-get dist-upgrade</code>, you’ll understand).</li>
<li><strong>Thousands of versions and states:</strong> With the Debian upgrade model, devices really behaved like normal computers: Right after they were freshly imaged and shipped out, everything was great and new, but the older they got the more the OS deteriorated &ndash; thereby leaving devices in vastly different states. Just to give you an idea: We had 40 different versions of VirtualBox on our devices (before we switched to KVM), 25 different ZFS versions and over 80 different Linux kernels!</li>
</ul>
<p>That is by no means a complete list of problems, but I don’t want to bore you with even more problems. Let&rsquo;s get to the fun part: How to solve it!</p>
<h2 id="2-images-not-packages">2. Images, not packages!</h2>
<p>With all these problems it’s pretty obvious that we needed something better. We needed to manage the state and configuration for our devices. It was important to minimize the number of different device configurations/packages/versions in the fleet, and to ensure that with every upgrade, we’d be able to upgrade the entire stack &ndash; be it our own software, third party software, or even system libraries such as libc or the kernel.</p>
<h3 id="21-requirements">2.1. Requirements</h3>
<p>We started defining our requirements for a solution, and they were actually pretty short:</p>
<ul>
<li>All devices follow the same upgrade path. There is only one upgrade path.</li>
<li>All devices can be upgraded this way (even older ones with small OS drives)</li>
<li>Switching from one version to another is atomic (or as atomic as possible).</li>
<li>Rolling back to a previous version is possible (if the upgrade fails).</li>
</ul>
<p>The strongest implication of these requirements is of course that we were going to drop package based upgrades, and &ndash; if you can read between the lines &ndash; that rebooting an appliance as part of the upgrade process is acceptable.</p>
<p>Both of these are a pretty big deal. Big decisions were being made!</p>
<h3 id="22-what-are-images-anyway">2.2. What are images anyway?</h3>
<p>To be able to reduce the number of configurations, we decided that we wanted to stop treating our software and all its dependencies as separate pieces. Instead, we wanted to combine them all into a single deliverable &ndash; an image.</p>
<p>So what’s an image? An image (in our world) is an <a href="https://en.wikipedia.org/wiki/Ext4">ext4 file system</a> that contains everything needed to boot and run a BCDR appliance. That includes:</p>
<ul>
<li>The Ubuntu base operating system (kernel, system libraries, ..)</li>
<li>Required third party tools and libraries (Apache, KVM, ZFS, &hellip;)</li>
<li>The device software (dubbed IRIS by our marketing team)</li>
</ul>
<p>Here’s a nice picture showing an example:</p>
<p><img src="/uploads/2019/08/image1.png" alt=""></p>
<p>We were excited about this idea because with images there is exactly one number, the image version (415 in the picture above), that defines the version of every piece of software that is installed. No more testing our software with multiple ZFS versions, no more hoping that it’ll work with all KVM versions. Yey!</p>
<h2 id="3-image-based-upgrades-ibu">3. Image based upgrades (&ldquo;IBU&rdquo;)</h2>
<p>After making all of these important decisions, we still needed to find a way to build, distribute and boot these images on our devices. So let’s dive into these topics.</p>
<h3 id="31-building-an-image">3.1. Building an image</h3>
<p>We typically build an image automatically every time we tag a new release (or release candidate): Every time we push a tag in Git, a CI worker starts the image build process. The build process itself is pretty interesting, but a little out of scope for this post. But I don’t want to leave you hanging so here is the short version:</p>
<p>We first build Debian packages of our own software and publish that to a Debian repository. We snapshot the Debian repository using <a href="https://www.aptly.info/">aptly</a>, and we do the same regularly with an upstream Ubuntu repository (“Upstream packages”). We then we use <a href="https://wiki.debian.org/Debootstrap">debootstrap</a> to create a base Ubuntu system and install our software with all its dependencies in a <a href="https://en.wikipedia.org/wiki/Chroot">chroot</a>. Once that’s done, we tar it up and rsync it to our image server. On there, we extract the tarball and rsync it over the last image, which resides in a <a href="https://en.wikipedia.org/wiki/ZFS#Data_structures:_Pools,_datasets_and_volumes">ZFS volume (zvol)</a> formatted as ext4. After we zero out unused ext4 blocks, we finally snapshot the zvol containing the file system.</p>
<p>Here’s what that looks like on the image server:</p>
<p><img src="/uploads/2019/08/image5.png" alt=""></p>
<p>That zvol now contains the ext4 file system of our BCDR appliance. That is the image. That’s our single deliverable. It can be tested as one unit and once it passes QA it can be distributed to our customers&rsquo; BCDR devices.</p>
<h3 id="32-distributing-the-image">3.2. Distributing the image</h3>
<p>Great, now we’ve successfully built an image. How do we get it from our data centers to the &gt;80k devices? Pretty simple: We use <a href="https://docs.oracle.com/cd/E19253-01/819-5461/gbchx/index.html">ZFS send/recv</a>!</p>
<p>Our devices all have ZFS pools anyway to store their image based backups, and we already heavily use ZFS send/recv to offsite their backups. So using this technology in reverse was a no-brainer.</p>
<p>So here’s what we do: When it’s time to upgrade, we instruct a device to download a ZFS sendfile diff over HTTPS (we&rsquo;d use ZFS send/recv over SSH directly but that cannot be cached unfortunately):</p>
<p><img src="/uploads/2019/08/image3-1024x137.png" alt=""></p>
<p>As you can see in the screenshot, we typically don’t have to download the full image, because the devices have upgraded before and already have a version of the image in their local pool. That’s pretty sweet: Using this technique, we can do <strong>differential operating system upgrades</strong> &ndash; meaning devices only have to download the changed blocks of the image.</p>
<p>That’s a win-win, because it means we don’t overuse our customers’ networks and we don’t have to provide lots of bandwidth for upgrades from our data centers.</p>
<p>Once the image is downloaded, we import it to the local ZFS pool. This is particularly important for the next upgrade (to ensure we can download diffs):</p>
<p><img src="/uploads/2019/08/image4.png" alt=""></p>
<h3 id="33-booting-the-image">3.3. Booting the image</h3>
<p>Now that we have the image, how do we boot into that file system? And if every image version we build is a completely fresh OS, how do we go from one version to the next?</p>
<h4 id="331-zfs-on-root-ab-partitions-and-ab-folders">3.3.1. ZFS-on-root, A/B partitions and A/B folders</h4>
<p>Not surprisingly, there is more than one answer to these questions. There are many ways to use the image to produce a bootable system, so we had to experiment a little bit to find the best one.</p>
<p>Given that they are quite interesting, I thought I’d briefly mention them and why we didn’t end up doing them:</p>
<ul>
<li><strong><a href="https://github.com/zfsonlinux/zfs/wiki/Ubuntu-18.04-Root-on-ZFS">ZFS-on-root</a> with A/B datasets:</strong> We use ZFS pretty heavily for our image based backups, so we naturally thought we could also use it as the root file system of our appliance. The idea here was to distribute the image for the BCDR appliance as a ZFS dataset (not as zvol as described above), clone it and then directly boot into the ZFS clone. Since Grub supports reading ZFS in its newer versions and there’s also a ZFS initramfs module, ZFS-on-root is absolutely possible. To upgrade from one image to the next (i.e. one ZFS dataset to the next), we’d simply update the <a href="https://www.gnu.org/software/grub/">Grub</a> config and reboot. It worked beautifully, but since booting into ZFS is pretty new, we didn’t feel like it was mature enough for our main product. Pass.</li>
<li><strong>Simple A/B partitions:</strong> Quite a few appliances and phones simply have two partitions, one with the current system and one with the next. This idea was straight forward: Download the new image, rsync it to the inactive partition, update Grub, and then reboot. The unfortunate thing here was that not all of our devices have spare space for an extra partition (not without repartitioning at least). We experimented (and succeeded) with splitting the active root partition from inside initramfs during the first reboot (cool, right?), but again, given that this is our main product, it felt like an insane risk to take. Pass.</li>
<li><strong>Boot into A/B directories:</strong> Given the lack of a spare partition, we experimented with having two copies of the image on the root partition in two different folders (e.g. <code>/images/412</code> and <code>/images/415</code>), and then modifying initramfs to boot into <code>/images/415</code> instead of <code>/</code>. Believe it or not, while it sounds super crazy, it actually worked and was super easy by messing with initramfs a little: <code>mount --bind /images/415 /root</code> did the trick. Everything booted just fine. However, lots of Linux tools (df, mount, …) got really confused by the fact that the root is not / &ndash; so we decided to pass on that as well.</li>
</ul>
<h4 id="322-loops-all-the-way">3.2.2. Loops all the way!</h4>
<p>After trying so many different ways of booting into an image, what we ended up doing almost sounds a bit boring. <a href="http://boringtechnology.club/">But boring is good</a>, right?</p>
<p>The simplest and most reliable way we found to boot into an image was to leverage Grub’s <a href="https://www.gnu.org/software/grub/manual/grub/html_node/Loopback-booting.html">loopback booting</a> mechanism, combined with the loop support in <a href="http://manpages.ubuntu.com/manpages/xenial/man8/initramfs-tools.8.html">initramfs</a> (see <code>loop=...</code> parameter):</p>
<p>Grub, as you most certainly know, is a boot loader. Its responsibility is to load the initial RAM disk and the kernel. For this purpose, Grub has built-in read support for many file systems and as we will see shortly also for file systems within file systems via the <a href="https://www.gnu.org/software/grub/manual/grub/html_node/loopback.html#loopback">loopback</a> command. The <code>loopback</code> command will find an image file on the root partition and loop it, so that the <code>linux</code> and <code>initrd</code> command can be used like normal to find the kernel and RAM disk. Here’s one of the menu entries we generate (via a hook in <code>/etc/grub.d</code>) for our device’s grub.cfg files:</p>
<pre tabindex="0"><code>menuentry &#39;Custom OS (v415.0)&#39; {
  search --set=root --no-floppy --fs-uuid 8c43bf01-046c-401c-8cb8-97cb658ef698
  loopback loop /images/415.0.img
  linux (loop)/vmlinuz root=UUID=8c43bf01-046c-401c-8cb8-97cb658ef698 rw loop=/images/415.0.img ...
  initrd (loop)/initrd.img
}
</code></pre><p>In this example, Grub will first find the root partition (just like on a normal Ubuntu installation) by its UUID via <code>search</code>. It will then discover the image file <code>/images/415.0.img</code> on that root partition and finally find the kernel (<code>(loop)/vmlinuz</code>) and RAM disk (<code>(loop)/initrd.img</code>) inside the image.</p>
<p>It’s incredibly simple, but really also incredibly cool: The fact that a boot loader can do all that still amazes me.</p>
<p>Once Grub found the kernel and the initial RAM disk, it loads the RAM disk into memory (shocker!), which is then responsible for mounting the root file system before handing control over to the init process.</p>
<p>In Ubuntu, the <code>initramfs-tools</code> package provides utilities to create and modify the initial RAM disk. Luckily it already supports loopback booting, so typically there isn’t really much more to do besides passing a <code>loop=</code> parameter in the kernel line. If that is set, initramfs will loop-mount the root file system to the image via <code>mount -o loop</code> (see <a href="https://git.launchpad.net/ubuntu/+source/initramfs-tools/tree/scripts/local?h=applied/ubuntu/xenial-updates&amp;id=1b769f37d3458824e3f898f4628477a64a6cc0ef#n206">source code</a>). Given that there is a pretty scary FIXME message in the code (<code># FIXME This has no error checking</code>), we thought it’d be good to make that a little more resilient by adding error handling and <code>fsck</code>ing to it. In most cases, it should boot without messing with initramfs though.</p>
<p>That’s really it. A lot of words for such a simple solution.</p>
<p>Here’s what it looks like in practice. As you can see, this device’s root file system lives on <code>/dev/loop0</code>. This loop device was set up in initramfs and it points to the image file:</p>
<p><img src="/uploads/2019/08/image6.png" alt=""></p>
<p>In this case, the image resides on the root partition (e.g. <code>/dev/sda1</code>) in <code>/images/412.0.img</code>. Note if an empty <code>/host</code> folder is present inside the image, initramfs will mount the root partition there:</p>
<p><img src="/uploads/2019/08/image2.png" alt=""></p>
<h3 id="34-upgrading-between-images">3.4. Upgrading between images</h3>
<p>So now we can build, distribute and boot images. If we combine these things, you can already see that upgrading from one image to the next is not hard:</p>
<ol>
<li>Clean up old images, download new image, import into pool, export to image file</li>
<li>Migrate configuration from current image to next image</li>
<li>Update Grub to point to new image</li>
<li>Reboot</li>
</ol>
<p>And that’s pretty much what we do. We wrote a tool called <code>upgradectl</code> for that:</p>
<p><img src="/uploads/2019/08/image7.png" alt=""></p>
<p><code>upgradectl</code> is typically triggered remotely by our checkin process: It prepares the upgrade in the background during normal device operation by downloading and exporting the image (step 1). And when it’s finally time to upgrade (usually at night when the device is idle), the actual upgrade process will be blazingly fast, because it only has to migrate configuration, update Grub and reboot (steps 2-4). Typically, the downtime for a device during an upgrade is about 5-10 minutes, depending on how long it takes to reboot (larger devices will take longer due to <a href="https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface">IPMI/BMC</a> initialization).</p>
<p>Now of course there are tons and tons of gotchas and edge cases to consider: It sounds easy, but it’s really quite hard to get right &ndash; especially considering that our 80k existing appliances had been in production for up to 7 years.</p>
<p>That made for some fun challenges: We upgraded thousands of devices from Ubuntu 12.04 (and even 10.04) directly to Ubuntu 16.04. We implemented logic to deal with falling back to old images in case the upgrade failed for some reason or the next. We dealt with full OS drives, faulty hardware (disks, IPMI, RAM, …), RAIDed OS drives and Grub’s inability to write to them, ZFS pool corruption, hung Linux processes (good old <code>D</code> state), stuck reboots, and many many more.</p>
<p>But guess what: It was worth it. It felt like spring cleaning after a winter that lasted 7 years. We brought devices into the best shape of their lives. And we’re still doing it. Every 2 weeks!</p>
<h2 id="4-summary">4. Summary</h2>
<p>In this post, I showed you how we changed the deployment process for our BCDR appliance from Debian packages to images. I talked about how we build images, how we distribute them and how we use Grub’s loopback mechanism to boot them.</p>
<p>While I touched all the pieces to perform image based upgrades, the most exciting thing about all of it is this: Using this mechanism, we can move between kernels and even major operating system versions. We’re effectively booting into a completely fresh, new operating system every time we ship an upgrade. That means that the system won’t deteriorate over time, manual changes will be wiped out and that we could technically switch Linux distros in a heartbeat if we wanted to.</p>
<p>And all of this is done in the background, with no interaction of our customers, completely invisible to their eyes: 80k operating system upgrades every two weeks, how cool is that?</p>]]></content:encoded></item><item><title>Deduplicating NTFS file systems (fsdup)</title><link>https://heckel.io/blog/deduplicating-ntfs-file-systems-fsdup/</link><pubDate>Mon, 22 Jul 2019 20:14:16 -0400</pubDate><guid>https://heckel.io/blog/deduplicating-ntfs-file-systems-fsdup/</guid><description>At my work, we store hundreds of thousands of block-level backups for our customers. Since our customer base is mostly Windows focused, most of these backups are copies of NTFS file systems. As of today, we&amp;rsquo;re not performing any data deduplication on these backups, which is pretty crazy …</description><content:encoded><![CDATA[<p>At my work, we store hundreds of thousands of block-level backups for our customers. Since our customer base is mostly Windows focused, most of these backups are copies of NTFS file systems. As of today, we&rsquo;re not performing any data deduplication on these backups, which is pretty crazy considering that how well you&rsquo;d think a Windows OS will probably dedup.</p>
<p>So I started on a journey to attempt to dedup NTFS. This blog post briefly describes my journey and thoughts, but also introduces a tool called <code>fsdup</code> I developed as part of a 3 week proof-of-concept. Please note that while the tool works, it&rsquo;s highly experimental and should not be used in production!</p>
<h2 id="0-link-to-the-fsdup-tool">0. Link to the <code>fsdup</code> tool</h2>
<p>If you&rsquo;re impatient, here&rsquo;s a link to the <a href="https://github.com/binwiederhier/fsdup">fsdup</a> tool I created as part of this proof-of-concept.</p>
<h2 id="1-thoughts-on-ntfs-deduping">1. Thoughts on NTFS deduping</h2>
<h3 id="11-the-obvious-approach">1.1. The obvious approach</h3>
<p>The obvious approach to dedup NTFS would be to simply dedup blocks (= clusters), so typically that would mean deduping with a fixed offset of 4096 bytes (4K cluster size). While that will lead to the best dedup results, it also produces <em>a lot</em> of metadata and the <a href="/blog/minimizing-remote-storage-usage-and-synchronization-time-using-deduplication-and-multichunking-syncany-as-an-example/#Metadata-Overhead">metadata-to-data ratio (metadata overhead)</a> is pretty bad. In addition to that, the more chunks you produce the higher the chance of hash collisions (unless you increase the hash size, which in turn increases the metadata overhead &hellip;). On top of that, many object stores or file systems are not really good with small files like that, so a higher chunk size is desirable.</p>
<p>That said, given that we&rsquo;re talking about hundreds of thousands (maybe even millions? I should really check &hellip;) of NTFS file systems, it might not matter if the objects are 4K or 128K in size.</p>
<h3 id="12-what-i-did-instead">1.2. What I did instead</h3>
<p>Since this was a proof-of-concept, and I wanted to learn about NTFS and write some fun Go code, I chose to go a different route:</p>
<p>Instead of <a href="/blog/minimizing-remote-storage-usage-and-synchronization-time-using-deduplication-and-multichunking-syncany-as-an-example/#Fixed-Size-Chunking">fixed offset chunking</a> with 4K chunks, I decided to parse the NTFS filesystem using a combination of <a href="/blog/minimizing-remote-storage-usage-and-synchronization-time-using-deduplication-and-multichunking-syncany-as-an-example/#File-Type-Aware-Chunking">content aware chunking</a> (for the file system itself) and fixed offset chunking (for the files within the file system).</p>
<p>Here&rsquo;s my approach:</p>
<ul>
<li>
<p><strong>Index files within NTFS:</strong> Read all entries in the <a href="https://flatcap.org/linux-ntfs/ntfs/files/mft.html">master file table</a> (<code>$MFT</code>) that are larger than X (e.g. 128K, 2M, &hellip;). For each entry, read all <a href="https://flatcap.org/linux-ntfs/ntfs/concepts/data_runs.html">data runs</a> (extents, fragments) sequentially (i.e. re-assmble the file). Break the re-assembled file into max. 32M sized chunks and then hash each chunk using blake2b. For each chunk checksum, compare if it&rsquo;s in the global chunk index and only store new chunks.</p>
</li>
<li>
<p><strong>Index empty space</strong>: Read the <a href="https://flatcap.org/linux-ntfs/ntfs/files/bitmap.html">NTFS bitmap</a> (<code>$Bitmap</code>), find unused sections and mark them as sparse. <em>This step is optional. If it is used, the re-assembled file system will likely not be identical as the original one, because unused space is rarely ever filled with zeros. However, the space savings due to this step are enormous and the user won&rsquo;t care.</em></p>
</li>
<li>
<p><strong>Find gaps</strong>: All sections of the file system that are not covered by step 1 or 2 are stored in gap chunks. This typically includes all files smaller than X (see above).</p>
</li>
</ul>
<p>The algorithm will create a manifest file for every NTFS file system which can later be used to re-create it from scratch, either by exporting it from the global chunk store, or by mapping it to a drive or mountpoint.</p>
<p>Here&rsquo;s a (simplified) diagram showing (mostly) step one:</p>
<p><img src="/uploads/2019/07/dedup-image-1024x792.png" alt=""></p>
<h2 id="2-fsdup-a-tool-to-dedup-ntfs">2. fsdup: A tool to dedup NTFS</h2>
<p>So to prove that the method above works, I created a small tool called <a href="https://github.com/binwiederhier/fsdup">fsdup</a>. It implements the algorithm above, and also provides ways to re-assemble deduped file systems or expose them as mapped drives. Here&rsquo;s the current feature set:</p>
<ul>
<li><code>fsdup index</code>: Index/dedup NTFS file systems into a chunk index (stored locally or in Ceph). This creates a manifest file to describe the chunks that make up a file system.</li>
<li><code>fsdup export</code>: Using a manifest and a chunk index, this command re-assembles a file system into an image file. This is the reverse of the index command.</li>
<li><code>fsdup map</code>: Using a manifest and a chunk index, this command exposes a block device of the block device that was indexed using the index command. This command is super useful for booting VMs directly of off a chunk store.</li>
</ul>
<h3 id="21-indexing">2.1. Indexing</h3>
<p>First, let&rsquo;s dedup the NTFS file system by breaking it into chunks using the approach described above: The <code>fsdup index</code> command takes two arguments, an input file system (block device or file), as well as an output file (a manifest that describes what chunks the input file consists of). In this example, we assume that the NTFS file system is in a file called Cdrive.img.</p>
<pre tabindex="0"><code># Deduplicates file, creates chunk index, generates Cdrive.manifest.
# By default, this will index files &gt;128k and use a local folder &#34;index&#34; as chunk store.
$ fsdup index Cdrive.img Cdrive.manifest
Detected MBR disk
NTFS partition of size 249.0 MB found at offset 1048576
Indexed 52.2 MB in 9 file(s) (147 skipped)                        
Indexed 195.6 MB of unused space                             
Indexed 1.2 MB of gaps (21 chunk(s))             
NTFS partition successfully indexed  
Indexed 1.0 MB of gaps (1 chunk(s))           
MBR disk fully indexed

# Alternatively, Ceph can be used as chunk store, and the default parameters can be overriden
$ fsdup index -minsize 2M -store &#39;ceph:?pool=fsdup2m&#39; Cdrive.img Cdrive.manifest
...

# Prints the manifest
$ fsdup print Cdrive.manifest
diskoff 000000 - 196608 len 196608  chunk a4bfc70 chunkoff 0 - 196608
diskoff 196608 - 288832 len 9092224 sparse
diskoff 288832 - 759872 len 471040  chunk 8976c52 chunkoff 0 - 471040
diskoff 759872 - 981256 len 28282   chunk beef1da chunkoff 28282 - 61040
...
</code></pre><p>The main deduplication algorithm described above is codified in the <a href="https://github.com/binwiederhier/fsdup/blob/d43469e2afb2854d68e2da83b4078b8edd0702cc/chunker_ntfs.go">chunker_ntfs.go</a> file. The top of the file also describes the above mentioned approach. Feel free to poke around and leave comments.</p>
<p><strong>Please be reminded that this is a proof-of-concept.</strong> The algorithm is not optimized at all. In particular, it does not do a good job reading the file system sequentially (see section &ldquo;Thoughts&rdquo; below), meaning that it&rsquo;s particularly slow on spinning disks. It wouldn&rsquo;t be hard to rewrite it to work (more) sequentially though.</p>
<h3 id="22-exporting-and-mapping">2.2. Exporting and mapping</h3>
<p>Once indexed, the file system can be exported from the chunk store, or mapped to a local mountpoint using the manifest we created with the index command. These operations can be done with the <code>fsdup export</code> and <code>fsdup map</code> commands.</p>
<pre tabindex="0"><code># Creates an image file from chunk index using the manifest
$ fsdup export Cdrive.manifest Cdrive.img

# Alternatively, if Ceph was used as a chunk store, you have to specify the pool
$ fsdup export -store &#39;ceph:?pool=fsdup2m&#39; Cdrive.manifest Cdrive.img

# Creates a block device /dev/nbdX that allows reading/writing the image file without exporting it.
# The second parameter is a copy-on-write file which is used as target for write operations.
$ fsdup map Cdrive.manifest Cdrive.cow
</code></pre><p>The <code>fsdup map</code> command is super useful for booting VMs directly from the deduplicated chunk index in Ceph/RADOS. That eliminates wait times for exporting/re-assembling the image. One could, for instance, boot the VM like this:</p>
<pre tabindex="0"><code>$ fsdup map Cdrive.manifest Cdrive.cow # maps to /dev/nbd0
$ kvm -drive format=raw,file=/dev/nbd0 -serial stdio -m 4G -cpu host -smp 2
</code></pre><p>Please note: The <code>fsdup map</code> command is <strong>really really experimental</strong> and as of now <strong>super inefficient</strong>: Regardless of the size of the a read/write request, the command reads the entire 32M chunk from the global chunk store and writes it to a cache <em>and</em> the copy-on-write file (here: Cdrive.cow). Don&rsquo;t judge: It was really easy to implement and I was planning to optimize it later. In practice, this means that until the relevant sections have been written to the local copy-on-write file, reads and writes will be slow and your Windows OS may feel sluggish.</p>
<h2 id="3-experiments">3. Experiments</h2>
<p>I did some preliminary experiments with 126 production NTFS file systems, totalling 53.5 TB in size, with 16.2 TB used space on disk. The goal of the experiments was to maximize the dedup ratio and the space reduction percentage. As a reminder, here&rsquo;s how they are defined (for a detailed description, check out <a href="/blog/minimizing-remote-storage-usage-and-synchronization-time-using-deduplication-and-multichunking-syncany-as-an-example/#Deduplication-Ratio">my Master&rsquo;s thesis</a>):</p>
<table style="width: 100%">
<tbody>
<tr>
<td style="text-align: center"><img id="Equation-3-1" style="border: none" src="/uploads/2013/05/thesis-equation-3-1.png"></td>
<td style="text-align: right; width: 50px"></td>
</tr>
</tbody>
</table>
<table style="width: 100%">
<tbody>
<tr>
<td style="text-align: center"><img id="Equation-3-2" style="border: none" src="/uploads/2013/05/thesis-equation-3-2.png"></td>
<td style="text-align: right; width: 50px"></td>
</tr>
</tbody>
</table>
<p><strong>Disclaimer:</strong> This is a very small sample so results might vary quite significantly with more input data. Also, please note that these experiments only measure the dedup ratio and space reduction. They do not take indexing performance or compression into account, which can have tremendous impact on the overall space savings or speed.</p>
<p><strong>Experiments:</strong></p>
<ol>
<li>
<p>Dedup with <code>fsdup index -minsize 2M</code></p>
</li>
<li>
<p>Dedup with <code>fsdup index -nowrite -minsize 128k</code></p>
</li>
<li>
<p>Dedup with <code>fsdup index -nowrite -minsize 64k</code></p>
</li>
<li>
<p>Dedup with <code>fsdup index -nowrite -minsize 8k</code></p>
</li>
</ol>
<p><code>-minsize X</code> defines the minimum file size to be indexed<br>
<code>-nowrite</code> means to not actually write the chunk index, i.e. only create a manifest file.</p>
<p>Experiments 2-4 were performed without actually writing out the chunk index, which is not actually necessary to calculate the dedup ratio and space reduction.</p>
<h3 id="31-experiment-1-minimum-file-size-2m">3.1. Experiment 1: Minimum file size 2M</h3>
<pre tabindex="0"><code>$ fsdup stat *.manifest
Manifests:               126
Number of unique chunks: 3,860,409
Total image size:        53.5 TB (58853137407488 bytes)
Total used size:         16.2 TB (17782383914496 bytes)
Total sparse size:       37.4 TB (41070753492992 bytes)
Total chunk size:        11.6 TB (12751789303491 bytes)
Average chunk size:      3.2 MB (3303222 bytes)
Dedup ratio:             1.4 : 1
Space reduction ratio:   28.3 %
</code></pre><h3 id="32-experiment-2-minimum-file-size-128k">3.2. Experiment 2: Minimum file size 128k</h3>
<pre tabindex="0"><code>$ fsdup stat *.manifest
Manifests:               126
Number of unique chunks: 8,660,507
Total image size:        53.5 TB (58853137407488 bytes)
Total used size:         16.2 TB (17782383914496 bytes)
Total sparse size:       37.4 TB (41070753492992 bytes)
Total chunk size:        10.8 TB (11887333358175 bytes)
Average chunk size:      1.3 MB (1372591 bytes)
Dedup ratio:             1.5 : 1
Space reduction ratio:   33.2 %   
</code></pre><h3 id="33-experiment-3-minimum-file-size-64k">3.3. Experiment 3: Minimum file size 64k</h3>
<pre tabindex="0"><code>$ fsdup stat *.manifest
Manifests:               126
Number of unique chunks: 10,729,245
Total image size:        53.5 TB (58853137407488 bytes)
Total used size:         16.2 TB (17782383914496 bytes)
Total sparse size:       37.4 TB (41070753492992 bytes)
Total chunk size:        10.7 TB (11811050790919 bytes)
Average chunk size:      1.0 MB (1100828 bytes)
Dedup ratio:             1.5 : 1
Space reduction ratio:   33.6 %
</code></pre><h3 id="34-experiment-4-minimum-file-size-8k">3.4. Experiment 4: Minimum file size 8k</h3>
<pre tabindex="0"><code>$ fsdup stat *.manifest
Manifests:               126
Number of unique chunks: 17,831,801
Total image size:        53.5 TB (58853137407488 bytes)
Total on disk size:      16.2 TB (17782383914496 bytes)
Total sparse size:       37.4 TB (41070753492992 bytes)
Total chunk size:        10.6 TB (11699232512973 bytes)
Average chunk size:      640.7 KB (656088 bytes)
Dedup ratio:             1.5 : 1
Space reduction ratio:   34.2 %
</code></pre><h2 id="4-thoughts">4. Thoughts</h2>
<h3 id="41-results">4.1. Results</h3>
<p>While these results are pretty good, I was a little disappointed by the fact that I could only get 33% space savings across so many Windows installations. It could be the sample size, or it could be the fact that people store more user-specific data than I thought they would, or it could be that I&rsquo;m not excluding temporary files or large computer-specific files such as <code>pagefile.sys</code> or <code>hiberfil.sys</code>.</p>
<p>In any case, there is much more to be investigated and much more to be optimized. I am quite confident that I can get to much higher dedup ratios with a higher sample and a little more investigation time.</p>
<p>Too bad I don&rsquo;t have more time right now. So this is it for now. Or is it?</p>
<h3 id="42-further-experiments">4.2. Further experiments</h3>
<p>Since I wasn&rsquo;t satisfied with the results, I informally continued the experiments in a less structured way. I picked 128k as a minimum file size, because that seems good enough, especially since 64k and 8k were so much slower than 128k and 2M (not shown in the results).</p>
<p>In one experiment, I continued ingesting more NTFS image files (up to 195), and while the dedup ratio for a 128k min size shrunk a little (32.1%), I experimented with compressing the chunks individually before storing them. With the same dataset, i.e. 195 image files, I got the following results:</p>
<pre tabindex="0"><code>$ fsdup stat *.manifest

Manifests:                  195
Number of unique chunks:    13001394
Total image size:           70.1 TB (77086756862464 bytes)
- Used:                     23.6 TB (25972460016504 bytes)
- Sparse/empty:             46.5 TB (51114296845960 bytes)
Total chunk size:           16.0 TB (17644694259014 bytes)
Average chunk size:         1.3 MB (1357139 bytes)
Median chunk size:          92.6 KB (94845 bytes)
Dedup ratio:                1.5 : 1
Space reduction:            32.1 %

Stats after compression (Ceph/gzip):
Total size of image files:            23.6 TB (no compression)
Total size of chunks (in Ceph/gzip):  10.0 TB (deduped in Ceph with gzipped chunks)          
Dedup ratio (raw vs Ceph/gzip):       2.36 : 1 (-57.6 %)
</code></pre><p>As you can see, compression is a real game changer (obviously). <strong>We now get 57% space savings!</strong></p>
<p>Now the obvious question is: What if I simply compress the original image files with gzip? Will that not lead to similar savings? Good question, my dear reader! I have, unfortunately, not done any tests with purely compressing image files using gzip.</p>
<p>I do, however, have at least <em>some</em> numbers with regards to this: Storing these 195 image files on ZFS with lz4 compression takes up about 17 TB of space, so that&rsquo;s a ratio of 1.38 : 1 (23.6 : 17), which is obviously much worse than Ceph+gzip, but it also doesn&rsquo;t really say anything because it&rsquo;s not an apples-to-apples kind of comparison.</p>
<p>Maybe I&rsquo;ll eventually find the time to continue the experiments &hellip; Who knows.</p>
<h3 id="42-interesting-things-ive-learned">4.2. Interesting things I&rsquo;ve learned</h3>
<p>Besides the results of the experiments, I&rsquo;ve learned quite a few things, which I thought I&rsquo;d share with you.</p>
<h4 id="421-parsing-ntfs-is-hard">4.2.1. Parsing NTFS is hard</h4>
<p>Who would have thought that a simple file system could be so complicated. When I started this, I briefly looked for Go libraries to parse NTFS and I found <a href="https://github.com/Velocidex/go-ntfs">go-ntfs</a>, which works wonderfully, but had a super awkward interface, and it simply didn&rsquo;t fit my needs. What I needed was the boundaries for all extents (data runs) for all files greater than X bytes &ndash; not something the library provided. I then briefly looked into using <a href="https://www.tuxera.com/community/open-source-ntfs-3g/">ntfs-3g</a> and use <a href="https://golang.org/cmd/cgo/">cgo</a> as an interface to use it in Go. That turned out to be tedious (maybe because I had never used cgo before? maybe because ntfs-3g is hardly documented at all?), so I didn&rsquo;t go that route either.</p>
<p>Long story short, I decided to write my own parser. How hard could it be?</p>
<p>Turns out, it&rsquo;s not terribly hard to read the sector size, the cluster size and other basic information from the <a href="https://flatcap.org/linux-ntfs/ntfs/files/boot.html">NTFS boot sector</a>. Getting from the boot sector to the <code>$MFT</code> and then iterating over all entries was also manageable. Once I had a file entry though, NTFS has these strange <a href="https://flatcap.org/linux-ntfs/ntfs/concepts/data_runs.html">data runs</a> (extends, fragments) that are terribly hard to parse. Once you know how to read little endian ints (signed and unsigned!), it&rsquo;s all super easy, but getting there was quite the challenge.</p>
<p>Oh, and then there is this thing called <a href="https://flatcap.org/linux-ntfs/ntfs/concepts/fixup.html">NTFS fixups</a> (also called update sequences) which replaces bytes at the end of each MFT entry as a means to detect errors. What crazy madness is this?</p>
<p>The one resource I can highly recommend for anything NTFS is <a href="https://flatcap.org/linux-ntfs/ntfs/index.html">Richard Russon&rsquo;s NTFS documentation</a>, a great reference for all the weird things that NTFS does and doesn&rsquo;t do. I particularly enjoyed his writing style. Here&rsquo;s a gem from the section about <a href="https://flatcap.org/linux-ntfs/ntfs/files/bitmap.html"><code>$Bitmap</code> parsing</a>:</p>
<blockquote>
<p><em>&ldquo;The backup copy of the boot sector lies in this no-mans-land the cluster is hence marked as in use. In theory, on very small volume, this attribute could be resident. In practice, Windows crashes.&rdquo;</em></p>
</blockquote>
<p>Another resource I highly recommend is the <a href="http://www.disk-editor.org">Active Disk Editor</a>, a wonderful colored hex editor that is really good at displaying NTFS sectors, MFT entries and data runs. It only worked properly on Windows, but it was worth it working in a VM. Without it, I would not have succeeded.</p>
<p>Anyway. What I learned was this: <strong>Don&rsquo;t write your own NTFS parser. Use someone else&rsquo;s</strong>! I should have spent more time with ntfs-3g and cgo. Or just written this part in C.</p>
<h4 id="422-i-got-better-at-go">4.2.2. I got better at Go</h4>
<p>Which brings me to this. Part of why I wrote this in Go is to get better at Go. I think I did. I know how to structure things and I&rsquo;m generally more confident with the language. Yeyy!</p>
<h4 id="423-random-access-on-hdds-is-sloowww-">4.2.3. Random access on HDDs is sloowww &hellip;.</h4>
<p>As of its current iteration, <code>fsdup</code> doesn&rsquo;t do a great job to read the disk (file system) sequentially. It does this instead: Read MFT entry, read data runs, read each data run from disk. Since the MFT is (usually) at the beginning of the disk and the data runs are spread out all over the place (the more fragmented the file system the more spread out they are), the read head has to jump back and forth a lot.</p>
<p>That&rsquo;s slow! Yes, I know. It&rsquo;s obvious. I knew random access on HDDs was slow, but I didn&rsquo;t know how much of a difference it made. Well now I know.</p>
<p>The indexing/chunking algorithm can be optimized for (mostly) sequential reads though pretty easily. I haven&rsquo;t done this yet, but here&rsquo;s how I&rsquo;d do it if I had more time: Read all entries in the MFT, sort by first data run offset, then read each MFT entry like above.</p>
<p>Assuming that most file entries fit in one data run, and that the file system is not horribly fragmented, that approach should read the disk mostly sequentially.</p>
<h2 id="5-what-now-so-what-now-well-right-now-i-am-not-working-on-this-anymore-but-i-suspect-that-i-will-in-the-near-future-ill-probably-experiment-with-file-exclusion-for-temporary-files-and-files-like-pagefilesys-ill-also-try-to-get-some-file-size-histograms-so-i-can-get-some-data-about-what-a-good-min-size-will-be">5. What now? So what now? Well right now, I am not working on this anymore, but I suspect that I will in the near future. I&rsquo;ll probably experiment with file exclusion for temporary files and files like <code>pagefile.sys</code>. I&rsquo;ll also try to get some file size histograms so I can get some data about what a good min size will be.</h2>
<p>In addition to that, I might try to use the simple 4K approach, i.e. simply dedup blocks/clusters instead of all this mumbo-jumbo with the files. If I find a good data store to support this many objects without much overhead, why not, right?</p>
<p>I also need to investigate and find a way to fit compression into this concept. The current implementation gzips the chunks before storing them, which is great for writing, but not great for random access reading of partial chunks (think: get me bytes 1024-2048 from chunk 1234), because gzip is not really easily seekable. Not great.</p>
<p>So there&rsquo;s lots to do and no time to do it. Isn&rsquo;t that what it always feels like?</p>
<p>Thanks for reading. Feel free to leave comments. I love conversing about this!</p>]]></content:encoded></item><item><title>Snippet 0x0E: Booting image files and ISOs with KVM/QEMU (EFI and BIOS)</title><link>https://heckel.io/blog/booting-image-files-and-isos-with-kvm-qemu-efi-and-bios/</link><pubDate>Mon, 06 Aug 2018 02:12:10 -0400</pubDate><guid>https://heckel.io/blog/booting-image-files-and-isos-with-kvm-qemu-efi-and-bios/</guid><description>For my job, I work with file systems and image files a lot: Every day, we mess with Grub, the partition tables (MBR/GPT), EFI and BIOS systems, etc. So pretty much every day, I need to boot some image file or investigate why some image didn&amp;rsquo;t boot.
This (super duper) short post shows how to …</description><content:encoded><![CDATA[<p>For my job, I work with file systems and image files a lot: Every day, we mess with Grub, the partition tables (MBR/GPT), EFI and BIOS systems, etc. So pretty much every day, I need to boot some image file or investigate why some image didn&rsquo;t boot.</p>
<p>This (super duper) short post shows how to <strong>boot image files using straight<code>kvm</code> commands</strong>.</p>
<h2 id="1-booting-image-files-with-kvm-non-efibios">1. Booting image files with KVM (non-EFI/BIOS)</h2>
<p>First, make sure you have KVM installed (I&rsquo;m assuming you&rsquo;re running Debian/Ubuntu):</p>
<p>_Install KVM _</p>
<pre tabindex="0"><code>$ sudo apt install qemu-kvm
</code></pre><p>Now that that&rsquo;s done, let&rsquo;s boot the image: assuming you have a raw image file (e.g. <code>disk.img</code>) or an ISO (e.g. <code>ubuntu-18.04.iso</code>), you can use the following script to start a virtual machine in KVM:</p>
<p><em>Script &lsquo;boot&rsquo; to boot from a single image file via KVM</em></p>
<pre tabindex="0"><code>#!/bin/bash
kvm \
  -drive format=raw,file=$1 \
  -serial stdio \
  -m 4G \
  -cpu host \
  -smp 2
</code></pre><p>The command will attach one disk (<code>-drive format=raw,file=$1</code> with <code>$1</code> being the disk file) and it&rsquo;ll assign 4 GB of RAM to the VM (<code>-m 4G</code>). Since no <code>-bios</code> flag is specified, KVM/QEMU will use its own BIOS. To boot via UEFI, see below.</p>
<p>If you save the snippet as a script (and call it <code>boot</code>), you can use it very easily to boot any kind of image like this:</p>
<pre tabindex="0"><code>$ boot disk.img
$ boot debian-live-8.4.0-amd64-standard.iso
...
</code></pre><p>If you&rsquo;ve done everything right, you should see the system booting up:</p>
<p><img src="/uploads/2018/08/180805-Screenshot-QEMU-20-13-001.png" alt=""></p>
<p>You can control the VM using the KVM command shell by hitting <code>Ctrl-Alt-1</code>:</p>
<p><img src="/uploads/2018/08/180805-Screenshot-QEMU-20-15-004.png" alt=""></p>
<p>What I mostly use this for is to send the <code>Ctrl-Alt-F1</code> to the VM to switch to a different TTY.</p>
<h2 id="2-booting-image-files-with-kvm-efi">2. Booting image files with KVM (EFI)</h2>
<p>Booting a virtual machine via UEFI is pretty similar. The only real thing you have to change is to specify a UEFI firmware that KVM should use. <a href="https://www.linux-kvm.org/page/OVMF">OVMF</a> is such a firmware. You can install it via apt:</p>
<p><em>Install KVM and the UEFI firmware</em></p>
<pre tabindex="0"><code>$ sudo apt install qemu-kvm ovmf
</code></pre><p>Once that&rsquo;s installed, simply add the <code>-bios</code> option like this:</p>
<p><em>Script &lsquo;boot-efi&rsquo; to boot from a single image file via KVM</em></p>
<pre tabindex="0"><code>#!/bin/bash
kvm \
  -bios /usr/share/qemu/OVMF.fd \
  -drive format=raw,file=$1 \
  -net none \
  -serial stdio \
  -m 4G \
  -cpu host \
  -smp 2
</code></pre><p>After you save this as a script, e.g. <code>boot-efi</code>, you can use it like this:</p>
<pre tabindex="0"><code>$ boot-efi disk.img
$ boot-efi ubuntu-16.04.1-desktop-amd64.iso
...
</code></pre><p>When booting, you&rsquo;ll see that the output will have changed slightly, like this:</p>
<p><img src="/uploads/2018/08/180805-Screenshot-QEMU-20-47-003.png" alt=""></p>
<p>That&rsquo;s really it. You can check out more KVM options in the <a href="https://manpages.debian.org/stretch/qemu-system-x86/qemu-system-x86_64.1.en.html">man page</a> by typing <code>man qemu-system-x86_64</code>.</p>
<h2 id="a-about-this-post">A. About this post</h2>
<p>I&rsquo;m trying a new section for my blog. I call it <a href="/blog/categories/code-snippets/">Code Snippets</a>. It&rsquo;ll be very short, code-focused posts of things I recently discovered or find fascinating or helpful. I hope this helps.</p>]]></content:encoded></item><item><title>Using Let's Encrypt for internal servers</title><link>https://heckel.io/blog/issuing-lets-encrypt-certificates-for-65000-internal-servers/</link><pubDate>Sun, 05 Aug 2018 19:11:05 -0400</pubDate><guid>https://heckel.io/blog/issuing-lets-encrypt-certificates-for-65000-internal-servers/</guid><description>Let&amp;rsquo;s Encrypt is a revolutionary new certificate authority that provides free certificates in a completely automated process. These certificates are issued via the ACME protocol. Over the last 2 years or so, the Internet has widely adopted Let&amp;rsquo;s Encrypt &amp;ndash; over 50% of the …</description><content:encoded><![CDATA[<p><a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a> is a revolutionary new certificate authority that provides free certificates in a completely automated process. These certificates are issued via the <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html">ACME protocol</a>. Over the last 2 years or so, the Internet has widely adopted Let&rsquo;s Encrypt &ndash; over 50% of the web&rsquo;s SSL/TLS certificates are now issued by Let&rsquo;s Encrypt.</p>
<p>But while there are many tools to automatically renew certificates for publicly available webservers (<a href="https://certbot.eff.org/">certbot</a>, <a href="https://github.com/kuba/simp_le">simp_le</a>, <a href="/blog/lets-encrypt-5-min-guide-to-set-up-cronjob-based-certificate-renewal/">I wrote about how to do that 3 years back</a>), it&rsquo;s hard to find any useful information about how to issue certificates for internal non Internet facing servers and/or devices with Let&rsquo;s Encrypt.</p>
<p>This blog posts describes <strong>how to issue Let&rsquo;s Encrypt certificates for internal servers</strong>. At my work, we issued a certificate for each of our <del>65,000</del> 90,000+ BCDR appliances using this exact mechanism.</p>
<p><strong>Hello Hacker News,</strong> first time on the HN front page! I feel honored, yeyy! I responded to all of the concerns in the <a href="https://news.ycombinator.com/item?id=19353294">comments section</a>.</p>
<p><strong>If you&rsquo;re looking for an implementation of this idea,</strong> you may find <a href="https://github.com/Corollarium/localtls">localtls</a> interesting. I have not tested it myself, but it seems to do similar things to what I am describing here.</p>
<hr>
<h2 id="1-how-does-it-work">1. How does it work?</h2>
<p>To issue a certificate through Let&rsquo;s Encrypt, you must prove that you either own the website you want to issue the certificate for, or that you own the domain it runs on. Typically, automated tools like <a href="https://certbot.eff.org/">certbot</a> use the <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.8.3">HTTP challenge</a> to prove site ownership using the <code>.well-known</code> directory. While this works beautifully if the site is Internet-facing (and Let&rsquo;s Encrypt can verify the HTTP challenge files via a simple HTTP request), it doesn&rsquo;t work if your server runs on <code>10.1.1.4</code> or any other internal address.</p>
<p>The <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.8.4">DNS challenge</a> solves this problem by letting you prove domain ownership through the DNS <code>TXT</code> record <code>_acme-challenge.example.com</code>. Let&rsquo;s Encrypt will verify that the record matches what it expects and issue your certificate if it all adds up.</p>
<p>So really the magic ingredients to issuing certificates for internal non Internet facing machines are:</p>
<ul>
<li>A dedicated DNS zone for all your internal devices, e.g. <code>xi8qz.example.com</code>, and a dynamic DNS server to manage this zone (here: <code>example.com</code>)</li>
<li>An ACME client capable of using the Let&rsquo;s Encrypt&rsquo;s DNS challenge to prove domain ownership</li>
</ul>
<h2 id="2-example-an-internal-server-10114-aka-xi8qzexamplecom">2. Example: An internal server 10.1.1.4, aka. xi8qz.example.com</h2>
<p>The following diagram shows how we have implemented our Let’s Encrypt integration for our backup appliances. Each appliance (read: internal server) is behind a NAT and carries its own local IP address.</p>
<p>The general approach is simple: The appliance regularly reaches out to our control server to ensure that it can be reached via its own subdomain. If its local IP address changes, it triggers an update of its own subdomain. In addition, it checks regularly if the certificate is still valid, and requests a renewal if it&rsquo;s outdated.</p>
<p>Here&rsquo;s a bit more detail to this process:</p>
<p><img src="/uploads/2018/07/LetsEncryptBlogPost-1-1024x837.png" alt=""></p>
<p>For this example, let&rsquo;s assume we&rsquo;re trying to issue a certificate for an appliance with the identifier <code>xi8qz</code> and the local IP address <code>10.1.1.4</code>. From the perspective of this appliance, there are two requests to be made:</p>
<ul>
<li><strong>Steps 1-3:</strong> First, it needs to set/update its own DNS domain (here: <code>xi8qz.example.com</code>). This domain will later be used as a common name (<code>CN</code>) in the certificate. On top of that, it needs to make sure that this record is updated every time the server&rsquo;s IP address changes.</li>
<li><strong>Steps 4-14:</strong> It needs to regularly check if the local certificate needs to be renewed and request a renewal if it&rsquo;s time. Obviously, if there is no certificate it needs to be &ldquo;renewed&rdquo;.</li>
</ul>
<p>Let&rsquo;s now examine these steps in greater detail.</p>
<h3 id="21-prerequisites-assigning-a-domain-for-each-machine-steps-1-3">2.1. Prerequisites: Assigning a domain for each machine (steps 1-3)</h3>
<p>As mentioned above, we need to give each appliance a proper domain name in order to be able to prove ownership to Let&rsquo;s Encrypt, so we need to buy a domain (here: <code>example.com</code>) and delegate its <code>NS</code> records to our DDNS server:</p>
<p><em>Our DDNS server should own the domain we&rsquo;ve chosen for our machines</em></p>
<pre tabindex="0"><code>$ dig +short NS example.com
ddns1.mycompany.com.
</code></pre><p>On top of that, we need the ability to dynamically add and remove records from it (via an API of some sort). I&rsquo;ve previously written about how to <a href="/blog/your-own-dynamic-dns-server-powerdns-mysql/">spin up your own DDNS server</a>, if you are interested.</p>
<p>Once that&rsquo;s all set up, we need to make sure that the machine&rsquo;s <code>A</code> record is updated whenever its IP address changes. For our internal machine, let&rsquo;s assign <code>xi8qz.example.com</code> as its domain. If everything&rsquo;s working properly, you should be able to resolve this domain to its IP address using a normal DNS query:</p>
<p><em>The machine&rsquo;s A record resolves to its local IP address</em></p>
<pre tabindex="0"><code>$ dig +short xi8qz.example.com
10.1.1.4
</code></pre><h3 id="22-requesting-a-certificate-steps-4-14">2.2. Requesting a certificate (steps 4-14)</h3>
<p>Assuming you now control the DNS zone for <code>example.com</code> completely and you can quickly edit it dynamically, you&rsquo;re all set for actually issuing certificates for your local device domain via Let&rsquo;s Encrypt.</p>
<p>For our example appliance, it will regularly check if the existing certificate is still valid (step 4). If there is no certificate or the existing one is about to expire, the device will generate a keypair and a <a href="https://en.wikipedia.org/wiki/Certificate_signing_request">certificate signing request (CSR)</a> using its assigned hostname (here: <code>xi8qz.example.com</code>) as a <code>CN</code>, and it&rsquo;ll send that CSR to the control server (step 5).</p>
<p>After authorizing the request (an important step not shown in the diagram!), the control server requests a DNS challenge for the given domain from the ACME API via the <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.7.4.1">Pre-Authorization</a>/<code>new-authz</code> API call (step 6). The ACME API responds with a DNS challenge (step 7). If all goes well, this looks something like this:</p>
<p><em>Response from the ACME API for a new-authz request</em></p>
<pre tabindex="0"><code>{
  &#34;identifier&#34;: {
    &#34;type&#34;: &#34;dns&#34;,
    &#34;value&#34;: &#34;xi8qz.example.com&#34;
  },
  &#34;status&#34;: &#34;pending&#34;,
  &#34;expires&#34;: &#34;2018-04-15T21:26:29Z&#34;,
  &#34;challenges&#34;: [
    {
      &#34;type&#34;: &#34;dns-01&#34;,
      &#34;status&#34;: &#34;pending&#34;,
      &#34;uri&#34;: &#34;https://acme-staging.api.letsencrypt.org/acme/challenge/VtjihR4X8nLAj4MDwI...&#34;,
      &#34;token&#34;: &#34;aLptEKAeUOajkiGrx-kkbjUX4b1MC...&#34;
    },
    // ...
  ],
  // ...
}
</code></pre><p>Using this response, the control server must set a DNS <code>TXT</code> record at <code>_acme-challenge.xi8qz.example.com</code> (step 8) and notify the ACME API that the challenge response has been placed (step 9).</p>
<p>Once the challenge response has been verified by Let&rsquo;s Encrypt (step 10-11), the certificate can finally be requested using the CSR (step 12-13).</p>
<p>After Let&rsquo;s Encrypt responds with a certificate, you&rsquo;ll see something like this on the wire:</p>
<pre tabindex="0"><code>-----BEGIN CERTIFICATE-----
MIIGEjCCBPqgAwIBAgISAyk2izMz7OXSqHeZhg+rUR5uMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
...
</code></pre><p>If decoded with <code>openssl</code>, we can see that&rsquo;s it&rsquo;s the real deal:</p>
<pre tabindex="0"><code>$ openssl x509 -in www.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            03:29:36:8b:33:33:ec:e5:d2:a8:77:99:86:0f:ab:51:1e:6e
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let&#39;s Encrypt, CN=Let&#39;s Encrypt Authority X3
        Validity
            Not Before: Jul 18 23:37:35 2018 GMT
            Not After : Oct 16 23:37:35 2018 GMT
        Subject: CN=xi8qz.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:be:69:df:28:04:9c:2b:e9:94:72:c3:de:a6:fd:
                    a4:38:93:be:43:a7:81:8b:dc:9a:be:19:0d:c0:d1:
...
</code></pre><p>This certificate is then returned to the machine (step 14). After the webserver of the appliance/server has been restarted, it&rsquo;s web interface can be accessed via HTTPS in the browser or on the command line:</p>
<p><em>Connecting to the internal server via HTTPS</em></p>
<pre tabindex="0"><code>$ curl -v https://xi8qz.example.com/login
*   Trying 10.1.1.4...
* TCP_NODELAY set
* Connected to xi8qz.example.com (10.1.1.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=xi8qz.example.com
*  start date: Jul 18 23:37:35 2018 GMT
*  expire date: Oct 16 23:37:35 2018 GMT
*  subjectAltName: host &#34;xi8qz.example.com&#34; matched cert&#39;s &#34;xi8qz.example.com&#34;
*  issuer: C=US; O=Let&#39;s Encrypt; CN=Let&#39;s Encrypt Authority X3
*  SSL certificate verify ok.
&gt; GET /login HTTP/1.1
&gt; Host: xi8qz.example.com
&gt; User-Agent: curl/7.58.0
&gt; Accept: */*
&gt; 
&lt; HTTP/1.1 200 OK
&lt; Date: Sun, 05 Aug 2018 17:38:49 GMT
&lt; Server: Apache/2.4.18 (Ubuntu)
...
</code></pre><h2 id="3-deployment-considerations-lets-encrypt-rate-limits">3. Deployment considerations: Let&rsquo;s Encrypt rate limits</h2>
<p>It&rsquo;s important to note that if you are considering implementing this mechanism for a large number of servers that you use the Let&rsquo;s Encrypt <a href="https://letsencrypt.org/docs/staging-environment/">staging environments</a> for testing and, more importantly, that you consider their <a href="https://letsencrypt.org/docs/rate-limits/">rate limit restrictions</a>.</p>
<p>By default, Let&rsquo;s Encrypt only allows you to issue 20 certificates per week for the same domain or the same account. To increase this number, you have to either <a href="https://goo.gl/forms/plqRgFVnZbdGhE9n1">request a higher rate limit</a> or get your domain added to the <a href="https://publicsuffix.org/">public suffix list</a> (note: adding your domain here has other implications!).</p>
<p>Due to these rate limits, it is vital that you spread out the initial deployment enough to stay under the rate limit, and that you leave enough room for future servers to be added. Also consider renewals in the initial rollout plan.</p>
<h2 id="4-summary-as-you-can-see-its-not-really-rocket-science">4. Summary As you can see it&rsquo;s not really rocket science.</h2>
<p>We first assigned each appliance (aka. internal server) a public domain name using our own dynamic DNS server and a dedicated DNS zone. Using the server&rsquo;s assigned domain (here: <code>xi8qz.example.com</code>), we then used Let&rsquo;s Encrypt&rsquo;s free certificate offering and their DNS challenge to issue a certificate for that server.</p>
<p>By doing that for all internal servers, we can provide secure communication in our internal IT infrastructure without having to deploy a custom CA cert or having to pay for certificates.</p>]]></content:encoded></item><item><title>USB disk causes blinking cursor at boot; how to "fix" the MBR bootstrap code</title><link>https://heckel.io/blog/usb-disk-blinking-cursor-at-boot-fix-the-mbr-bootstrap-code/</link><pubDate>Sun, 18 Mar 2018 22:05:09 -0400</pubDate><guid>https://heckel.io/blog/usb-disk-blinking-cursor-at-boot-fix-the-mbr-bootstrap-code/</guid><description>Have you ever rebooted your computer only to see a black screen with a blinking cursor? If you have a USB drive attached, chances are the blinking cursor is caused by invalid bootstrap code in the Master Boot Record (MBR) on that drive which has caused the normal boot execution to stop without …</description><content:encoded><![CDATA[<p>Have you ever rebooted your computer only to see a <strong>black screen with a blinking cursor</strong>? If you have a USB drive attached, chances are the blinking cursor is <strong>caused by invalid bootstrap code in the Master Boot Record (MBR)</strong> on that drive which has caused the normal boot execution to stop without returning control to the BIOS. If you have physical access to the machine, simply remove the USB drive and/or change the boot order to pick the OS disk first.</p>
<p>If you have no physical access, things are a bit more tricky: This exact thing happened to me at work the other day. Unfortunately, it didn&rsquo;t happen to my computer, but to a few dozen of our customer backup appliances during their scheduled upgrade/reboot. Now, while dozens out of over 60k isn&rsquo;t that much, our customers rely on these devices, so it&rsquo;s not acceptable to have them not boot properly.</p>
<p>In this short post, I&rsquo;ll demonstrate how to <strong>reproduce the blinking cursor problem</strong>, and <strong>how to &ldquo;fix&rdquo; the MBR</strong> to ensure the computer still boots, regardless of the boot order.</p>
<h2 id="1-diagnosing-a-blinking-cursor">1. Diagnosing a blinking cursor</h2>
<p>Assuming that your system is booting via BIOS (not UEFI), you&rsquo;ve surely encountered the infamous blinking cursor at least once:</p>
<p><img src="/uploads/2018/03/QEMU_014.jpg" alt=""></p>
<p>When we encountered it a few months ago when a few of our customer devices refused to reboot, we were a bit puzzled at first. How could one possibly diagnose something like that without having physical access to the device itself? With lots of on site customer help, we quickly narrowed things down to an attached USB flash drive, and we immediately suspected that the BIOS is trying to boot from that drive.</p>
<p>But how was that possible? None of the partitions in the MBR on that drive was marked bootable, and there wasn&rsquo;t even a bootable OS on the USB drive. How does the BIOS decide which partition to boot from anyway? Won&rsquo;t it just look at the MBR partition table and pick the first bootable partition?</p>
<p>No. Apparently not.</p>
<p>As we found out quickly, the BIOS in fact doesn&rsquo;t really care about the partition table at all. All it does is to list all attached drives, and go through them one by one according to the boot order. <strong>If it finds a drive with an MBR signature<code>0x55aa</code> at offset <code>0x1fe</code></strong>(see image below in green), <strong>it simply begins executing whatever code resides at offset<code>0x00</code> of the disk</strong> (in red):</p>
<p><img src="/uploads/2018/03/Selection_011.jpg" alt=""></p>
<p>Wow isn&rsquo;t that surprising? It surely was to me. I thought the BIOS was supposed to be a bit smarter than that. What that means is that really <em>any</em> disk in a boot position before your OS drive can render your computer unbootable if its MBR bootstrap code is not properly programmed, or has been overwritten with garbage.</p>
<p>In our case, we found that certain tools are apparently under the impression that the first few bytes of a disk are &ldquo;unused&rdquo;, because they don&rsquo;t contain the partition table and use this area to store their own metadata.</p>
<p>Since these first 446 bytes do in fact contain the MBR bootstrap code, they are incredibly important to the BIOS during the boot process. <strong>If whatever is stored there is not valid x86 code, the BIOS will fail to execute it and drop to the blinking cursor of doom</strong>.</p>
<h2 id="2-reproducing-the-problem">2. Reproducing the problem</h2>
<p>Now that we knew what the problem is we needed a quick way to reproduce it, so that we could be certain whatever fix we developed would work. The easiest way to do this is by using a virtual machine in KVM/QEMU, a live Linux ISO and a raw disk image with &ldquo;broken&rdquo; or non-existent MBR bootstrap code.</p>
<p>First, download a live Linux distro. Something like Tiny Core Linux is more than enough for now:</p>
<pre tabindex="0"><code># Download Tiny Core Linux
curl http://distro.ibiblio.org/tinycorelinux/9.x/x86/release/TinyCore-current.iso \
  &gt; TinyCore-current.iso
</code></pre><p>Then, we&rsquo;ll create our &ldquo;broken&rdquo; USB disk: To do that, we&rsquo;ll create a 1 MB sparse file using <code>truncate -s 1M</code> and we&rsquo;ll create an MBR with a single partition using <code>fdisk</code>. I was a bit lazy here and shortened the fdisk command by using it non-interactively like that; but you can do all of this via the interactive console: <code>o</code> creates the MBR, <code>n</code>-<code>p</code>-<code>1</code>-<code>1</code> creates the partition, and <code>w</code> writes the changes.</p>
<pre tabindex="0"><code># Create a sparse image file (our broken USB disk)
truncate -s 1M brokenusb.img

# Create an MBR and a partition
echo -e &#34;o\nn\np\n1\n1\n\nw\n&#34; | fdisk brokenusb.img

# List partitions and MBR info
fdisk -l brokenusb.img
</code></pre><p>That&rsquo;s really it to reproduce the problem. If you now attach the Tiny Core ISO as a CD-ROM and the image file as a USB device, you&rsquo;ll see that KVM won&rsquo;t boot the Tiny Core live Linux, even though the disk with the MBR has no bootable partitions whatsoever.</p>
<pre tabindex="0"><code># Try to boot from it. 
# This will fail with a blinking cursor!

kvm \
  -drive media=cdrom,file=TinyCore-current.iso \
  -usb -usbdevice disk:format=raw:brokenusb.img \
  -serial stdio -m 1G -cpu host -smp 2 -net none
</code></pre><h2 id="3-interrupt-18h-to-the-rescue">3. Interrupt 18h to the rescue!</h2>
<p>At first we thought we can&rsquo;t really do anything about this case. We can detect it now programatically, but what could we possibly do about it?</p>
<p>Well, it turns out that at the very end of the <a href="http://www.scs.stanford.edu/05au-cs240c/lab/specsbbs101.pdf">BIOS Boot Specification</a> in Appendix D.2 (yes, I read the entire 46 pages &hellip;), it says:</p>
<blockquote>
<p><em>If an O/S is either not present, or otherwise not able to load, execute an INT 18h instruction so that control can be returned to the BIOS. Currently, hard drive boot sectors do this, but floppy diskette boot sectors execute an INT 19h instead of INT 18h. The BIOS Boot Specification defines INT 18h as the recovery vector for failed boot attempts.</em></p>
</blockquote>
<p>Hurray! Exactly what we need. We need to tell the BIOS to try the next disk. The equivalent of &ldquo;nothing to see here, move along&rdquo;.</p>
<p>Well, let&rsquo;s try it then. Let&rsquo;s write an incredibly complex assembly program containing one instruction <code>int 18h</code> (to call interrupt 18h) and write it to the beginning of the disk:</p>
<pre tabindex="0"><code># Create the .asm file
echo &#34;int 18h&#34; &gt; skipdisk.asm

# Compile to x86 code to &#34;skipdisk&#34; output file
nasm skipdisk.asm

# Note:
#   If you don&#39;t have &#34;nasm&#34; installed, you can simply run
#   this to produce the same file: echo -en &#34;\xcd\x18&#34; &gt; skipdisk

# Write to beginning of the disk
dd if=skipdisk of=brokenusb.img conv=notrunc
</code></pre><p>That&rsquo;s it already. <strong>This essentially nukes the bootability of any MBR based disk</strong>, which is exactly what we needed. If you look at the disk in a hex editor like <code>dhex</code>, you&rsquo;ll see the interrupt call at the very beginning as <code>0xcd18</code>:</p>
<p><img src="/uploads/2018/03/dhex-brokenusb.img_016.jpg" alt=""></p>
<p>Now you can try to boot the VM again, using the exact same parameters, and you&rsquo;ll see that this time it skips the attached USB disk, and it&rsquo;ll boot the live Linux instead:</p>
<pre tabindex="0"><code># Aaannndd let&#39;s boot it again.
# Booom! It now skips the disk, and boots Tiny Core Linux
kvm \
  -drive media=cdrom,file=TinyCore-current.iso \
  -usb -usbdevice disk:format=raw:brokenusb.img \
  -serial stdio -m 1G -cpu host -smp 2 -net none
</code></pre><p>If everything worked as expected, you should see the Tiny Core Linux boot loader screen:</p>
<p><img src="/uploads/2018/03/QEMU_015.jpg" alt=""></p>
<h2 id="x-bonus">X. Bonus</h2>
<p>If you&rsquo;re like me and the whole world of MBR bootstrap code is new to you, you&rsquo;ll probably start experimenting with the <code>skipdisk.asm</code> file I provided. If you really want to, you can replace the entire bootstrap code area and do all sorts of things in there.</p>
<p>There is a wonderful <a href="https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders">wikibook on x86 bootloaders in assembly </a> that I can recommend.</p>
<p>I found a good starting point also <a href="https://github.com/danrl/danrlOS-MBR">Dan Luedtke&rsquo;s boot loader on Github</a>. I modified his boot loader, ran <code>nasm</code>, and then used <code>dd bs=446 count=1 conv=notrunc</code> to replace the first 446 bytes of my disk. A great learning exercise.</p>]]></content:encoded></item><item><title>Creating a BIOS/GPT and UEFI/GPT Grub-bootable Linux system</title><link>https://heckel.io/blog/creating-a-bios-gpt-and-uefi-gpt-grub-bootable-linux-system/</link><pubDate>Sun, 28 May 2017 17:52:50 -0400</pubDate><guid>https://heckel.io/blog/creating-a-bios-gpt-and-uefi-gpt-grub-bootable-linux-system/</guid><description>Good old Master Boot Record (MBR) unfortunately cannot address anything beyond 2TB, so partitioning large disks and making them bootable is impossible using MBR. The GUID Partition Table (GPT) solves this problem: It supports disks up to 16EB. However, installing grub does not work without a special …</description><content:encoded><![CDATA[<p>Good old <a href="https://en.wikipedia.org/wiki/Master_boot_record">Master Boot Record (MBR)</a> unfortunately cannot address anything beyond 2TB, so partitioning large disks and making them bootable is impossible using MBR. The <a href="https://en.wikipedia.org/wiki/GUID_Partition_Table">GUID Partition Table (GPT) </a> solves this problem: It supports disks up to 16EB. However, installing grub does not work without a special <a href="https://en.wikipedia.org/wiki/BIOS_boot_partition">BIOS boot partition</a>. If you also want to support booting the same system via <a href="https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface">UEFI</a>, another partition, the <a href="https://en.wikipedia.org/wiki/EFI_system_partition">EFI System Partition (ESP)</a>, is necessary.</p>
<p>This should post shows you how to partition a disk with GPT and make a <strong>bootable Linux system via BIOS/Legacy and UEFI</strong>.</p>
<h2 id="requirements--assumptions">Requirements &amp; Assumptions</h2>
<p>For this post, I&rsquo;ll assume that you are running everything on a Debian-based system, and all commands shown must be run as root.</p>
<h2 id="1-create-a-minimal-linux-optional">1. Create a minimal Linux (optional)</h2>
<p>For this example, we&rsquo;ll use a small Linux install that we can create with the following commands. This will bootstrap (<code>debootstrap</code>) a minimal Ubuntu 16.04 system to the <code>chroot/</code> folder which we will later use to boot our system. To create a bootable system, we need a kernel and the bootloader modules and files, so let&rsquo;s install <code>linux-image-generic</code> and <code>grub-pc</code>:</p>
<p><em>Bootstrapping a new Ubuntu 16.04 system (with a kernel and grub)</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Bootstrap minimal system
</span></span><span class="line"><span class="cl">debootstrap --variant=minbase xenial chroot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Install kernel and grub
</span></span><span class="line"><span class="cl">for d in dev sys proc; do 
</span></span><span class="line"><span class="cl">  mount --bind /$d chroot/$d
</span></span><span class="line"><span class="cl">done
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">DEBIAN_FRONTEND=noninteractive chroot chroot apt-get install linux-image-generic grub-pc -y --force-yes
</span></span><span class="line"><span class="cl">umount chroot/{dev,proc,sys}
</span></span></code></pre></div><p>Alternatively, you can of course use any Linux root file system, provided that it has a kernel and grub. You could, for instance, just copy your own root file system to the <code>chroot/</code> folder.</p>
<h2 id="2-creating-a-gpt-with-a-bios-boot-partition-and-an-efi-system-partition">2. Creating a GPT with a BIOS boot partition and an EFI System Partition</h2>
<p>Now that we have the Linux root file system in the <code>chroot/</code> folder, we&rsquo;ll create a sparse file that represents our disk. You may of course do all this with a proper HDD/SSD, i.e. with <code>/dev/sdX</code>, but for testing things using a raw disk file is much easier. The following snippet will create 3 partitions:</p>
<ul>
<li>A 1 MB <a href="https://en.wikipedia.org/wiki/BIOS_boot_partition">BIOS boot partition</a> (of type <code>ef02</code>) that Grub will use to store its core image.</li>
<li>A 100 MB <a href="https://en.wikipedia.org/wiki/EFI_system_partition">EFI System Partition</a> (of type <code>ef00</code>) formatted as FAT32 in which we will store the EFI boot image</li>
<li>And a partition for our root file system (in our example formatted with ext4)</li>
</ul>
<p><em>Partitioning a disk/file with GPT to boot via UEFI and BIOS</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Create sparse file to represent our disk
</span></span><span class="line"><span class="cl">truncate --size 30G test.img
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create partition layout
</span></span><span class="line"><span class="cl">sgdisk --clear \
</span></span><span class="line"><span class="cl">  --new 1::+1M --typecode=1:ef02 --change-name=1:&#39;BIOS boot partition&#39; \
</span></span><span class="line"><span class="cl">  --new 2::+100M --typecode=2:ef00 --change-name=2:&#39;EFI System&#39; \
</span></span><span class="line"><span class="cl">  --new 3::-0 --typecode=3:8300 --change-name=3:&#39;Linux root filesystem&#39; \
</span></span><span class="line"><span class="cl">  test.img
</span></span></code></pre></div><p>Once this is done, you can list the partitions with <code>gdisk -l test.img</code>:</p>
<p><em>Listing the partitions we just created</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Now list the partitions
</span></span><span class="line"><span class="cl">gdisk -l test.img 
</span></span><span class="line"><span class="cl">GPT fdisk (gdisk) version 1.0.1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># ...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Number  Start (sector)    End (sector)  Size       Code  Name
</span></span><span class="line"><span class="cl">   1            2048            4095   1024.0 KiB  EF02  BIOS boot partition
</span></span><span class="line"><span class="cl">   2            4096          208895   100.0 MiB   EF00  EFI System
</span></span><span class="line"><span class="cl">   3          208896        62914526   29.9 GiB    8300  Linux root filesystem
</span></span></code></pre></div><p>After the partitions are created, the EFI and root partition need to be formatted:</p>
<p><em>Formatting the root partition and the EFI System Partition</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Loop sparse file
</span></span><span class="line"><span class="cl">LOOPDEV=$(losetup --find --show test.img)
</span></span><span class="line"><span class="cl">partprobe ${LOOPDEV}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create filesystems
</span></span><span class="line"><span class="cl">mkfs.fat -F32 ${LOOPDEV}p2
</span></span><span class="line"><span class="cl">mkfs.ext4 -F -L &#34;demoroot&#34; ${LOOPDEV}p3 # &lt;&lt; Note the label &#39;demoroot&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Get rid of loop device
</span></span><span class="line"><span class="cl">losetup -d ${LOOPDEV}
</span></span></code></pre></div><p>Note that I named the root partition <code>demoroot</code> (the ext4 label). This will be important later in the Grub configuration.</p>
<h2 id="3-copying-the-root-file-system-installing-grub-into-the-bios-partition">3. Copying the root file system, installing grub into the BIOS partition</h2>
<p>Now that the partition to be used for our root file system is formatted, let&rsquo;s copy our <code>chroot/</code> directory to it and then install grub to the disk with <code>grub-install</code>. Because the disk is GPT formatted, grub will use the BIOS partition to install its core image:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Loop sparse file
</span></span><span class="line"><span class="cl">LOOPDEV=$(losetup --find --show test.img)
</span></span><span class="line"><span class="cl">partprobe ${LOOPDEV}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Mount OS partition and copy from chroot
</span></span><span class="line"><span class="cl">MOUNTDIR=$(mktemp -d -t demoXXXXXX)
</span></span><span class="line"><span class="cl">mount ${LOOPDEV}p3 $MOUNTDIR
</span></span><span class="line"><span class="cl">rsync -a chroot/ ${MOUNTDIR}/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Install grub, create config
</span></span><span class="line"><span class="cl">for d in dev sys proc; do mount --bind /$d ${MOUNTDIR}/$d; done
</span></span><span class="line"><span class="cl">chroot ${MOUNTDIR}/ grub-install --modules=&#34;ext2 part_gpt&#34; ${LOOPDEV}
</span></span><span class="line"><span class="cl">chroot ${MOUNTDIR}/ update-grub
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Unmount OS partition
</span></span><span class="line"><span class="cl">umount $MOUNTDIR/{dev,proc,sys,}
</span></span><span class="line"><span class="cl">rmdir $MOUNTDIR
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Remove loop
</span></span><span class="line"><span class="cl">losetup -d ${LOOPDEV}
</span></span></code></pre></div><p>After this, you should be able to boot the system via BIOS from the root GPT partition. I always do that via KVM like this:</p>
<p><em>Test booting the image via KVM</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">kvm -drive format=raw,file=test.img -serial stdio -m 4G -cpu host -smp 2
</span></span></code></pre></div><h2 id="4-preparing-the-efi-partition">4. Preparing the EFI partition</h2>
<p>After you&rsquo;ve verified that you can boot via BIOS, let&rsquo;s make sure that we can also boot on UEFI systems. We formatted the EFI partition earlier. All that&rsquo;s remaining is to create/copy the <code>bootx64.efi</code> image file and a valid <code>grub.cfg</code> to it.</p>
<p>Grub can create the EFI image via the <code>grub-mkimage</code> command using the Grub modules in <code>/usr/lib/grub/x86_64-efi</code> (part of the <code>grub-efi-amd64-bin</code> package). In order to avoid having to load anything from the file system, we include all the modules in the image. If you are not booted via EFI, you may need to install the <code>grub-efi-amd64-bin</code> package.</p>
<p>The Grub config file in the EFI partition <code>grub.cfg</code> is rather simple: It uses <code>search --label demoroot</code> to look for the root partition and then simply includes the actual grub config file via <code>configfile ...</code>.</p>
<p>Let&rsquo;s mount the EFI partition again, create the image and the grub config:</p>
<pre tabindex="0"><code># Loop sparse file
LOOPDEV=$(losetup --find --show test.img)
partprobe ${LOOPDEV}

# Mount EFI partition
MOUNTDIR=$(mktemp -d -t demoXXXXXX)
mount ${LOOPDEV}p2 $MOUNTDIR

# Create EFI boot image
apt-get install grub-efi-amd64-bin -y --force-yes

mkdir -p ${MOUNTDIR}/EFI/BOOT
grub-mkimage \
  -d /usr/lib/grub/x86_64-efi \
  -o ${MOUNTDIR}/EFI/BOOT/bootx64.efi \
  -p /efi/boot \
  -O x86_64-efi \
    fat iso9660 part_gpt part_msdos normal boot linux configfile loopback chain efifwsetup efi_gop \
    efi_uga ls search search_label search_fs_uuid search_fs_file gfxterm gfxterm_background \
    gfxterm_menu test all_video loadenv exfat ext2 ntfs btrfs hfsplus udf

# Create grub config
cat &lt;&lt;GRUBCFG &gt; ${MOUNTDIR}/EFI/BOOT/grub.cfg
search --label demoroot --set prefix # &lt;&lt; Note again, this searches by the &#39;demoroot&#39; label!
configfile (\$prefix)/boot/grub/grub.cfg
GRUBCFG

# Unmount and clean up
umount ${MOUNTDIR}
losetup -d ${LOOPDEV}
</code></pre><p>After that, the EFI partition should contain the two files <code>/EFI/BOOT/bootx64.efi</code> and <code>/EFI/BOOT/grub.cfg</code>. You can verify that like this:</p>
<pre tabindex="0"><code># Loop sparse file
LOOPDEV=$(losetup --find --show test.img)
partprobe ${LOOPDEV}
 
# Mount EFI partition
MOUNTDIR=$(mktemp -d -t demoXXXXXX)
mount ${LOOPDEV}p2 $MOUNTDIR

# List things
(cd $MOUNTDIR; find .)

# Should output this:
# ./EFI
# ./EFI/BOOT
# ./EFI/BOOT/bootx64.efi
# ./EFI/BOOT/grub.cfg

# Unmount and clean up
umount ${MOUNTDIR}
losetup -d ${LOOPDEV}
rmdir ${MOUNTDIR}
</code></pre><p>That&rsquo;s it. You should now be able to boot this image via UEFI. I always test this with KVM and <a href="http://www.tianocore.org/ovmf/">OVMF</a>:</p>
<p><em>Booting the image via UEFI/EFI</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">apt-get install ovmf -y --force-yes
</span></span><span class="line"><span class="cl">kvm --bios /usr/share/qemu/OVMF.fd -net none -drive format=raw,file=test.img -serial stdio -m 4G -cpu host -smp 2
</span></span></code></pre></div><h2 id="5-one-script-to-do-it-all">5. One script to do it all</h2>
<p>If you are an impatient man/woman, then you may just want to run the following script to do all of this in one go. You&rsquo;ll of course have to adjust it to your needs for production use.</p>
<p>The script can be called in two ways:</p>
<p><em>Examples on how to use the mkbiosefi script</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Makes a BIOS/UEFI bootable test.img using &#34;demoroot&#34; as OS partition label
</span></span><span class="line"><span class="cl"># and create a minimal chroot via debootstrap in the &#34;chroot/&#34; folder to do so.
</span></span><span class="line"><span class="cl">./mkbiosefi test.img demoroot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Makes a BIOS/UEFI bootable test.img using &#34;demoroot&#34; as OS partition label,
</span></span><span class="line"><span class="cl"># but re-uses the existing &#34;chroot/&#34; folder (this is faster!)
</span></span><span class="line"><span class="cl">./mkbiosefi test.img demoroot chroot/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># If you want to use your own image file, you may create it beforehand and loop it
</span></span><span class="line"><span class="cl">truncate -s 40G my.img
</span></span><span class="line"><span class="cl">myloop=$(losetup --find --show my.img)
</span></span><span class="line"><span class="cl">./mkbiosefi $myloop demo chroot/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Or use a raw disk, of course
</span></span><span class="line"><span class="cl">./mkbiosefi /dev/sdX demo chroot/
</span></span></code></pre></div><p>Here&rsquo;s the script:</p>
<p><em>mkbiosefi script that can be used to create a BIOS/UEFI bootable disk/image</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">#!/bin/bash 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">DISK=$1
</span></span><span class="line"><span class="cl">BOOTLABEL=$2
</span></span><span class="line"><span class="cl">ROOTDIR=$3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">if [ -z &#34;$DISK&#34; -o -z &#34;$BOOTLABEL&#34; ]; then
</span></span><span class="line"><span class="cl">  echo &#34;Syntax: $0 &lt;image|disk&gt; &lt;root-label&gt; [&lt;chroot-dir&gt;]&#34;
</span></span><span class="line"><span class="cl">  exit 1
</span></span><span class="line"><span class="cl">fi
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">if [ &#34;$UID&#34; != &#34;0&#34; ]; then
</span></span><span class="line"><span class="cl">  echo &#34;Must be root.&#34;
</span></span><span class="line"><span class="cl">  exit 1
</span></span><span class="line"><span class="cl">fi
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Exit on errors
</span></span><span class="line"><span class="cl">set -xe
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Install dependencies
</span></span><span class="line"><span class="cl">apt-get install -y --force-yes \
</span></span><span class="line"><span class="cl">  debootstrap \
</span></span><span class="line"><span class="cl">  gdisk \
</span></span><span class="line"><span class="cl">  rsync \
</span></span><span class="line"><span class="cl">  grub-efi-amd64-bin \
</span></span><span class="line"><span class="cl">  e2fsprogs
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create chroot (if requested)
</span></span><span class="line"><span class="cl">if [ -z &#34;$ROOTDIR&#34; ]; then
</span></span><span class="line"><span class="cl">  ROOTDIR=chroot/
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  # Bootstrap minimal system
</span></span><span class="line"><span class="cl">  debootstrap --variant=minbase xenial chroot
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">  # Install kernel and grub
</span></span><span class="line"><span class="cl">  for d in dev sys proc; do mount --bind /$d chroot/$d; done
</span></span><span class="line"><span class="cl">  DEBIAN_FRONTEND=noninteractive chroot chroot apt-get install linux-image-generic grub-pc -y --force-yes
</span></span><span class="line"><span class="cl">  umount chroot/{dev,proc,sys}
</span></span><span class="line"><span class="cl">fi
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create sparse file (if we&#39;re not dealing with a block device)
</span></span><span class="line"><span class="cl">if [ ! -b &#34;${DISK}&#34; ]; then
</span></span><span class="line"><span class="cl">  truncate --size 30G $DISK
</span></span><span class="line"><span class="cl">fi
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create partition layout
</span></span><span class="line"><span class="cl">sgdisk --clear \
</span></span><span class="line"><span class="cl">  --new 1::+1M --typecode=1:ef02 --change-name=1:&#39;BIOS boot partition&#39; \
</span></span><span class="line"><span class="cl">  --new 2::+100M --typecode=2:ef00 --change-name=2:&#39;EFI System&#39; \
</span></span><span class="line"><span class="cl">  --new 3::-0 --typecode=3:8300 --change-name=3:&#39;Linux root filesystem&#39; \
</span></span><span class="line"><span class="cl">  $DISK 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Loop sparse file
</span></span><span class="line"><span class="cl">LOOPDEV=$(losetup --find --show $DISK)
</span></span><span class="line"><span class="cl">partprobe ${LOOPDEV}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create filesystems
</span></span><span class="line"><span class="cl">mkfs.fat -F32 ${LOOPDEV}p2
</span></span><span class="line"><span class="cl">mkfs.ext4 -F -L &#34;${BOOTLABEL}&#34; ${LOOPDEV}p3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Mount OS partition, copy chroot, install grub
</span></span><span class="line"><span class="cl">MOUNTDIR=$(mktemp -d -t demoXXXXXX)
</span></span><span class="line"><span class="cl">mount ${LOOPDEV}p3 ${MOUNTDIR}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">rsync -a ${ROOTDIR}/ ${MOUNTDIR}/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">for d in dev sys proc; do mount --bind /$d ${MOUNTDIR}/$d; done
</span></span><span class="line"><span class="cl">chroot ${MOUNTDIR}/ grub-install --modules=&#34;ext2 part_gpt&#34; ${LOOPDEV}
</span></span><span class="line"><span class="cl">chroot ${MOUNTDIR}/ update-grub
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">umount $MOUNTDIR/{dev,proc,sys,}
</span></span><span class="line"><span class="cl">rmdir $MOUNTDIR
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Mount EFI partition
</span></span><span class="line"><span class="cl">MOUNTDIR=$(mktemp -d -t demoXXXXXX)
</span></span><span class="line"><span class="cl">mount ${LOOPDEV}p2 $MOUNTDIR
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">mkdir -p ${MOUNTDIR}/EFI/BOOT
</span></span><span class="line"><span class="cl">grub-mkimage \
</span></span><span class="line"><span class="cl">  -d /usr/lib/grub/x86_64-efi \
</span></span><span class="line"><span class="cl">  -o ${MOUNTDIR}/EFI/BOOT/bootx64.efi \
</span></span><span class="line"><span class="cl">  -p /efi/boot \
</span></span><span class="line"><span class="cl">  -O x86_64-efi \
</span></span><span class="line"><span class="cl">    fat iso9660 part_gpt part_msdos normal boot linux configfile loopback chain efifwsetup efi_gop \
</span></span><span class="line"><span class="cl">    efi_uga ls search search_label search_fs_uuid search_fs_file gfxterm gfxterm_background \
</span></span><span class="line"><span class="cl">    gfxterm_menu test all_video loadenv exfat ext2 ntfs btrfs hfsplus udf
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"># Create grub config
</span></span><span class="line"><span class="cl">cat &lt;&lt;GRUBCFG &gt; ${MOUNTDIR}/EFI/BOOT/grub.cfg
</span></span><span class="line"><span class="cl">search --label &#34;${BOOTLABEL}&#34; --set prefix
</span></span><span class="line"><span class="cl">configfile (\$prefix)/boot/grub/grub.cfg
</span></span><span class="line"><span class="cl">GRUBCFG
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">umount $MOUNTDIR
</span></span><span class="line"><span class="cl">rmdir $MOUNTDIR
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Remove loop device
</span></span><span class="line"><span class="cl">sync ${LOOPDEV} 
</span></span><span class="line"><span class="cl">losetup -d ${LOOPDEV}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">echo &#34;Done. ${DISK} is ready to be booted via BIOS and UEFI.&#34;
</span></span></code></pre></div>]]></content:encoded></item><item><title>How-To: Using ZFS Encryption at Rest in OpenZFS (ZFS on Linux, ZFS on FreeBSD, ...)</title><link>https://heckel.io/blog/zfs-encryption-openzfs-zfs-on-linux/</link><pubDate>Sun, 08 Jan 2017 22:21:07 -0500</pubDate><guid>https://heckel.io/blog/zfs-encryption-openzfs-zfs-on-linux/</guid><description>An upcoming feature of OpenZFS (and ZFS on Linux, ZFS on FreeBSD, &amp;hellip;) is At-Rest Encryption, a feature that allows you to securely encrypt your ZFS file systems and volumes without having to provide an extra layer of devmappers and such. To give you a brief overview of what the feature can do, …</description><content:encoded><![CDATA[<p>An <a href="http://open-zfs.org/wiki/Roadmap">upcoming feature</a> of <a href="http://open-zfs.org/">OpenZFS</a> (and <a href="http://zfsonlinux.org/">ZFS on Linux</a>, ZFS on FreeBSD, &hellip;) is <strong>At-Rest Encryption</strong>, a feature that allows you to <strong>securely encrypt your ZFS file systems and volumes</strong> without having to provide an extra layer of devmappers and such. To give you a brief overview of what the feature can do, I thought I&rsquo;d write a short post about it.</p>
<p>The current ZFS encryption implementation is not (yet) merged into the upstream repository (as of January 2017). There is a <a href="https://github.com/zfsonlinux/zfs/pull/4329">pretty big pull request</a> which is still being reviewed, but because the feature is so incredibly cool (and because my colleague <a href="https://github.com/tcaputi">Tom Caputi</a> developed it), I thought a sneak preview is absolutely necessary.</p>
<h2 id="0-this-post">0. This post</h2>
<p>This post demonstrates a feature that has <strong>not yet been released</strong>. For the demos I will focus on <strong>ZFS on Linux</strong> on an <strong>Ubuntu 16.04</strong> based machine. The code I run is available in the official repos (links below) or in my forks (<a href="https://github.com/binwiederhier/spl">SPL</a> &amp; <a href="https://github.com/binwiederhier/zfs">ZFS</a>, use branch &ldquo;blogpost&rdquo; for both).</p>
<h2 id="1-introduction">1. Introduction</h2>
<p><a href="https://en.wikipedia.org/wiki/Data_at_rest">At-rest</a> encryption is a new feature in ZFS (<code>zpool set feature@encryption=enabled &lt;pool&gt;</code>) that will automatically encrypt almost all data written to disk using modern <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated ciphers (AEAD)</a> such as AES-CCM and AES-GCM.</p>
<p>The CLI makes it incredibly easy to enable encryption on a per dataset/volume basis (<code>zfs create -o encryption=on &lt;dataset&gt;</code>). The keys used for encryption can be inherited or are manually set for a dataset. Keys can be loaded from different sources (prompt or file) and various input formats are available (raw, hex or passphrase). Keys and key sources can be changed after the dataset/volume creation, and without re-encrypting the data (as they are never used directly).</p>
<p>The encryption parameters and key status of a dataset/volume are represented in various properties (<code>encryption=&lt;on|aes-128-gcm|...&gt;</code>, <code>keysource=&lt;raw|hex|passphrase&gt;,&lt;prompt|file&gt;</code>, <code>keystatus=&lt;none|available|unavailable&gt;</code>, <code>pbkdf2iters=&lt;n&gt;</code>).</p>
<p>Many normal ZFS commands are available even if the key of a dataset is not loaded, meaning that administrators can manage the pool without having to know the keys. For instance: a pool can be scrubbed (<code>zpool scrub &lt;pool&gt;</code>) without the keys, and datasets and snapshots can be listed (<code>zfs list -rt</code>). In future releases, <code>zfs send</code> and <code>zfs recv</code> will also work even if the key is not available.</p>
<p>Having built-in support for encryption at the file system level is huge. It means that you no longer have to use <a href="https://en.wikipedia.org/wiki/Dm-crypt">dm-crypt</a> if you want to encrypt your data on disk, and you can still manage your pools even if keys are not loaded. <strong>Many thanks to<a href="https://github.com/tcaputi">Tom Caputi</a> for bringing us this incredible feature.</strong></p>
<h3 id="11-whats-encrypted">1.1. What&rsquo;s encrypted</h3>
<p>All important pieces are encrypted (actual data and metadata, ACLs, permissions, directory listings, &hellip;), while some things are unencrypted to allow managing pools more easily.</p>
<p>Here&rsquo;s a listing of what&rsquo;s encrypted and what&rsquo;s not:</p>
<table style="text-align: center">
<tr>
  <th style="text-align: left">Encrypted</th>
  <th style="text-align: left">Not Encrypted</th>
</tr>
<tr>
<td style="vertical-align: top; text-align: left">
<ul>
<li>File data and metadata
<li>ACLs, names, permissions, attrs
<li>Directory listings
<li>All Zvol data
<li>FUID Mappings
<li>Master encryption keys
<li>All of the above in the L2ARC
<li>All of the above in the ZIL
</ul>
</td>
<td style="vertical-align: top; text-align: left">
<ul>
<li>Dataset / snapshot names
<li>Dataset properties
<li>Pool layout
<li>ZFS Structure
<li>Dedup tables
<li>Everything in RAM
</ul>
</td>
</tr>
</table>
<h3 id="12-crypto-details">1.2. Crypto Details</h3>
<p><em>Note: This section describes the nitty gritty crypto details. You can safely skip it if you just want to use the feature.</em></p>
<p>Crypto concepts are always a bit hard to explain without confusing everyone. Tom has done an excellent job explaining the ZFS encryption crypto concept in <a href="https://www.youtube.com/watch?v=frnLiXclAMo">his talk</a> and it is visualized very nicely in <a href="/uploads/2017/01/zfs-encryption-nov-2016.pdf">his slides</a> (PDF; or on Google Drive: <a href="https://docs.google.com/presentation/d/1cL4zvPxqyi2UjmTMFa-QnuIraqWeDmi5agXzhcFAFq0/edit#slide=id.g1718e7210a_0_28">original</a>, <a href="https://docs.google.com/presentation/d/1fFWNfoyH-ztnmy1IeACdbc-aV9lTyAM-AcQYi9UciO4/edit#slide=id.g1188ea2d58_0_6">mirror</a>).</p>
<p>If you don&rsquo;t have the time to watch the entire talk, let me try to summarize the concepts one of his slides:</p>
<p><img src="/uploads/2017/01/Selection_060.jpg" alt=""></p>
<p><strong>Normal / non-dedup case</strong>: Before the plaintext block data (or metadata) is written, it is encrypted using <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES</a> (in <a href="https://en.wikipedia.org/wiki/CCM_mode">CCM</a> or <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode">GCM</a> mode, depending on the <code>-o encryption=..</code> property) with a 128/192/256-bit encryption key (default is AES-CCM-256). The 96-bit <a href="https://en.wikipedia.org/wiki/Initialization_vector">initialization vector (IV)</a> used for CCM/GCM is randomly generated using the <a href="http://www.2uo.de/myths-about-urandom/">standard linux PRNG</a>, and it is never reused. The encryption key itself is derived from the encrypted master key (see below) using the key derivation function <a href="https://tools.ietf.org/html/rfc5869">HKDF</a>. The 64-bit salt used for HKDF is randomly generated (using the above mentioned PRNG) and stored with the encryption key in a volatile salt cache. The encryption key is reused (for performance reasons) until it goes stale.</p>
<p><strong>Dedup case</strong>: If deduplication is enabled, the algorithm behaves slightly differently, because it has to produce the same ciphertext for the same plaintext (given the same master key). To achieve that, the salt and the IV are not randomly generated, but instead a 160-bit HMAC of the plaintext is used: the first 64 bits are used as the salt, the remaining 128 bits are used as IV. The 256-bit HMAC key is randomly generated (using above mentioned PRNG), and stored alongside the master key.</p>
<p><strong>Master key</strong>: The master key is randomly generated (using above mentioned PRNG) and it is never exposed to the user directly. Instead, the master key is encrypted (with the same cipher and mode with a 256-bit key) using a user provided wrapping key. This wrapping key is provided via a file (as hex or raw, see <code>-o keysource=..</code> property) or via a password prompt. If a passphrase is supplied by the user, the wrapping key is derived using the password-based key derivation function PBKDF2 (using 100k iterations by default, or whatever you specify in the property <code>-o pbkdf2iters=..</code>).</p>
<p>If you want to know more, I highly suggest watching <a href="https://www.youtube.com/watch?v=frnLiXclAMo"><strong>Tom Caputi&rsquo;s ZFS encryption talk</strong></a>, or reviewing <a href="/uploads/2017/01/zfs-encryption-nov-2016.pdf">the slides</a> (PDF; or on Google Drive: <a href="https://docs.google.com/presentation/d/1cL4zvPxqyi2UjmTMFa-QnuIraqWeDmi5agXzhcFAFq0/edit#slide=id.g1718e7210a_0_28">original</a>, <a href="https://docs.google.com/presentation/d/1fFWNfoyH-ztnmy1IeACdbc-aV9lTyAM-AcQYi9UciO4/edit#slide=id.g1188ea2d58_0_6">mirror</a>).</p>
<p><a href="https://www.youtube.com/watch?v=frnLiXclAMo">https://www.youtube.com/watch?v=frnLiXclAMo</a></p>
<h2 id="2-using-zfs-encryption">2. Using ZFS encryption</h2>
<p>Using the encryption feature is pretty simple. All the relevant commands and properties are described in great detail in the ZFS man page (<code>man zfs</code>), but here&rsquo;s an excerpt of what you need to know.</p>
<h3 id="21-enabling-the-feature-on-the-pool">2.1. Enabling the feature on the pool</h3>
<p>Assuming you have installed a version of ZFS with encryption installed (<strong>if not, follow the steps in sectioncompile and install at your own risk</strong>), you need to turn it on for your pool. I&rsquo;ll create a test pool called <code>testpool</code> for this post:</p>
<p><em>Creating a test pool, and turning on the encryption feature</em></p>
<pre tabindex="0"><code># Creating a test pool
$ truncate -s 1G block
$ zpool create testpool $(pwd)/block

# Turning on encryption (you NEED a ZFS version that supports this!)
$ zpool set feature@encryption=enabled testpool
</code></pre><h3 id="22-creating-an-encrypted-dataset">2.2. Creating an encrypted dataset</h3>
<p>Once you&rsquo;ve enabled the feature on the pool, you can create encrypted datasets and volumes. To do that, you need to pass the two properties <code>-o encryption=.. -o keysource=..</code> to the <code>zfs create</code> command. Depending on your preferences, you may also pass <code>-o pbkdf2iters=..</code>:</p>
<p><em>Creating an encrypted dataset with &lsquo;zfs create&rsquo;</em></p>
<pre tabindex="0"><code>$ zfs create \
    -o encryption=&lt;off | on | aes-128-ccm | aes-192-ccm | aes-256-ccm | aes-128-gcm | aes-192-gcm | aes-256-gcm&gt; \
    -o keysource=&lt;raw | hex | passphrase&gt;,&lt;prompt | file://...&gt; \
    -o pbkdf2iters=&lt;n&gt; \
    &lt;dataset&gt;
</code></pre><p>The <code>-o encryption=..</code> property controls the ciphersuite (cipher, key length and mode). The default is <code>aes-256-ccm</code>, which is used if you specify <code>-o encryption=on</code>.</p>
<p>The <code>-o keysource=..</code> property controls what format the encryption key will be provided as and where it should be loaded from. The key can be formatted as raw bytes, as hex representation or as a user password. It can be provided via a user prompt which will pop up when you first create it, or when you mount the dataset (<code>zfs mount</code>) or load the key manually (<code>zfs key -l</code>). Unless you want to automate things, <code>-o keysource=passphrase,prompt</code> seems like a good option.</p>
<p>The <code>-o pbkdf2iters=..</code> property is only used if a passphrase is used (<code>-o keysource=passphrase,..</code>). It controls the iterations of PBKDF2. Higher is better as it slows down potential dictionary attacks on the password. The default is <code>-o pbkdf2iters=100000</code>.</p>
<p>Here are a few <strong>examples</strong> of how to create encrypted datasets and volumes (ZVOLs):</p>
<p>Creating an encrypted dataset, using the defaults:</p>
<p><em>Creating an encrypted dataset, using the defaults</em></p>
<pre tabindex="0"><code>$ zfs create \
    -o encryption=on \
    -o keysource=passphrase,prompt \
    testpool/enc1
  
# This will ask you to enter/confirm a password.
</code></pre><p>Creating an encrypted child dataset, which inherits all parameters and keys from its parent:</p>
<p><em>Creating an encrypted child dataset, inheriting params and keys</em></p>
<pre tabindex="0"><code>$ zfs create testpool/enc1/encinherit
</code></pre><p>Creating an encrypted dataset, using AES/GCM with 128-bit key loaded from a file (encoded as hex):</p>
<p><em>Creating an encrypted dataset, and loading the key from a file</em></p>
<pre tabindex="0"><code>  
$ echo 0000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF &gt; /dev/shm/enc2key
$ zfs create \
    -o encryption=aes-128-gcm \
    -o keysource=hex,file:///dev/shm/enc2key \
    testpool/enc2
# No prompts for a password!
</code></pre><p>Creating an encrypted dataset, using AES/GCM with a 256-bit key loaded from a file (not encoded):</p>
<pre tabindex="0"><code>$ head -c 32 /dev/urandom &gt; /dev/shm/enc3key
$ zfs create \
    -o encryption=aes-256-gcm \
    -o keysource=raw,file:///dev/shm/enc3key \
    testpool/enc3
</code></pre><p>Creating an encrypted ZFS volume (ZVOL), using the defaults with one million PBKDF2 rounds:</p>
<pre tabindex="0"><code>$ zfs create \
    -V 10M \
    -o encryption=on \
    -o keysource=passphrase,prompt \
    -o pbkdf2iters=1000000 \
    testpool/enc4
    
# This will ask you to enter/confirm a password.    
</code></pre><h3 id="23-reading-the-encryption-properties">2.3. Reading the encryption properties</h3>
<p>Once you&rsquo;ve created a dataset or volume, you can query its encryption properties like you normally would:</p>
<p><em>Querying the encryption parameters</em></p>
<pre tabindex="0"><code>$ zfs get -p encryption,keystatus,keysource,pbkdf2iters
testpool/enc1             encryption   aes-256-ccm                  local
testpool/enc1             keystatus    available                    -
testpool/enc1             keysource    passphrase,prompt            local
testpool/enc1             pbkdf2iters  100000                       local
testpool/enc1/encinherit  encryption   aes-256-ccm                  inherited from testpool/enc1
testpool/enc1/encinherit  keystatus    available                    -
testpool/enc1/encinherit  keysource    passphrase,prompt            inherited from testpool/enc1
testpool/enc1/encinherit  pbkdf2iters  100000                       inherited from testpool/enc1
testpool/enc2             encryption   aes-128-gcm                  local
testpool/enc2             keystatus    available                    -
testpool/enc2             keysource    hex,file:///dev/shm/enc2key  local
testpool/enc2             pbkdf2iters  1000000                      local
testpool/enc3             encryption   aes-256-gcm                  local
testpool/enc3             keystatus    available                    -
testpool/enc3             keysource    raw,file:///dev/shm/enc3key  local
testpool/enc3             pbkdf2iters  0                            -
testpool/enc4             encryption   aes-256-ccm                  local
testpool/enc4             keystatus    available                    -
testpool/enc4             keysource    passphrase,prompt            local
testpool/enc4             pbkdf2iters  1000000                      local
</code></pre><h3 id="24-importing-a-pool-mounting-datasets-and-loading-keys">2.4. Importing a pool, mounting datasets and loading keys</h3>
<p>If a dataset is encrypted, the read-only property <code>keystatus</code> represents the status of the key, and thereby also whether the dataset can be used (mounted, written to, read from &hellip;). It can be either <code>off</code> (unencrypted dataset), <code>available</code> (the key is loaded) or <code>unavailable</code> (the key is not loaded).</p>
<p>When a pool is imported using <code>zpool import</code>, encrypted datasets are left unmounted, because their keys are not automatically loaded. Only if the <code>-l</code> option is passed will encrypted datasets be loaded (if they can):</p>
<p><em>Importing a pool with -l to load the keys</em></p>
<pre tabindex="0"><code>$ zpool import testpool -d . -l
Enter passphrase for &#39;testpool/enc1&#39;: (enter password)
Key load error: Failed to open key material file  # &lt;&lt; Key file does not exist!
Enter passphrase for &#39;testpool/enc4&#39;: (enter password)
</code></pre><p>Instead of using <code>zpool import -l ...</code>, you can manually load the keys for individual datasets and volumes using <code>zfs key -l</code>:</p>
<p><em>Manually loading and unloading a key using &lsquo;zfs key -l&rsquo;</em></p>
<pre tabindex="0"><code>$ zfs key -l testpool/enc4
Enter passphrase for &#39;testpool/enc4&#39;: (enter password)

$ zfs mount testpool/enc4
# Does not prompt. Just mounts!
</code></pre><p>If <code>zfs mount</code> is called on an encrypted dataset with unavailable key, it will prompt you:</p>
<p><em>Mounting an encrypted dataset with unavailable key will prompt</em></p>
<pre tabindex="0"><code>$ zfs mount testpool/enc4
Enter passphrase for &#39;testpool/enc4&#39;: (enter password)
</code></pre><p>Unloading a key of a mounted dataset won&rsquo;t work, because it&rsquo;s still in use. The dataset has to be unmounted first:</p>
<p><em>Unloading a key only works if the dataset is not mounted</em></p>
<pre tabindex="0"><code># Does not work because dataset is mounted
$ zfs key -u testpool/enc4
Key unload error: Dataset is busy.

# Works like a charm
$ zfs umount testpool/enc4
$ zfs key -u testpool/enc4
</code></pre><p>That&rsquo;s essentially all the magic. If you want to know more, I suggest reading the ZFS man page (<code>man zfs</code>).</p>
<h2 id="3-compile-and-install">3. Compile and install</h2>
<p>If you want to try the current implementation (before it is released), here are a few steps to compile and install it yourself on an Ubuntu 16.04-based system. Other systems will be very similar, but not identical. You can consult the <a href="https://github.com/zfsonlinux/zfs/wiki/Building-ZFS">Building ZFS</a> wiki page for details.</p>
<p><strong>Warning:</strong> Please be sure to only perform these steps on a <strong>test machine</strong> or a throw-away VM, because this will replace the ZFS kernel modules.</p>
<p>First, install all the build dependencies:</p>
<p><em>Installing build dependencies</em></p>
<pre tabindex="0"><code>apt install libtool zlib1g-dev attr uuid-dev libblkid-dev libattr1-dev autoconf
</code></pre><p>Once that&rsquo;s done, compile and install SPL using the steps below. All relevant ZFS encryption pulls have been merged (as of January 2017), so this should &ldquo;just work&rdquo;. If it doesn&rsquo;t (e.g. because the code has changed; you may be reading this in the future &hellip;), you may want to use my forked version instead (see <a href="https://github.com/binwiederhier/spl">SPL</a> and <a href="https://github.com/binwiederhier/zfs">ZFS</a>, use branch &ldquo;blogpost&rdquo; for both):</p>
<p><em>Compiling and installing SPL</em></p>
<pre tabindex="0"><code># Compile and install SPL
#
# If this does not work anymore, use my forked version instead:
#   $ git clone https://github.com/binwiederhier/spl.git
#   $ git checkout blogpost
#
git clone https://github.com/zfsonlinux/spl.git
cd spl
./autogen.sh
./configure
make
make install
cd ..
</code></pre><p>Next, compile and install ZFS using <a href="https://github.com/tcaputi/zfs">Tom&rsquo;s fork</a>. If <a href="https://github.com/zfsonlinux/zfs/pull/4329">the ZFS encryption pull request</a> has been merged, you may just want to use the <a href="https://github.com/zfsonlinux/zfs">upstream master branch</a>:</p>
<p><em>Compile and install ZFS from Tom&rsquo;s fork (with the encryption code)</em></p>
<pre tabindex="0"><code># Compile and install ZFS (Tom&#39;s fork)
#
# If this does not work anymore, use my forked version instead:
#   $ git clone https://github.com/binwiederhier/zfs.git
#   $ git checkout blogpost
#
git clone https://github.com/tcaputi/zfs.git
cd zfs
./autogen.sh
./configure --prefix=/usr  # &lt;&lt; Don&#39;t forget the --prefix
make install
cd ..
</code></pre><p>Now the ZFS modules should be built in <code>/lib/modules/$(uname -r)/extra</code>, so all you have to do is load them. Be sure to remove the old modules first:</p>
<p><em>Removing the old modules, inserting the new modules</em></p>
<pre tabindex="0"><code># Remove existing modules (repeat until successful!)
modprobe -r splat
modprobe -r zavl
modprobe -r zcommon
modprobe -r zunicode
modprobe -r znvpair
modprobe -r icp
modprobe -r spl
modprobe -r zfs

# Insert newly compiled modules (all of these must succeed!)
cd /lib/modules/$(uname -r)/extra
insmod avl/zavl.ko 
insmod unicode/zunicode.ko 
insmod spl/spl.ko
insmod nvpair/znvpair.ko 
insmod zcommon/zcommon.ko 
insmod icp/icp.ko 
insmod zfs/zfs.ko
</code></pre><p>If that succeeded, be sure to yell &ldquo;hooray&rdquo; before you move on, because these steps took me a while to get right when I did it for the first time.</p>
<p>You can now use the feature (as described above).</p>
<h2 id="4-faq">4. FAQ</h2>
<h3 id="41-is-deduplication-supported">4.1. Is deduplication supported?</h3>
<p>Yes, it is supported. However, there is some information that is leaked due to the nature of deduplication. See more in Tom&rsquo;s talk.</p>
<h3 id="42-can-i-change-the-password-will-data-be-re-encrypted">4.2. Can I change the password? Will data be re-encrypted?</h3>
<p>Yes, the password can be changed. The data does not get re-encrypted, because the password is merely used to decrypt a master key.</p>
<h3 id="43-does-it-work-with-tpm">4.3. Does it work with TPM?</h3>
<p>No, not yet.</p>
<h2 id="5links">5.Links</h2>
<ul>
<li><a href="http://open-zfs.org/wiki/Roadmap">OpenZFS Roadmap (with ZFS encryption on it)</a></li>
<li><a href="https://news.ycombinator.com/item?id=12131239">Hacker News discussion about ZFS encryption</a></li>
<li><a href="https://github.com/zfsonlinux/zfs/pull/4329">GitHub pull request for ZFS encryption (ZFS on Linux)</a></li>
<li><a href="https://github.com/openzfs/openzfs/pull/124">GitHub pull request for ZFS encryption (OpenZFS)</a></li>
<li><a href="https://www.youtube.com/watch?v=frnLiXclAMo">Talk at OpenZFS Developer Summit by Tom Caputi about ZFS encryption (video)</a></li>
<li><a href="https://github.com/zfsonlinux/zfs/wiki/Building-ZFS">Instructions on how to compile / build ZFS</a></li>
<li><a href="/uploads/2017/01/zfs-encryption-nov-2016.pdf">Slides used for the talk at the OpenZFS Developer Summit</a> (PDF; or on Google Drive: <a href="https://docs.google.com/presentation/d/1cL4zvPxqyi2UjmTMFa-QnuIraqWeDmi5agXzhcFAFq0/edit#slide=id.g1718e7210a_0_28">original</a>, <a href="https://docs.google.com/presentation/d/1fFWNfoyH-ztnmy1IeACdbc-aV9lTyAM-AcQYi9UciO4/edit#slide=id.g1188ea2d58_0_6">mirror</a>)</li>
</ul>]]></content:encoded></item><item><title>zfsu: ZFS utils for offsite backup, retention and maintaining a slow mirror</title><link>https://heckel.io/blog/zfs-utils-offsite-backup-retention/</link><pubDate>Sun, 01 Jan 2017 17:50:15 -0500</pubDate><guid>https://heckel.io/blog/zfs-utils-offsite-backup-retention/</guid><description>My laptop runs ZFS as its root file system (see this blog post) &amp;ndash; meaning that I can snapshot my root file system and I can send it to another machine as a backup very easily. Unfortunately, while ZFS provides the raw functionality, there is no great tool to manage offsite backups and …</description><content:encoded><![CDATA[<p>My laptop runs ZFS as its root file system (see <a href="/blog/move-existing-linux-install-zfs-root/">this blog post</a>) &ndash; meaning that I can snapshot my root file system and I can send it to another machine as a backup very easily. Unfortunately, while ZFS provides the raw functionality, there is no great tool to manage offsite backups and retention. To ease this pain, I wrote/forked and packaged a few helper scripts which I called <strong>zfsu</strong>, a collection of ZFS utilities.</p>
<p>It consists of the following tools: <strong>zfsu tx</strong> (aka zfstx) maintains a mirror of a ZFS pool over the network. <strong>zfsu ret</strong> (aka zfsret) is a simple script to apply local retention (destroy snapshots) of a file system and its snapshots. <strong>zfsu res</strong> (aka zfsres) is a script to resilver a slow mirror, e.g. a HDD disk if mirrored with a SSD.</p>
<h2 id="0-code-available-on-github">0. Code available on GitHub</h2>
<p>All the code for this post is available in a <a href="https://github.com/binwiederhier/zfsu">zfsu Project</a> on GitHub. Feel free to poke around there and/or steal bits and pieces from it.</p>
<h2 id="1-installation">1. Installation</h2>
<p>I host a package of the scripts in my Debian repository, so if you are Debian/Ubuntu based, feel free to install it like this:</p>
<p><em>Installing zfsu on Debian/Ubuntu</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Add my repository</span>
</span></span><span class="line"><span class="cl">wget -qO - http://archive.philippheckel.com/apt/Release.key <span class="p">|</span> sudo apt-key add -
</span></span><span class="line"><span class="cl">sudo sh -c <span class="s2">&#34;echo deb http://archive.philippheckel.com/apt/release/ release main &gt; /etc/apt/sources.list.d/archive.philippheckel.com.list&#34;</span>
</span></span><span class="line"><span class="cl">sudo apt-get update
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install zfsu</span>
</span></span><span class="line"><span class="cl">sudo apt-get install zfsu
</span></span></code></pre></div><p>If you&rsquo;re using a different system, simply download and place <a href="https://github.com/binwiederhier/zfsu/tree/master/files/usr/sbin">these scripts</a> in your PATH.</p>
<h2 id="2-zfsu-tx-aka-zfstx">2. zfsu tx (aka zfstx)</h2>
<p><strong>zfstx</strong> lets you <strong>pull</strong> ZFS snapshots from a remote host into the local zpool, meaning that you need to install this script on the backup machine itself, not on the machine that&rsquo;s being backed up. While this may seem strange at first, it&rsquo;s great if you&rsquo;re backing up many different machines, because you can manage the schedule and logic in one place.</p>
<p>The tool was originally developed by <a href="https://github.com/jvsalo/zfs_transfer">Jaako</a> as part of his zfs_transfer tool. I merely adjusted it a little and packaged it for my needs. Many thanks to him for this great tool.</p>
<h3 id="21-usage">2.1. Usage</h3>
<p><em>Usage of the zfstx tool</em></p>
<pre tabindex="0"><code>$ zfsu tx
Usage: zfstx [OPTIONS] &lt;remote-host&gt;:&lt;remote-fs&gt; &lt;local-fs&gt;
Pull ZFS snapshots from a remote host into the local zpool.

Arguments:
  &lt;remote-host&gt;            - Remote host, e.g. myhost
  &lt;remote-fs&gt;              - Filesystem on remote host, e.g. tank/home
  &lt;local-fs&gt;               - Filesystem on local host, e.g. backuppool/myhost/home

Options:
  -k, --keep &lt;count&gt;       - preserved history length
  -b, --mbuffer &lt;bufsz&gt;    - mbuffer buffer size, default 4G
  -p, --port &lt;port&gt;        - custom SSH port, default 22
  -P, --no-pigz            - disable pigz
  -n, --dry-run            - Don&#39;t apply changes, just print (experimental)
</code></pre><h3 id="22-examples">2.2. Examples</h3>
<p><em>Example usage of zfstx</em></p>
<pre tabindex="0"><code>$ zfstx platop:tank/home/pheckel tank/backups/platop/home/pheckel
  # Pull all (missing) snapshots from host &#34;platop&#34; into the local pool &#34;tank&#34;
  # and don&#39;t apply any retention.

$ zfstx --keep 5 platop:tank/home/pheckel/vms tank/backups/platop/home/pheckel/vms
  # Pull all (missing) snapshots from host &#34;platop&#34; into the local pool &#34;tank&#34;
  # and only keep the 5 latest snapshots locally.
</code></pre><h2 id="3-zfsu-ret-aka-zfsret">3. zfsu ret (aka zfsret)</h2>
<p><strong>zfsret</strong> is a simple script to apply local retention (destroy snapshots) of a file system and its snapshots. Its meant to complement the zfstx utility to only maintain a certain number of snapshots on the backup host. It can also be used to locally prune snapshots on demand, or regularly.</p>
<p>The retention logic is pretty simple as you can only specify how many of the last snapshots to keep. There is no sophisticated logic (yet) to keep X per week or Y per month.</p>
<h3 id="31-usage">3.1. Usage</h3>
<p><em>Usage of the zfsret tool</em></p>
<pre tabindex="0"><code>$ zfsu ret
Usage: zfsret [OPTIONS] &lt;local-fs&gt; &lt;keep&gt;
Destroy local ZFS snapshots for a specific filesystem.

Arguments:
  &lt;local-fs&gt;               - Filesystem on local host, e.g. backuppool/myhost/home
  &lt;keep&gt;                   - Number of snapshots to keep

Options:
  -n, --dry-run            - Don&#39;t apply changes, just print
</code></pre><h3 id="32-examples">3.2. Examples</h3>
<p><em>Example usage of zfstx</em></p>
<pre tabindex="0"><code>$ zfsu ret tank/home/pheckel 10
  # Destroy all but 10 snapshots of filesystem tank/home/pheckel
  # This is not recursive (no -r)!
</code></pre><h2 id="4-zfsu-res-aka-zfsres">4. zfsu res (aka zfsres)</h2>
<p><strong>zfsres</strong> is a tool specifically written for a ZFS mirror/RAID1 between a slow/HDD and a fast/SSD disk. zfsres will regularly resilver the slow HDD and thereby not make the array slow for normal write/read operations, but still allow for a RAID1 configuration of two disks of vastly different speeds.</p>
<p>This tool covers a pretty exotic use case. I wrote it because my laptop has a SSD and a HDD, and I wanted to keep a second copy on the HDD. Note that this tool only makes sense in this scenario.</p>
<h3 id="41-usage">4.1. Usage</h3>
<p><em>Usage of the zfsret tool</em></p>
<pre tabindex="0"><code>$ zfsu res
Usage: zfsres &lt;pool&gt; &lt;slow-mirror&gt;
Enable slow mirror(s) and wait for them to be resilvered and exit.

Arguments:
  &lt;pool&gt;           - Name of the ZFS pool, e.g. tank
  &lt;slow-mirror&gt;    - Description of a slow mirror device, e.g. wwn-0x50004cf20c41a05b
</code></pre><h3 id="42-examples">4.2. Examples</h3>
<p><em>Example usage of zfstx</em></p>
<pre tabindex="0"><code>$ zfsres tank wwn-0x50004cf20c41a05b
  # Will online the disk wwn-0x50004cf20c41a05b and wait for resilvering to 
  # complete, and then offline the disk again.
</code></pre><h2 id="5-example-my-setup">5. Example: My setup</h2>
<p>I use these scripts to back up 5 different machines, some of which have ZFS file systems, and others that do not. My backup hub&rsquo;s ZFS pool looks like this (shortened):</p>
<p><em>&lsquo;zfs list&rsquo; output of my backup hub ZFS pool (shortened)</em></p>
<pre tabindex="0"><code>tank/backups/laptop1  1.09T  1.29T  9.65G  /tank/backups/laptop1
tank/backups/box       207G  1.29T  9.51G  /tank/backups/box
tank/backups/box2     1.07G  1.29T   868M  /tank/backups/box2
tank/backups/laptop2   893G  1.29T  14.0G  /tank/backups/laptop2
tank/backups/blurp     419G  1.29T  6.60G  /tank/backups/blurp
</code></pre><p>Backups of these machines are tiggered in a cronjob by a custom wrapper script called <code>gobackup</code>, which uses the <code>zfsu</code> utilities. It pretty much looks like this (representative excerpt):</p>
<p><em>Example usage of zfstx and zfsret in my setup</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Example &#39;laptop1&#39;: a system that does not use ZFS as a file system</span>
</span></span><span class="line"><span class="cl"><span class="c1"># We just rsync from there and manage snapshots and retention locally</span>
</span></span><span class="line"><span class="cl">laptop1_root<span class="o">()</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">  <span class="o">(</span>
</span></span><span class="line"><span class="cl">    flock -n -x <span class="m">9</span> <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;Already running.&#34;</span><span class="p">;</span> <span class="k">return</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">    rsync <span class="se">\
</span></span></span><span class="line"><span class="cl">      -e <span class="s2">&#34;ssh -o ConnectTimeout=15&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -av <span class="se">\
</span></span></span><span class="line"><span class="cl">      --delete <span class="se">\
</span></span></span><span class="line"><span class="cl">      --delete-excluded <span class="se">\
</span></span></span><span class="line"><span class="cl">      --one-file-system <span class="se">\
</span></span></span><span class="line"><span class="cl">      --compress <span class="se">\
</span></span></span><span class="line"><span class="cl">      root@laptop1:/ <span class="se">\
</span></span></span><span class="line"><span class="cl">      /tank/backups/laptop1/ <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="o">&amp;&amp;</span> zfs snapshot tank/backups/laptop1@<span class="sb">`</span>date +%y%m%d%H%M<span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="o">&amp;&amp;</span> zfsret tank/backups/laptop1 <span class="m">100</span>
</span></span><span class="line"><span class="cl">  <span class="o">)</span> 9&gt; /var/lock/gobackup_laptop1_root
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Example &#39;laptop2&#39;: a system that does use ZFS. A single command</span>
</span></span><span class="line"><span class="cl"><span class="c1"># pulls the latest snapshots to the backup hub.</span>
</span></span><span class="line"><span class="cl">laptop2_home<span class="o">()</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">  zfstx <span class="se">\
</span></span></span><span class="line"><span class="cl">    --mbuffer 1G <span class="se">\
</span></span></span><span class="line"><span class="cl">    --keep <span class="m">9000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    laptop2:tank/home/pheckel <span class="se">\
</span></span></span><span class="line"><span class="cl">    tank/backups/laptop2/home/pheckel      
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">laptop1_root
</span></span><span class="line"><span class="cl">laptop2_home
</span></span></code></pre></div>]]></content:encoded></item><item><title>How-To: Move your existing Linux install to ZFS on Root</title><link>https://heckel.io/blog/move-existing-linux-install-zfs-root/</link><pubDate>Sat, 31 Dec 2016 13:38:16 -0500</pubDate><guid>https://heckel.io/blog/move-existing-linux-install-zfs-root/</guid><description>Ever since I joined my new company two years ago, ZFS has been part of my work every day. And every day, I am amazed how great it is. So naturally, I wanted to move my existing Linux Mint 18 installation to boot off of ZFS. Why, you may wonder? Well that&amp;rsquo;s easy. Because now I can snapshot my …</description><content:encoded><![CDATA[<p>Ever since I joined my new company two years ago, <a href="http://zfsonlinux.org/">ZFS</a> has been part of my work every day. And every day, I am amazed how great it is. So naturally, I wanted to move my existing Linux Mint 18 installation to boot off of ZFS. Why, you may wonder? Well that&rsquo;s easy. Because now I can snapshot my root file system, I can roll back if I need to, and I can restore individual files in a heartbeat.</p>
<p>It took a bit of fiddling in the beginning, but once you know how it works, it&rsquo;s a piece of cake. This short post shows you how to <strong>move your existing Linux installation to ZFS on root</strong> (preferably Ubuntu 16.04+ based, may work for others).</p>
<h2 id="1-requirements-and-assumptions">1. Requirements and Assumptions</h2>
<p>For this example, we&rsquo;re assuming that you&rsquo;re running an U<strong>buntu 16.04+ based operating system</strong> (Ubuntu, Kubuntu, Xubuntu, Lubuntu, Linux Mint, &hellip;), and that you have <strong>another partition</strong> (to be used for the ZFS pool) with at least the amount of disk space that your current root partition has. If that is not the case, you can follow step 3 to make room.</p>
<h2 id="2-this-example-kubuntu-1604">2. This example: Kubuntu 16.04</h2>
<p>In this example, I&rsquo;ll be using a regular Kubuntu 16.04 installation. This could be your laptop or workstation, with any derivative of Ubuntu installed.</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Snapshot-1-Running-Oracle-VM-VirtualBox_048-1.jpg" alt=""></p>
<h2 id="3-creating-the-zfs-pool-partition-optional">3. Creating the ZFS pool partition (optional)</h2>
<p><strong>Optional (if you have no spare partition):</strong> As you can see in the screenshot above, <code>lsblk</code> shows us that the machine has no extra partition for a new ZFS pool (yet), so we&rsquo;ll have to split the root partition to make room for the ZFS pool partition. If you already have a disk/partition that you want to use as a pool, you can skip this step.</p>
<p>To modify your root partition, you need to first boot an Ubuntu/Kubuntu/Mint Live CD/USB, so you can fiddle with the partition layout in <strong>gparted</strong> or the <strong>KDE Partition Manager</strong>. I have opted to boot into an Ubuntu Live CD (to make it clear that this is not your live system).</p>
<p>To <strong>split your root partition</strong>,</p>
<ul>
<li>Open gparted or KDE Partition Manager</li>
<li>Select your root partition, right click and select &ldquo;Resize&rdquo;, enter the target size and hit &ldquo;Apply&rdquo;</li>
<li>Then create a new (unformatted) partition in the empty space after the old root partition</li>
<li>Click Apply (<strong>Warning: Back up your system before you do this. There is a chance of data loss!</strong>)</li>
</ul>
<p>It should look something like this (if you&rsquo;re using gparted):</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Snapshot-1-Running-Oracle-VM-VirtualBox_049-1.jpg" alt=""></p>
<p>After you hit &ldquo;Apply&rdquo; and after you rebooted into your actual system, your disk layout changed and you have a new partition (here: <code>/dev/sda3</code>). This is the partition we will use to create the ZFS pool:</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Snapshot-1-Running-Oracle-VM-VirtualBox_050-1.jpg" alt=""></p>
<h2 id="4-creating-the-root-zfs-pool-and-copying-your-os">4. Creating the root ZFS pool, and copying your OS</h2>
<p>Now that we have a free partition for our new pool, let&rsquo;s first install ZFS (if you don&rsquo;t have it already):</p>
<p><em>Install ZFS and its initramfs module</em></p>
<pre tabindex="0"><code>$ apt install zfs-dkms zfs-initramfs
</code></pre><p>Then, identify the newly created partition and create the root pool:</p>
<p><em>Identifying the new partition with lsblk</em></p>
<pre tabindex="0"><code>$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0   20G  0 disk 
├─sda1   8:1    0  8.8G  0 part /
├─sda2   8:2    0    1K  0 part 
├─sda3   8:3    0  9.2G  0 part             # &lt;&lt;&lt;&lt;&lt; Our new partition! It&#39;ll likely be different for you!
└─sda5   8:5    0    2G  0 part [SWAP]
</code></pre><p>Now create the root pool and the root dataset you want to use. I usually call my pools <code>tank</code> and put my systems in <code>tank/os/&lt;osname&gt;</code>, but this is really up to you:</p>
<p><em>Creating a pool and the ZFS datasets</em></p>
<pre tabindex="0"><code>$ zpool create tank /dev/sda3     # Warning, choose your partition here! ZFS does not forgive typos!
$ zfs create tank/os
$ zfs create tank/os/kubuntu1604

# Alternatively, use the by-id symlink like so:
# $ zpool create tank /dev/disk/by-id/....
</code></pre><p>After you created the new dataset, it is auto-mounted by ZFS at <code>/tank/os/kubuntu1604</code>. You may now start copying your running system to this location:</p>
<p><em>Copying the current system to the ZFS dataset</em></p>
<pre tabindex="0"><code>$ rsync -a --one-file-system / /tank/os/kubuntu1604/
</code></pre><p>This may take a while, depending on your disk I/O speed and load; and it may even throw some warnings. Usually these warnings can be safely ignored if they are about files in <code>/proc</code>.</p>
<h2 id="5-make-system-bootable">5. Make system bootable</h2>
<p>Now that we have the dataset of our new root file system (<code>tank/os/kubuntu1604</code>), we need to tell our bootloader (here: <code>grub2</code>) that it should boot into the new system. To do that, we need to chroot into the system and run <code>update-grub</code> and <code>grub-install</code>:</p>
<p><em>Chrooting into the new system, and updating grub</em></p>
<pre tabindex="0"><code>$ cd /tank/os/kubuntu1604
$ mount --bind /dev dev
$ mount --bind /proc proc
$ mount --bind /sys sys
$ mount --bind /run run
$ chroot .

# You are now in the new system. Your current &#34;/&#34; points to &#34;/tank/os/kubuntu1604&#34;
# All the following commands will apply only to that system.

$ update-grub
Generating grub configuration file ...
Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.
Found linux image: /boot/vmlinuz-4.4.0-31-generic
Found initrd image: /boot/initrd.img-4.4.0-31-generic
Found memtest86+ image: /os/kubuntu1604@/boot/memtest86+.elf
Found memtest86+ image: /os/kubuntu1604@/boot/memtest86+.bin
Found Ubuntu 16.04.1 LTS (16.04) on /dev/sda1
done

$ grub-install /dev/sda                # Note: This should be your root disk (not partition!)
Installing for i386-pc platform.
Installation finished. No error reported.

$ exit
$ reboot
</code></pre><p>That&rsquo;s it. During the <strong>reboot</strong>, if you press ESC, you can see that the grub menu will show the new system as &ldquo;Ubuntu&rdquo;, and the old system as &ldquo;Ubuntu 16.04.1 LTS (16.04) on /dev/sda1&rdquo;. That means if you&rsquo;re having trouble with your ZFS on Root based system, you can always go back to your old system:</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Split-root-partition-Running-Oracle-VM-VirtualBox_051-1.jpg" alt=""></p>
<p>Once you booted, you can verify that you&rsquo;re indeed running on ZFS with <code>zfs list</code> or <code>mount</code>:</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Split-root-partition-Running-Oracle-VM-VirtualBox_053-1.jpg" alt=""></p>
<h2 id="6-snapshot-your-system-regularly-accessing-snapshots">6. Snapshot your system regularly, accessing snapshots</h2>
<p>You can (and should) now snapshot your root file system, and you can even roll back if you really want to. The first thing you should do is set up a cronjob to regularly snapshot your system:</p>
<p><em>Snapshotting your root file system regularly</em></p>
<pre tabindex="0"><code>$ crontab -e # as root!

# Then add a line line this:
0  20     * * * zfs snapshot tank/os/kubuntu1604@`date +\%y\%m\%d\%H\%M`
</code></pre><p>Once you&rsquo;ve done that, you can always restore individual files by accessing the ZFS directory in <code>/.zfs/snapshot/&lt;snapshotname&gt;</code>, like this:</p>
<p><em>Restoring a file from an old snapshot</em></p>
<pre tabindex="0"><code>$ cp /.zfs/snapshot/1612221520/etc/hosts /etc/hosts
</code></pre><p>Or, if you really messed up, you can also completely roll back to an old snapshot (use with caution, as running programs may expect the disk contents to be different!):</p>
<p><img src="/uploads/2016/12/Kubuntu-16.04-Split-root-partition-Running-Oracle-VM-VirtualBox_054-1.jpg" alt=""></p>]]></content:encoded></item><item><title>How-To: Your own dynamic DNS server (with PowerDNS &amp; a MySQL backend)</title><link>https://heckel.io/blog/your-own-dynamic-dns-server-powerdns-mysql/</link><pubDate>Sat, 31 Dec 2016 02:33:49 -0500</pubDate><guid>https://heckel.io/blog/your-own-dynamic-dns-server-powerdns-mysql/</guid><description>I was using dyndns.org and no-ip.com for a long time, and because I&amp;rsquo;m too cheap to buy the premium version for a simple service like this, I finally decided to set up my own dynamic DNS server for my various systems.
This is a short tutorial describing how I did it. It&amp;rsquo;s really not …</description><content:encoded><![CDATA[<p>I was using dyndns.org and no-ip.com for a long time, and because I&rsquo;m too cheap to buy the premium version for a simple service like this, I finally decided to set up my own dynamic DNS server for my various systems.</p>
<p>This is a short tutorial describing how I did it. It&rsquo;s really not rocket science, so don&rsquo;t expect too much.</p>
<h2 id="1-requirements">1. Requirements</h2>
<p>You need:</p>
<ul>
<li>
<p>A publicly accessible server with root access</p>
</li>
<li>
<p>A domain and control to delegate DNS zones</p>
</li>
<li>
<p>About 30min of set up time</p>
</li>
</ul>
<p>In this example/post:</p>
<ul>
<li>
<p>Our new DNS server will be at <strong>99.88.77.66</strong></p>
</li>
<li>
<p>Its name will be <strong>dyndns.ns.heckel.xyz</strong></p>
</li>
<li>
<p>The new dynamic DNS zone will be named <strong>dyndns.heckel.xyz</strong></p>
</li>
</ul>
<h2 id="2-install-powerdns--mysql">2. Install PowerDNS &amp; MySQL</h2>
<p>Assuming that you&rsquo;re running Ubuntu 16.04+, install MySQL (<code>apt install mysql-server</code>), then install PowerDNS as per <a href="https://repo.powerdns.com/">instructions on their repo site</a>. Note that the Ubuntu 16.04 upstream version is broken, <a href="https://github.com/PowerDNS/pdns/issues/4232">as per this issue</a>, so be sure to install at least version 4.0.1.</p>
<p>Then install the PowerDNS MySQL backend via <code>apt install pdns-backend-mysql</code>.</p>
<p>If everything goes as planned, this will set up the <code>pdns</code> database for you automatically (including user and password), so you don&rsquo;t have to manually do that. If that does not happen, configure it in <code>/etc/powerdns/pdns.d/pdns.local.gmysql.conf</code>, so that it looks something like this:</p>
<pre tabindex="0"><code>launch+=gmysql

gmysql-host=localhost
gmysql-port=
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=supersecretpass
gmysql-dnssec=yes
</code></pre><p>Once you&rsquo;ve configured it, you may want to start the service to see if it is working (<code>systemctl start pdns.service</code>). Given that it is not properly configured with a zone, it may actually fail here. Check <code>journalctl -u pdns.service</code> for details.</p>
<h2 id="3-add-your-dns-zone">3. Add your DNS zone</h2>
<p>With the MySQL backend, you can configure your zone completely via SQL tables (duh!). For our dynamic DNS server, the only relevant tables are <strong>pdns.domains</strong> and <strong>pdns.records</strong>. If you want to know more about the other tables, a detailed documentation of the schema and plugin can be found on the <a href="https://doc.powerdns.com/md/authoritative/backend-generic-mysql/">PowerDNS plugin documentation</a> page.</p>
<p>For this example, we&rsquo;re configuring <strong>dyndns.heckel.xyz</strong> as our DNS zone, so let&rsquo;s add this domain to the <strong>pdns.domains</strong> table:</p>
<p><em>Inserting the DNS zone we want to use for the dynamic DNS server</em></p>
<pre tabindex="0"><code>INSERT INTO pdns.domains SET id=1, name=&#39;dyndns.heckel.xyz&#39;
</code></pre><p>Once we&rsquo;ve done this, the <strong>pdns.domains</strong> table should look like this (assuming you&rsquo;re looking at it via phpMyAdmin):</p>
<p><a href="/uploads/2016/12/Selection_040.jpg"><img src="/uploads/2016/12/Selection_040.jpg" alt=""></a></p>
<p>If you know DNS, you know that there are different types of records that can be added to a zone. Our dynamic DNS zone is pretty simple. We just need two records to get started. Let&rsquo;s add a <a href="https://support.dnsimple.com/articles/soa-record/"><strong>SOA record</strong></a> to declare that this server is authoritative for our dynamic DNS domain (here: <strong>dyndns.heckel.xyz</strong>) and our first dynamic <strong>A record</strong> (here: let&rsquo;s assume our home network&rsquo;s public IP address is 12.11.121.221):</p>
<p><em>Inserting the SOA record for the DNS zone and a first dynamic record</em></p>
<pre tabindex="0"><code>INSERT INTO pdns.records SET 
  domain_id=1, 
  name=&#39;dyndns.heckel.xyz&#39;, 
  type=&#39;SOA&#39;, 
  content=&#39;dyndns.ns.heckel.xyz noreply.heckel.xyz 1483141082 60 60 60 60&#39;,
  ttl=60, 
  change_date=unix_timestamp();

INSERT INTO pdns.records SET 
  domain_id=1, 
  name=&#39;dyndns.heckel.xyz&#39;, 
  type=&#39;A&#39;, 
  content=&#39;12.11.121.221&#39;, 
  ttl=60, 
  change_date=unix_timestamp();
</code></pre><p>If you add other A records, this may look like this:</p>
<p><img src="/uploads/2016/12/Selection_045.jpg" alt=""></p>
<p>Side note: PowerDNS looks at the <strong>change_date</strong> column (not shown in screenshot) to determine whether its cache needs to be updated. Be sure to always update that column if you change a record, and also always update the SOA record serial (third part of the record, see above <code>... 1483141082 ...</code>) when anything in the zone has changed. You&rsquo;ll get outdated/invalid results if you don&rsquo;t do this.</p>
<p>That&rsquo;s all you need to configure PowerDNS. After restarting the service (<code>systemctl restart pdns</code>), you should be able to query the DNS server for the two records that you added:</p>
<p><em>Querying the DNS server locally for the two records we added</em></p>
<pre tabindex="0"><code>$ dig SOA dyndns.heckel.xyz @localhost +short
dyndns.ns.heckel.xyz. noreply.heckel.xyz. 1483141082 60 60 60 60

$ dig A dyndns.heckel.xyz @localhost +short
12.11.121.221
</code></pre><h2 id="4-delegating-the-domain">4. Delegating the domain</h2>
<p>So far we can only verify that our zone works locally, but we haven&rsquo;t made the dynamic DNS domain known to the world. If you ask for any domain *.dyndns.heckel.xyz via DNS, the nameserver of your domain will still feel responsible. To tell it to delegate all requests for <strong>dyndns.heckel.xyz</strong> to the our server <strong>dyndns.ns.heckel.xyz</strong> (aka 99.88.77.66), we need to change some records in our domain&rsquo;s DNS entries.</p>
<p>My domain is hosted with Hosteurope, so I had to log into their domain management system and add two entries:</p>
<p><a href="/uploads/2016/12/Selection_043.jpg"><img src="/uploads/2016/12/Selection_043.jpg" alt=""></a></p>
<p>The two records you see are to map an IP address to the DNS server domain (<code>dyndns.ns.heckel.xyz A 99.88.77.66</code>) and to actually delegate the subdomain to this DNS server (<code>dyndns.heckel.xyz NS dyndns.ns.heckel.xyz</code>).</p>
<p>Once these records are live (this may take a few hours), you should be able to resolve queries for the zone from any computer. It&rsquo;s easiest to do this via the requests we ran before, but this time run it from your workstation (instead of the server itself):</p>
<p><em>Querying the DNS server from a workstation</em></p>
<pre tabindex="0"><code>$ dig SOA dyndns.heckel.xyz +short
dyndns.ns.heckel.xyz. noreply.heckel.xyz. 1483141082 60 60 60 60

$ dig A dyndns.heckel.xyz +short
12.11.121.221
</code></pre><h2 id="5-updating-the-dns-zone-regularly">5. Updating the DNS zone regularly</h2>
<p>Now that the zone is live and can be queried from anywhere, all you need to do is keep it up-to-date. How you do that is of course up to you. I&rsquo;ve set up a JSON-RPC API on my server (<a href="/blog/php-json-rpc-api-with-auth-validation-logging/">I also wrote a guide on how to do that</a>) and I&rsquo;m updating it through that.</p>
<p>Each of my clients/machines runs a simple script every 2 minutes in a cron job:</p>
<p><em>Updating the host IP address in a cronjob</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">#!/bin/bash
</span></span><span class="line"><span class="cl">curl \
</span></span><span class="line"><span class="cl">  -u worklaptop:supersecretkey \
</span></span><span class="line"><span class="cl">  -d &#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;hosts/update&#34;,&#34;params&#34;:{&#34;host&#34;:&#34;worklaptop&#34;}}&#39; \
</span></span><span class="line"><span class="cl">  https://heckel.xyz/api.php
</span></span></code></pre></div><p>On the server, the API endpoint essentially runs these two queries for every host update request:</p>
<p><em>Updating the records table on the server side</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$dbh</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PDO</span><span class="p">(</span><span class="o">...</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Update A record for &#39;&lt;host&gt;.dyndns.heckel.xyz&#39;
</span></span></span><span class="line"><span class="cl"><span class="nv">$stmt</span> <span class="o">=</span> <span class="nv">$dbh</span><span class="o">-&gt;</span><span class="na">prepare</span><span class="p">(</span><span class="s2">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    update records 
</span></span></span><span class="line"><span class="cl"><span class="s2">    set change_date=unix_timestamp(), content=:content
</span></span></span><span class="line"><span class="cl"><span class="s2">    where type=&#39;A&#39; and name=:name
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$stmt</span><span class="o">-&gt;</span><span class="na">execute</span><span class="p">(</span><span class="k">array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;name&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;</span><span class="si">$host</span><span class="s2">.dyndns.heckel.xyz&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;content&#39;</span> <span class="o">=&gt;</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;REMOTE_ADDR&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Update SOA record timestamp for &#39;dyndns.heckel.xyz&#39;
</span></span></span><span class="line"><span class="cl"><span class="nv">$serial</span> <span class="o">=</span> <span class="nx">time</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$dbh</span><span class="o">-&gt;</span><span class="na">query</span><span class="p">(</span><span class="s2">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    update records 
</span></span></span><span class="line"><span class="cl"><span class="s2">    set change_date=unix_timestamp(), content=&#39;dyndns.ns.heckel.xyz noreply.heckel.xyz </span><span class="si">$serial</span><span class="s2"> 60 60 60 60&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">    where type=&#39;SOA&#39; and name=&#39;dyndns.heckel.xyz&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>That&rsquo;s essentially it. Your dynamic DNS server is ready to be used. PowerDNS really makes things incredibly easy for us. As always, leave notes and questions in the comments section.</p>]]></content:encoded></item><item><title>How-To: PHP based JSON-RPC API, with authentication, validation and logging</title><link>https://heckel.io/blog/php-json-rpc-api-with-auth-validation-logging/</link><pubDate>Tue, 05 Jan 2016 15:15:55 -0500</pubDate><guid>https://heckel.io/blog/php-json-rpc-api-with-auth-validation-logging/</guid><description>At my work, we use JSON-RPC based APIs very heavily, in particular with our PHP JSON-RPC library php-json-rpc. While JSON-RPC is not as wide spread as REST, it fits our needs quite nicely. In particular, it is protocol independent and can be used over HTTP, SSH or as local CLI. With our library and …</description><content:encoded><![CDATA[<p>At my work, we use <a href="http://www.jsonrpc.org/specification">JSON-RPC</a> based APIs very heavily, in particular with our PHP JSON-RPC library <a href="https://github.com/datto/php-json-rpc">php-json-rpc</a>. While JSON-RPC is not as wide spread as REST, it fits our needs quite nicely. In particular, it is protocol independent and can be used over HTTP, SSH or as local CLI. With our library and its numerous extensions (<a href="https://github.com/datto/php-json-rpc-http">HTTP</a>, <a href="https://github.com/datto/php-json-rpc-ssh">SSH</a>, <a href="https://github.com/datto/php-json-rpc-auth">authentication</a>, <a href="https://github.com/datto/php-json-rpc-validator">validation</a>, <a href="https://github.com/datto/php-json-rpc-simple">request-to-class mapping</a> and <a href="https://github.com/datto/php-json-rpc-log">logging</a>), development is super fast and incredibly easy.</p>
<p>In this post, I&rsquo;d like to demonstrate how to set up <strong>a PHP based JSON-RPC API, with authentication, validation and logging</strong>.</p>
<h2 id="0-code-available-on-github">0. Code available on GitHub</h2>
<p>All the code for this post is available in a <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC Demo Project</a> on GitHub. Feel free to poke around there and/or steal bits and pieces from it.</p>
<p>To try the examples, check out the code and run this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/binwiederhier/json-rpc-demo
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> json-rpc-demo
</span></span><span class="line"><span class="cl">composer install
</span></span><span class="line"><span class="cl">php -S localhost:8888 -t web
</span></span></code></pre></div><h2 id="1-demo-api-managing-devices-eg-phones-computers">1. Demo API: Managing devices (e.g. phones, computers)</h2>
<p>For this blog post, we&rsquo;ll create a simple Device Management API to <strong>list and add devices</strong> (e.g. phones, computers, and such) using two endpoints called <code>devices/add</code> and <code>devices/listAll</code>. The endpoints will be simple stub implementations, but we&rsquo;ll still see how easy it is to create and manage endpoints.</p>
<p>Here&rsquo;s an example JSON-RPC API request and its response to list all devices:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// 2. Request to list all devices, sorted by id
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;jsonrpc&#34;</span><span class="o">:</span> <span class="s2">&#34;2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;id&#34;</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;method&#34;</span><span class="o">:</span> <span class="s2">&#34;devices/listAll&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;params&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sortBy&#34;</span><span class="o">:</span> <span class="s2">&#34;id&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// The corresponding response returns a list
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;jsonrpc&#34;</span><span class="o">:</span> <span class="s2">&#34;2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;id&#34;</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;result&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="o">:</span> <span class="s2">&#34;Philipp PC&#34;</span><span class="p">,</span> <span class="s2">&#34;id&#34;</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;pc&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="o">:</span> <span class="s2">&#34;Phil Phone&#34;</span><span class="p">,</span> <span class="s2">&#34;id&#34;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;phone&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="o">:</span> <span class="s2">&#34;Phil Washing Machine&#34;</span><span class="p">,</span> <span class="s2">&#34;id&#34;</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span> <span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;other&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="2-json-rpc-core-library-php-json-rpc">2. JSON-RPC core library (php-json-rpc)</h2>
<p>The core JSON-RPC library <a href="https://github.com/datto/php-json-rpc">php-json-rpc</a> is written by <a href="http://spencermortensen.com/">Spencer Mortensen</a>. It&rsquo;s a great piece of software that implements the core specification of <a href="http://www.jsonrpc.org/specification">JSON-RPC 2.0</a>.</p>
<h3 id="21-sample-code">2.1. Sample code</h3>
<p>At the center of it, the <code>JsonRpc\Server</code> class provides a way to encode and decode messages according to JSON-RPC standards. All you have to do is provide your own <code>JsonRpc\Evaluator</code> implementation to use it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$evaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DevicesEvaluator</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">JsonRpc\Server</span><span class="p">(</span><span class="nv">$evaluator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span> <span class="c1">// {&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;result&#34;:3}
</span></span></span></code></pre></div><p>An <code>Evaluator</code> basically translates JSON-RPC methods and parameters to PHP methods/arguments and executes the method. Your implementation must only provide a method <code>evaluate($method, $arguments)</code>. It might (but should not) look as simple as this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DevicesEvaluator</span> <span class="k">implements</span> <span class="nx">JsonRpc\Evaluator</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">evaluate</span><span class="p">(</span><span class="nv">$method</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$method</span> <span class="o">===</span> <span class="s1">&#39;devices/add&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">devices</span><span class="o">-&gt;</span><span class="na">add</span><span class="p">(</span><span class="nv">$arguments</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">],</span> <span class="nv">$arguments</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">],</span> <span class="nv">$arguments</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nv">$method</span> <span class="o">===</span> <span class="s1">&#39;devices/listAll&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">devices</span><span class="o">-&gt;</span><span class="na">listAll</span><span class="p">(</span><span class="nv">$arguments</span><span class="p">[</span><span class="s1">&#39;sortBy&#39;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span><span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="nx">Exception\Method</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="22-usage">2.2. Usage</h3>
<p>Once we&rsquo;ve implemented the endpoints/methods, we&rsquo;ll be able to add and list devices via HTTP(S). Here&rsquo;s an example via <code>curl</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Adding a device</span>
</span></span><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/add&#34;,&#34;params&#34;:{&#34;name&#34;:&#34;Philipp PC&#34;,&#34;type&#34;:&#34;pc&#34;,&#34;id&#34;:1}}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example0/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;result&#34;</span>:<span class="o">{</span><span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Philipp PC&#34;</span>,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}}</span>
</span></span></code></pre></div><p>Of course you can also use your own JSON-RPC client, or you can use JavaScript/jQuery (see below). I&rsquo;m using <code>curl</code> here because it&rsquo;s super easy.</p>
<h3 id="23-more-examples">2.3. More examples</h3>
<p>Find a working demo of the php-json-rpc library in the <a href="https://github.com/datto/php-json-rpc/tree/master/examples">examples folder of the library</a> or in <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example0/api.php"><strong>example 0</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>.</p>
<h2 id="3-method-and-parameter-mapping-php-json-rpc-simple">3. Method and parameter mapping (php-json-rpc-simple)</h2>
<p>While we can certainly implement all use cases with the core library, having to provide the mapping yourself (usually with a giant switch-case) can be tedious. The <a href="https://github.com/datto/php-json-rpc-simple">php-json-rpc-simple</a> library provides a way to do that mapping for you. It offers a simple implementation of the <code>JsonRpc\Evaluator</code> interface for you and maps JSON-RPC <code>method</code> and <code>params</code> to a corresponding PHP class, method and arguments.</p>
<h3 id="31-sample-code">3.1. Sample code</h3>
<p>To use the mapping magic of the php-json-rpc-simple library, alter the API server logic to automatically map JSON-RPC methods to a PHP class/method inside the <code>Demo\Api\Endpoint</code> namespace (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example1/api.php">example1/api.php</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$evaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Simple\Evaluator</span><span class="p">(</span><span class="k">new</span> <span class="nx">Simple\Mapper</span><span class="p">(</span><span class="s1">&#39;Demo\\Api\\Endpoint\\&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">JsonRpc\Server</span><span class="p">(</span><span class="nv">$evaluator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span>
</span></span></code></pre></div><p>Since the mapping is done for you, the endpoint methods only contain the actual logic and/or library calls (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/src/Api/Endpoint/Devices.php">src/Api/Endpoint/Devices.php</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nx">Demo\Api\Endpoint</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Devices</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>   
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">listAll</span><span class="p">(</span><span class="nv">$sortBy</span> <span class="o">=</span> <span class="s1">&#39;name&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">array</span><span class="p">(</span><span class="o">..</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="32-usage">3.2. Usage</h3>
<p>The usage is not very different from the example above. This time, however, the mapping is performed by the library:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Listing all devices</span>
</span></span><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;,&#34;params&#34;:{&#34;sortBy&#34;:&#34;id&#34;}}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example1/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;result&#34;</span>:<span class="o">[{</span><span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Marian-PC&#34;</span>,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}</span>,<span class="o">{</span><span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Philipp PC&#34;</span>,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}]}</span>
</span></span></code></pre></div><p>That also means, of course, that invalid or missing parameters will be detected by the library and a JSON-RPC error is returned:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Invalid parameters return an error</span>
</span></span><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/add&#34;,&#34;params&#34;:{&#34;INVALID&#34;:&#34;1&#34;}}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example1/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;error&#34;</span>:<span class="o">{</span><span class="s2">&#34;code&#34;</span>:-32602,<span class="s2">&#34;message&#34;</span>:<span class="s2">&#34;Invalid params&#34;</span><span class="o">}}</span>
</span></span></code></pre></div><h3 id="33-more-examples">3.3. More examples</h3>
<p>Find a working demo of the php-json-rpc-simple library in <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example1/api.php"><strong>example 1</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>.</p>
<h2 id="4-validation-of-parameters-php-json-rpc-validator">4. Validation of parameters (php-json-rpc-validator)</h2>
<p>Another tedious thing in API development is parameter validation. User input can be invalid or even malicious, an API (or any web page really) must validate the input parameters. The <a href="https://github.com/datto/php-json-rpc-validator">php-json-rpc-validator</a> library offers annotation-based validation of parameters.</p>
<h3 id="41-sample-code">4.1. Sample code</h3>
<p>Normally, a publicly available endpoint method must check the arguments that are passed to it. Code like this is not uncommon:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Devices</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>   
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">listAll</span><span class="p">(</span><span class="nv">$sortBy</span> <span class="o">=</span> <span class="s1">&#39;name&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$sortBy</span> <span class="o">!==</span> <span class="s1">&#39;name&#39;</span> <span class="o">&amp;&amp;</span> <span class="nv">$sortBy</span> <span class="o">!==</span> <span class="s1">&#39;type&#39;</span> <span class="o">&amp;&amp;</span> <span class="nv">$sortBy</span> <span class="o">!==</span> <span class="s1">&#39;id&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="nx">Exception</span><span class="p">(</span><span class="s1">&#39;Illegal argument&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Actual API code
</span></span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Instead of this annoying and polluting validation code, we can now define the constraints of each method argument using the <code>@Validate</code> annotation. This annotation is based on Symfony&rsquo;s <code>@Collection</code> annotation and supports <a href="http://symfony.com/doc/current/reference/constraints.html">a variety of constraints</a>. Examples include <code>@Assert\NotBlank</code>, <code>@Assert\NotNull</code>, <code>@Assert\EqualTo</code>, <code>@Assert\Regex</code>, <code>@Assert\GreaterThan</code>, and may more.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Devices</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>   
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @Validate(fields={
</span></span></span><span class="line"><span class="cl"><span class="sd">     *   &#34;sortBy&#34; = { @Assert\Regex(&#34;/^(id|name|type)$/&#34;) }
</span></span></span><span class="line"><span class="cl"><span class="sd">     * })
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">listAll</span><span class="p">(</span><span class="nv">$sortBy</span> <span class="o">=</span> <span class="s1">&#39;name&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>        
</span></span><span class="line"><span class="cl">        <span class="c1">// Actual API code
</span></span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>To add validation support to your API server file (in our examples: <code>api.php</code>), all you need to do is wrap the existing <code>JsonRpc\Evaluator</code> in a <code>Validator\Evaluator</code>. After that, the API server logic looks like this (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example2/api.php">example2/api.php</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$mapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Simple\Mapper</span><span class="p">(</span><span class="s1">&#39;Demo\\Api\\Endpoint\\&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$evaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Validator\Evaluator</span><span class="p">(</span><span class="k">new</span> <span class="nx">Simple\Evaluator</span><span class="p">(</span><span class="nv">$mapper</span><span class="p">),</span> <span class="nv">$mapper</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">JsonRpc\Server</span><span class="p">(</span><span class="nv">$evaluator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="42-usage">4.2. Usage</h3>
<p>If the input is valid, the request and response look no different than above. However, if the parameter input is invalid, the API now responds with a JSON-RPC error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Invalid parameter values are rejected by validation </span>
</span></span><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;,&#34;params&#34;:{&#34;sortBy&#34;:&#34;INVALID&#34;}}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example2/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;error&#34;</span>:<span class="o">{</span><span class="s2">&#34;code&#34;</span>:-32602,<span class="s2">&#34;message&#34;</span>:<span class="s2">&#34;Invalid params&#34;</span><span class="o">}}</span>
</span></span></code></pre></div><h3 id="43-more-examples">4.3. More examples</h3>
<p>Find a working demo of the php-json-rpc-validator library in <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example2/api.php"><strong>example 2</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>.</p>
<h2 id="5-authentication-php-json-rpc-auth">5. Authentication (php-json-rpc-auth)</h2>
<p>What would an API be without authentication? Probably reckless, in most cases.</p>
<p>The <a href="https://github.com/datto/php-json-rpc-auth">php-json-rpc-auth</a> library offers a simple framework to implement any kind of authentication and authorization for your API. Instead of implementing all the different auth mechanisms (HTTP Basic Auth, Digest, OAuth, SAML, Cookies, &hellip;), it merely provides a simplistic <code>Auth\Authenticator</code> class to consult a user-provided set of <code>Auth\Handler</code>s. The actual implementation of these handler class(es) must be provided by the developer.</p>
<h3 id="51-sample-code">5.1. Sample code</h3>
<p>Assuming that we want to implement HTTP Basic Auth for our API, we only need to implement an <code>Auth\Handler</code> (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/src/Api/Auth/BasicAuthHandler.php">src/Api/Auth/BasicAuthHandler.php</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">BasicAuthHandler</span> <span class="k">implements</span> <span class="nx">Auth\Handler</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">canHandle</span><span class="p">(</span><span class="nv">$method</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">isset</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;PHP_AUTH_USER&#39;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">authenticate</span><span class="p">(</span><span class="nv">$method</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Don&#39;t use &#39;===&#39;, as that&#39;s vulnerable to timing attacks!
</span></span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;PHP_AUTH_USER&#39;</span><span class="p">]</span> <span class="o">===</span> <span class="s1">&#39;user&#39;</span> <span class="o">&amp;&amp;</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;PHP_AUTH_PW&#39;</span><span class="p">]</span> <span class="o">===</span> <span class="s1">&#39;pass&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>canHandle()</code> method tells the authenticator whether the given request can be authenticated with this handler. The <code>authenticate()</code> method is only called if <code>canHandle()</code> returned <code>true</code> and then authenticates the actual request.</p>
<p>As for the API server code, we can simply wrap an <code>Auth\Evaluator</code> around our existing evaluator (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example3/api.php">example3/api.php</a>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Auth\Authenticator</span><span class="p">(</span><span class="k">array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="nx">BasicAuthHandler</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$mapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Simple\Mapper</span><span class="p">(</span><span class="s1">&#39;Demo\\Api\\Endpoint\\&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$evaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Auth\Evaluator</span><span class="p">(</span><span class="k">new</span> <span class="nx">Validator\Evaluator</span><span class="p">(</span><span class="k">new</span> <span class="nx">Simple\Evaluator</span><span class="p">(</span><span class="nv">$mapper</span><span class="p">),</span> <span class="nv">$mapper</span><span class="p">),</span> <span class="nv">$authenticator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">JsonRpc\Server</span><span class="p">(</span><span class="nv">$evaluator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span>
</span></span></code></pre></div><p>This may look a bit complicated, but it merely nests different <code>Auth\Evaluator</code> implementations. In the example above, an incoming request is first passed to the <code>Auth\Evaluator</code>, then (if authorized) to the <code>Validator\Evaluator</code>, and finally (if validated) to the <code>Simple\Evaluator</code>. This is a very classic implementation of the <a href="https://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.</p>
<h3 id="52-usage">5.2. Usage</h3>
<p>Trying to access API without authentication will lead to a &ldquo;Missing auth&rdquo; error message. In this example, please note that <code>curl</code>&rsquo;s <code>-u</code> parameter (basic auth user/pass) is not being passed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example3/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;error&#34;</span>:<span class="o">{</span><span class="s2">&#34;code&#34;</span>:-32651,<span class="s2">&#34;message&#34;</span>:<span class="s2">&#34;Missing auth.&#34;</span><span class="o">}}</span>
</span></span></code></pre></div><p>In this example, the <code>-u</code> parameter is passed, i.e. the <code>Authorization</code> HTTP header is sent, but with invalid credentials. We&rsquo;re basically trying to access API with invalid Basic Auth credentials:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -u invaliduser:pass <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example3/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;error&#34;</span>:<span class="o">{</span><span class="s2">&#34;code&#34;</span>:-32652,<span class="s2">&#34;message&#34;</span>:<span class="s2">&#34;Invalid auth.&#34;</span><span class="o">}}</span>
</span></span></code></pre></div><p>Only if we provide the correct credentials, we&rsquo;re allowed to access the API via HTTP (authenticated via HTTP Basic Auth):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    -u user:pass <span class="se">\
</span></span></span><span class="line"><span class="cl">    -d <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    http://localhost:8888/example3/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;result&#34;</span>:<span class="o">[{</span><span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Phil Phone&#34;</span>,<span class="s2">&#34;id&#34;</span>:2,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;phone&#34;</span><span class="o">}</span>,<span class="o">{</span><span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Phil Washing Machine&#34;</span>,<span class="s2">&#34;id&#34;</span>:3,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;other&#34;</span><span class="o">}</span>,<span class="o">{</span><span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Philipp PC&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}]}</span>
</span></span></code></pre></div><h3 id="53-more-examples">5.3. More examples</h3>
<p>Find a working demo of the php-json-rpc-auth library in <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example3/api.php"><strong>example 3</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>.</p>
<h2 id="6-logging-with-php-json-rpc-log">6. Logging with &ldquo;php-json-rpc-log&rdquo;</h2>
<p>Logging requests and responses of an API can be a very important tool for troubleshooting or statistics. The <a href="https://github.com/datto/php-json-rpc-log">php-json-rpc-log</a> library offers a very simplistic way to log JSON-RPC equests and responses.</p>
<h3 id="61-sample-code">6.1. Sample code</h3>
<p>We&rsquo;re mostly using Monolog, but the php-json-rpc-log library supports any kind of PSR-3 logger. Simply use the <code>Logged\Server</code> class instead of the original <code>JsonRpc\Server</code> class, and pass a <code>Psr\Log\LoggerInterface</code> to it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$evaluator</span> <span class="o">=</span> <span class="c1">// see above
</span></span></span><span class="line"><span class="cl"><span class="nv">$logger</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Logger</span><span class="p">(</span><span class="s1">&#39;demo.api&#39;</span><span class="p">,</span> <span class="k">array</span><span class="p">(</span><span class="k">new</span> <span class="nx">SyslogHandler</span><span class="p">(</span><span class="s1">&#39;api&#39;</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Logged\Server</span><span class="p">(</span><span class="nv">$evaluator</span><span class="p">,</span> <span class="nv">$logger</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="62-usage">6.2. Usage</h3>
<p>In the case above, we passed a <code>Monolog\Logger</code> with a <code>SyslogHandler</code> to it, meaning that requests that we send will be logged to <code>/var/log/syslog</code>. The usage is the same as in the other examples, but we&rsquo;ll see output like this when we tail syslog:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Jan 15 01:24:21 platop api[13157]: demo.api.INFO: Message received: {&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/add&#34;} [] []
</span></span><span class="line"><span class="cl">Jan 15 01:24:21 platop api[13157]: demo.api.INFO: Sending reply: {&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;error&#34;:{&#34;code&#34;:-32602,&#34;message&#34;:&#34;Invalid params&#34;}} [] []
</span></span><span class="line"><span class="cl">Jan 15 01:24:46 platop api[13157]: demo.api.INFO: Message received: {&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;} [] []
</span></span><span class="line"><span class="cl">Jan 15 01:24:46 platop api[13157]: demo.api.INFO: Sending reply: {&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;result&#34;:[{&#34;id&#34;:0,&#34;name&#34;:&#34;Philipp PC&#34;,&#34;type&#34;:&#34;pc&#34;}]} [] []
</span></span></code></pre></div><h3 id="63-more-examples">6.3. More examples</h3>
<p>Find a working demo of the php-json-rpc-log library in <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example4/api.php"><strong>example 4</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>.</p>
<h2 id="7-advanced-usage">7. Advanced usage</h2>
<p>There are a million ways to combine these components, and I can surely not list them all here. However, here&rsquo;s a short list of very useful applications.</p>
<h3 id="71-using-the-api-from-the-shell-or-via-ssh-with-root-only-access">7.1. Using the API from the shell or via SSH (with &lsquo;root&rsquo;-only access)</h3>
<p>Unlike REST-based APIs, we can easily expose a JSON-RPC based API via other protocols, such as SSH or raw TCP sockets. We can even just use it locally by piping the JSON-RPC request to a PHP script.</p>
<p>In <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example5/api.php"><strong>example 5</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>, we extend the <code>api.php</code> file to support requests from <code>STDIN</code> (if the file is called from the CLI). The API can still be used via HTTP, but the script now checks via <code>php_sapi_name()</code> if we&rsquo;re in CLI mode.</p>
<p>In addition to that, the demo implements another auth handler (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/src/Api/Auth/CliAuthHandler.php">src/Api/Auth/CliAuthHandler.php</a>) to check if the user is <code>root</code>, and disallows access if she isn&rsquo;t:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Auth\Authenticator</span><span class="p">(</span><span class="k">array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="nx">CliAuthHandler</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="nx">BasicAuthHandler</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$isCLI</span> <span class="o">=</span> <span class="nx">php_sapi_name</span><span class="p">()</span> <span class="o">===</span> <span class="s1">&#39;cli&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$isCLI</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://stdin&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">header</span><span class="p">(</span><span class="s1">&#39;Content-Type: application/json&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$message</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;php://input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">reply</span><span class="p">(</span><span class="nv">$message</span><span class="p">);</span>
</span></span></code></pre></div><p>With this insanely simple addition, we can now use the API locally by piping the request to the <code>api.php</code> script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Access locally; or NOT, since we&#39;re not &#39;root&#39; (access denied by CliAuthHandler)</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="p">|</span> php web/example5/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;error&#34;</span>:<span class="o">{</span><span class="s2">&#34;code&#34;</span>:-32652,<span class="s2">&#34;message&#34;</span>:<span class="s2">&#34;Invalid auth.&#34;</span><span class="o">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Let&#39;s try again as &#39;root&#39;</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="p">|</span> sudo php web/example5/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;result&#34;</span>:<span class="o">[{</span><span class="s2">&#34;id&#34;</span>:0,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Philipp PC&#34;</span>,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}]}</span>
</span></span></code></pre></div><p>Now, since we can simply pipe, that means using the API across machines via SSH is trivial:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># And via SSH ... !</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s1">&#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:1,&#34;method&#34;:&#34;devices/listAll&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">   <span class="p">|</span> ssh root@myserver php /opt/demo/web/example1/api.php
</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;jsonrpc&#34;</span>:<span class="s2">&#34;2.0&#34;</span>,<span class="s2">&#34;id&#34;</span>:1,<span class="s2">&#34;result&#34;</span>:<span class="o">[{</span><span class="s2">&#34;id&#34;</span>:0,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;Philipp PC&#34;</span>,<span class="s2">&#34;type&#34;</span>:<span class="s2">&#34;pc&#34;</span><span class="o">}]}</span>
</span></span></code></pre></div><h3 id="72-using-the-api-with-javascript-and-cookie-based-auth">7.2. Using the API with JavaScript and cookie-based auth</h3>
<p>I&rsquo;ve been using <code>curl</code> in all the examples, but of course the primary use case of an API is often the use inside a web application. Since JSON-RPC requests are merely POST requests with JSON payload, you can use <a href="http://api.jquery.com/jquery.ajax/">jQuery&rsquo;s $.ajax()</a> method &ndash; for instance like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">url</span><span class="o">:</span> <span class="s1">&#39;/example6/api.php&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">type</span><span class="o">:</span> <span class="s1">&#39;POST&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">contentType</span><span class="o">:</span> <span class="s1">&#39;application/json&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dataType</span><span class="o">:</span> <span class="s1">&#39;json&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">jsonrpc</span><span class="o">:</span> <span class="s1">&#39;2.0&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;devices/listAll&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">params</span><span class="o">:</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}),</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>In <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example6/"><strong>example 6</strong></a> of the <a href="https://github.com/binwiederhier/json-rpc-demo">JSON-RPC demo project</a>, we implement a small web page to display (and auto-refresh) the list of devices using the API, but only if we&rsquo;re logged in via a cookie:</p>
<table style="width: 100%"><tr>
 <td style="text-align: center; vertical-align: top">
  <u><b>Without being logged in (no cookie):</b></u><br />
  <img src="/uploads/2016/01/20160115021148_Selection_001.png" />
 </td>
 <td style="text-align: center; vertical-align: top">
  <u><b>When logged in (with cookie):</b></u><br />
  <img src="/uploads/2016/01/20160115021201_Selection_001.png" />
 </td>
</table>
<p>To achieve this, we implement yet another auth handler (<code>CookieAuthHandler</code>, see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/src/Api/Auth/CookieAuthHandler.php">src/Api/Auth/CookieAuthHandler.php</a>), and toggle a cookie when the &ldquo;Login&rdquo; button is pressed.</p>
<p>All that&rsquo;s left is to call the API in regular intervals and update the UI. We implemented a small helper class called <code>Datto.API.Client</code> (see <a href="https://github.com/binwiederhier/json-rpc-demo/blob/master/web/example6/js/client.js">web/example6/js/client.js</a>) to interact with the API from our web app:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Datto</span><span class="p">.</span><span class="nx">API</span><span class="p">.</span><span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;api.php&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">api</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s1">&#39;devices/listAll&#39;</span><span class="p">,</span> <span class="p">{},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Success function
</span></span></span><span class="line"><span class="cl">    <span class="nx">$devices</span><span class="p">.</span><span class="nx">empty</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">$</span><span class="p">(</span><span class="nx">data</span><span class="p">).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">idx</span><span class="p">,</span> <span class="nx">device</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">$devices</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="s1">&#39;&lt;li&gt;&#39;</span><span class="p">).</span><span class="nx">addClass</span><span class="p">(</span><span class="nx">device</span><span class="p">.</span><span class="nx">type</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="nx">device</span><span class="p">.</span><span class="nx">name</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Failure function
</span></span></span><span class="line"><span class="cl">    <span class="nx">$devices</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">empty</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="s1">&#39;&lt;li&gt;&#39;</span><span class="p">).</span><span class="nx">addClass</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="s1">&#39;ERROR: &#39;</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>That&rsquo;s it. I hope this was helpful. Please let me know what you think in the comments!</p>]]></content:encoded></item><item><title>Snippet 0x0D: Let's Encrypt - 5 min guide to set up cronjob based certificate renewal</title><link>https://heckel.io/blog/lets-encrypt-5-min-guide-to-set-up-cronjob-based-certificate-renewal/</link><pubDate>Fri, 04 Dec 2015 02:57:37 -0500</pubDate><guid>https://heckel.io/blog/lets-encrypt-5-min-guide-to-set-up-cronjob-based-certificate-renewal/</guid><description>Let&amp;rsquo;s Encrypt was officially released to the open public today. That means the Internet can finally get free, trusted SSL/TLS certificates. This quick guide shows how to set up Let&amp;rsquo;s Encrypt with auto-renewal through a cronjob &amp;ndash; using the simp_le client, an alternative client …</description><content:encoded><![CDATA[<p><a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a> was officially <a href="https://letsencrypt.org/2015/12/03/entering-public-beta.html">released to the open public today</a>. That means the Internet can finally get <strong>free, trusted SSL/TLS certificates</strong>. This quick guide shows how to set up <strong>Let&rsquo;s Encrypt with auto-renewal through a cronjob</strong> &ndash; using the <a href="https://github.com/zenhack/simp_le"><strong>simp_le</strong></a> client, an alternative client developed by one of the same authors who develop the official client.</p>
<h2 id="updates">Updates</h2>
<p><strong>Feb 2016</strong>: The new version of <code>simp_le</code> requires a <code>-f account_key.json</code> argument. I&rsquo;ve updated the post accordingly.</p>
<p><strong>Feb 2017</strong>: Unfortunately, the <a href="https://github.com/kuba/simp_le">original simp_le project</a> has been abandoned by the original author. <a href="https://github.com/zenhack/simp_le/">This fork</a> still works fine though. I have changed all the links, and it should just work.</p>
<h2 id="1-install-the-simp_le-client">1. Install the simp_le client</h2>
<p>On your web server, clone the simp_le client and install it in a sensible directory (e.g. <code>/opt/simp_le</code>). Then run the installation steps as provided in the <a href="https://github.com/zenhack/simp_le/blob/master/README.rst">README.md</a>:</p>
<p><em>Install client and symlink it</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Clone</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> /opt
</span></span><span class="line"><span class="cl">$ git clone https://github.com/zenhack/simp_le/
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> simp_le
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install</span>
</span></span><span class="line"><span class="cl">$ ./bootstrap.sh
</span></span><span class="line"><span class="cl">$ ./venv.sh
</span></span><span class="line"><span class="cl">$ ln -s <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>/venv/bin/simp_le /usr/local/sbin/simp_le
</span></span></code></pre></div><h2 id="2-generate-a-keypair-and-retrieve-signed-certificate">2. Generate a keypair and retrieve signed certificate</h2>
<p>Once the installation is done, everything else is super easy. All you need is a location for your keys and certificates, as well as the publicly available document root for your website. I store my certificates and keys in <code>/srv/cert</code>, with a subfolder for each domain, e.g. <code>/srv/cert/example.com</code>, and my document root is at <code>/srv/www/example.com/html</code>.</p>
<p><em>Create certificate signed by Let&rsquo;s Encrypt</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">cd</span> /srv/cert/example.com
</span></span><span class="line"><span class="cl">$ simp_le <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d example.com:/srv/www/example.com/html <span class="se">\
</span></span></span><span class="line"><span class="cl">  -f key.pem -f cert.pem -f fullchain.pem -f account_key.json
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">2015-12-04 01:31:52,131:INFO:simp_le:157: Creating new account key
</span></span><span class="line"><span class="cl">2015-12-04 01:31:56,529:INFO:requests.packages.urllib3.connectionpool:756: Starting new HTTPS connection <span class="o">(</span>1<span class="o">)</span>: acme-v01.api.letsencrypt.org
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2015-12-04 01:31:58,568:INFO:requests.packages.urllib3.connectionpool:207: Starting new HTTP connection <span class="o">(</span>1<span class="o">)</span>: example.com
</span></span><span class="line"><span class="cl">2015-12-04 01:31:58,588:INFO:simp_le:803: example.com was successfully verified by the client
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2015-12-04 01:32:04,570:INFO:simp_le:409: Saving key.pem
</span></span><span class="line"><span class="cl">2015-12-04 01:32:04,570:INFO:simp_le:370: Saving fullchain.pem
</span></span></code></pre></div><p>This command automatically generates a keypair, creates a certificate request and gets that signed by Let&rsquo;s Encrypt. It also verifies your domain ownership by creating a file in the document root at <code>http://example.com/.well-known/...</code>. So let&rsquo;s look at the key and certificate:</p>
<p><em>Certificate and private key file</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ ls
</span></span><span class="line"><span class="cl">account_key.json  cert.pem  fullchain.pem  key.pem
</span></span></code></pre></div><h2 id="3-configure-your-web-server">3. Configure your web server</h2>
<p>Now configure the SSL/TLS certificate in your web server. For me, that&rsquo;s Apache. The configuration for the virtual host looks like this:</p>
<p><em>Configuring the Apache virtual host</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /etc/apache2/sites-enabled/example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;VirtualHost *:443&gt;
</span></span><span class="line"><span class="cl">        SSLEngine on
</span></span><span class="line"><span class="cl">        SSLCertificateFile /srv/cert/example.com/cert.pem
</span></span><span class="line"><span class="cl">        SSLCertificateKeyFile /srv/cert/example.com/key.pem
</span></span><span class="line"><span class="cl">        SSLCertificateChainFile /srv/cert/example.com/fullchain.pem
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        DocumentRoot <span class="s2">&#34;/silv/www/example.com/html&#34;</span>
</span></span><span class="line"><span class="cl">        ServerName example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // ...
</span></span><span class="line"><span class="cl">&lt;/VirtualHost&gt;
</span></span></code></pre></div><p>And finally, restart Apache with <code>service apache2 restart</code> and you can access your website via HTTPS! Easy, right?</p>
<h2 id="4-automating-certificate-renewal-via-cronjob">4. Automating certificate renewal via cronjob</h2>
<p>The wonderful thing about the <code>simp_le</code> client is that the command for the initial creation and the renewal is the same, so you can run the exact same command as above in a script from a cronjob. I created a script called <code>/srv/bin/cert-renew</code> to do just that:</p>
<p><em>Script to check/renew the certificates every night</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /srv/bin/cert-renew 
</span></span><span class="line"><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /srv/cert/example.com
</span></span><span class="line"><span class="cl">simp_le -d example.com:/srv/www/example.com/html -f key.pem -f cert.pem -f fullchain.pem -f account_key.json <span class="se">\
</span></span></span><span class="line"><span class="cl">	<span class="o">&amp;&amp;</span> service apache2 reload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /srv/cert/example.xyz
</span></span><span class="line"><span class="cl">simp_le -d example.xyz:/srv/www/example.xyz/html -f key.pem -f cert.pem -f fullchain.pem -f account_key.json <span class="se">\
</span></span></span><span class="line"><span class="cl">	<span class="o">&amp;&amp;</span> service apache2 reload
</span></span></code></pre></div><p>This will renew the certificates for two domains (if necessary) and reload Apache if the certificate has been renewed. Note that Apache will not be reloaded if the certificate has not been altered.</p>
<p>The matching cronjob looks like this:</p>
<p><em>Cronjob to run renewal script every night</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">PATH</span><span class="o">=</span>/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
</span></span><span class="line"><span class="cl"><span class="m">43</span> <span class="m">1</span> * * * /srv/bin/cert-renew <span class="o">||</span> <span class="nb">true</span>
</span></span></code></pre></div><p>That&rsquo;s it! Now every night at 1:43am, your certificates are checked and renewed if necessary.</p>
<h2 id="a-about-this-post">A. About this post</h2>
<p>I&rsquo;m trying a new section for my blog. I call it <a href="/blog/categories/code-snippets/">Code Snippets</a>. It&rsquo;ll be very short, code-focused posts of things I recently discovered or find fascinating or helpful. I hope this helps.</p>]]></content:encoded></item><item><title>How-To: Create a Debian package and a Debian repository</title><link>https://heckel.io/blog/how-to-create-debian-package-and-debian-repository/</link><pubDate>Sun, 18 Oct 2015 18:15:21 -0400</pubDate><guid>https://heckel.io/blog/how-to-create-debian-package-and-debian-repository/</guid><description>Debian packages and repositories are everywhere, yet many people don&amp;rsquo;t understand that creating them is actually pretty easy. While there are dozens of tutorials out there, none of them seemed to really show a good step-by-step. This is a quick tutorial on how to create a Debian package from …</description><content:encoded><![CDATA[<p>Debian packages and repositories are everywhere, yet many people don&rsquo;t understand that creating them is actually pretty easy. While there are dozens of tutorials out there, none of them seemed to really show a good step-by-step. This is a quick tutorial on how to <strong>create a Debian package from scratch, and how to create a simple Debian repository.</strong></p>
<h2 id="1-demo-package-netutils">1. Demo package &rsquo;netutils'</h2>
<p>For the sake of this tutorial, we&rsquo;ll create a package called <strong>netutils</strong> with a command called <code>ipaddr</code>. The purpose of the command will be to get the external IP address, just like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> <span class="c1"># Target command &#39;ipaddr&#39; as part of the</span>
</span></span><span class="line"><span class="cl"><span class="c1"># to-be-built &#39;netutils&#39; package</span>
</span></span><span class="line"><span class="cl">$ ipaddr
</span></span><span class="line"><span class="cl">82.143.12.32
</span></span></code></pre></div><h2 id="2-create-a-debian-package">2. Create a Debian package</h2>
<p>Let&rsquo;s start by creating the empty project directory <code>netutils/</code>. This folder will contain both the source code and the Debian build instructions.</p>
<h3 id="21-create-the-debian-directory">2.1. Create the <code>debian/</code> directory</h3>
<p>For now, we&rsquo;ll use the <code>dh_make</code> command to create the <code>debian/</code> directory:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create empty project folder</span>
</span></span><span class="line"><span class="cl">$ mkdir -p Debian/netutils
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> Debian/netutils
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create debian/ folder with example files (.ex)</span>
</span></span><span class="line"><span class="cl">$ dh_make <span class="se">\
</span></span></span><span class="line"><span class="cl">  --native <span class="se">\
</span></span></span><span class="line"><span class="cl">  --single <span class="se">\
</span></span></span><span class="line"><span class="cl">  --packagename netutils_1.0.0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --email phil@example.com
</span></span></code></pre></div><p>This created the <code>debian/</code> folder. Explore them! Especially the example files (*.ex) as well as most importantly the files:</p>
<ul>
<li><code>debian/control</code></li>
<li><code>debian/changelog</code></li>
<li><code>debian/rules</code></li>
</ul>
<p>There are maaaany files, and Debian documented all of them in the <a href="https://www.debian.org/doc/manuals/maint-guide/dother.en.html">Debian Maintainer&rsquo;s Guide</a>. For now, we don&rsquo;t care about the rest of them, but here&rsquo;s a list of all the created files:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ find debian/ <span class="p">|</span> sort
</span></span><span class="line"><span class="cl">debian/
</span></span><span class="line"><span class="cl">debian/changelog
</span></span><span class="line"><span class="cl">debian/compat
</span></span><span class="line"><span class="cl">debian/control
</span></span><span class="line"><span class="cl">debian/copyright
</span></span><span class="line"><span class="cl">debian/netutils.cron.d.ex
</span></span><span class="line"><span class="cl">debian/netutils.default.ex
</span></span><span class="line"><span class="cl">debian/netutils.doc-base.EX
</span></span><span class="line"><span class="cl">debian/docs
</span></span><span class="line"><span class="cl">debian/init.d.ex
</span></span><span class="line"><span class="cl">debian/manpage.1.ex
</span></span><span class="line"><span class="cl">debian/manpage.sgml.ex
</span></span><span class="line"><span class="cl">debian/manpage.xml.ex
</span></span><span class="line"><span class="cl">debian/menu.ex
</span></span><span class="line"><span class="cl">debian/postinst.ex
</span></span><span class="line"><span class="cl">debian/postrm.ex
</span></span><span class="line"><span class="cl">debian/preinst.ex
</span></span><span class="line"><span class="cl">debian/prerm.ex
</span></span><span class="line"><span class="cl">debian/README
</span></span><span class="line"><span class="cl">debian/README.Debian
</span></span><span class="line"><span class="cl">debian/README.source
</span></span><span class="line"><span class="cl">debian/rules
</span></span><span class="line"><span class="cl">debian/source
</span></span><span class="line"><span class="cl">debian/source/format
</span></span><span class="line"><span class="cl">debian/watch.ex
</span></span></code></pre></div><h3 id="22-build-the-first-empty-package">2.2. Build the first (empty) package</h3>
<p>As you can see there are a lot of files to tweak the package, but for now we&rsquo;ll ignore all that and build an empty package. The <code>dpkg-buildpackage</code> command can be used to do that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Build empty package</span>
</span></span><span class="line"><span class="cl">$ dpkg-buildpackage
</span></span></code></pre></div><p>That&rsquo;s it! That command built four files:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Explore the files that got built</span>
</span></span><span class="line"><span class="cl">$ ls -1 ../netutils_*
</span></span><span class="line"><span class="cl">../netutils_1.0.0_amd64.changes
</span></span><span class="line"><span class="cl">../netutils_1.0.0_amd64.deb
</span></span><span class="line"><span class="cl">../netutils_1.0.0.dsc
</span></span><span class="line"><span class="cl">../netutils_1.0.0.tar.gz
</span></span></code></pre></div><ul>
<li><code>.tar.gz</code>: Source package, contains the contents of the <code>netutils/</code> folder</li>
<li><code>.deb</code>: Debian package, contains the installable package</li>
<li><code>.dsc/.changes</code>: Signature files, cryptographic signatures of all files</li>
</ul>
<p>Obviously, the most interesting file (for now) is the <code>.deb</code> file.<br>
Let&rsquo;s examine the contents with the <code>dpkg -c</code> (aka <code>dpkg --contents</code>) command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Listing the contents with &#39;dpkg --contents&#39;</span>
</span></span><span class="line"><span class="cl">$ dpkg -c ../netutils_1.0.0_amd64.deb 
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2015-10-18 14:52 ./
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2015-10-18 14:52 ./usr/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2015-10-18 14:52 ./usr/share/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2015-10-18 14:52 ./usr/share/doc/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2015-10-18 14:52 ./usr/share/doc/netutils/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">141</span> 2015-10-18 14:47 ./usr/share/doc/netutils/changelog.gz
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">183</span> 2015-10-18 14:47 ./usr/share/doc/netutils/README.Debian
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root      <span class="m">1401</span> 2015-10-18 14:47 ./usr/share/doc/netutils/copyright
</span></span></code></pre></div><p>Nothing really in the package except the default <code>changelog</code>, <code>copyright</code> and <code>README</code> file. Instead of just listing the contents, we can also extract a Debian archive to a local location with <code>dpkg -x</code> (aka <code>dpkg --extract</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Extract the archive without installing it</span>
</span></span><span class="line"><span class="cl">$ dpkg -x ../netutils_1.0.0_amd64.deb ../netutils_extracted
</span></span><span class="line"><span class="cl">$ find ../netutils_extracted/
</span></span><span class="line"><span class="cl">../netutils_extracted/
</span></span><span class="line"><span class="cl">../netutils_extracted/usr
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share/doc
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share/doc/netutils
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share/doc/netutils/copyright
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share/doc/netutils/changelog.gz
</span></span><span class="line"><span class="cl">../netutils_extracted/usr/share/doc/netutils/README.Debian
</span></span></code></pre></div><h3 id="23-install-the-empty-package">2.3. Install the (empty) package</h3>
<p>Enough playing around with the <code>.deb</code> file. Let&rsquo;s install it with the <code>dpkg -i</code> (aka <code>dpkg --install</code>) command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install the (empty) package</span>
</span></span><span class="line"><span class="cl">$ sudo dpkg -i ../netutils_1.0.0_amd64.deb 
</span></span><span class="line"><span class="cl">Preparing to unpack ../netutils_1.0.0_amd64.deb ...
</span></span><span class="line"><span class="cl">Unpacking netutils <span class="o">(</span>1.0.0<span class="o">)</span> ...
</span></span><span class="line"><span class="cl">Setting up netutils <span class="o">(</span>1.0.0<span class="o">)</span> ...
</span></span></code></pre></div><p>Done. That installed the package. Check out that it was actually installed by listing the installed packages with <code>dpkg -l</code> (aka <code>dpkg --list</code>). The list itself will contain all installed (or half-installed/configured) packages on the system, so we&rsquo;ll use <code>grep</code> to limit the output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Proof that it&#39;s actually installed</span>
</span></span><span class="line"><span class="cl">$ dpkg -l <span class="p">|</span> grep netutils
</span></span><span class="line"><span class="cl">  ii  netutils    1.0.0        amd64        &lt;insert up to <span class="m">60</span> chars description&gt;
</span></span><span class="line"><span class="cl"><span class="c1"># ^^  ^           ^            ^            ^</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||  |           |            |            |</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||  |           |            |             - Description</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||  |           |             - Architecture</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||  |            - Version</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||   - Package name</span>
</span></span><span class="line"><span class="cl"><span class="c1"># | - Actual/current package state (n = not installed, i = installed, ...)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  - Desired package state (i = install, r = remove, p = purge, ...)</span>
</span></span></code></pre></div><p>The columns in the list are <strong>desired package state</strong>, <strong>actual package state</strong>, <strong>package name</strong>, <strong>package version</strong>, <strong>package architecture</strong> and a short <strong>package description</strong>. If all is well, the first column will contain <code>ii</code>, which means that the package is properly installed. The <a href="http://man7.org/linux/man-pages/man1/dpkg-query.1.html">dpkg-query man page</a> (<code>man dpkg-query</code>) contains all the possible desired/actual state values.</p>
<p>Now that the package is installed, you can list its contents with the <code>dpkg -L</code> (aka <code>dpkg --listfiles</code>) command. Unlike <code>dpkg -c</code>, it only works for installed packages:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># List of installed Debian package contents</span>
</span></span><span class="line"><span class="cl">$ dpkg -L netutils 
</span></span><span class="line"><span class="cl">/.
</span></span><span class="line"><span class="cl">/usr
</span></span><span class="line"><span class="cl">/usr/share
</span></span><span class="line"><span class="cl">/usr/share/doc
</span></span><span class="line"><span class="cl">/usr/share/doc/netutils
</span></span><span class="line"><span class="cl">/usr/share/doc/netutils/copyright
</span></span><span class="line"><span class="cl">/usr/share/doc/netutils/changelog.gz
</span></span><span class="line"><span class="cl">/usr/share/doc/netutils/README.Debian
</span></span></code></pre></div><h3 id="24-adding-files-and-updating-the-changelog">2.4. Adding files and updating the changelog</h3>
<p>Okay, enough with the empty package. Let&rsquo;s now <strong>add actual files to our package</strong>. To do that, let&rsquo;s create a folder <code>files/</code> and use it to mirror the Linux filesystem structure. In that folder, let&rsquo;s create our script <code>files/usr/bin/ipaddr</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ mkdir -p files/usr/bin
</span></span><span class="line"><span class="cl">$ touch files/usr/bin/ipaddr
</span></span><span class="line"><span class="cl">$ chmod +x files/usr/bin/ipaddr
</span></span><span class="line"><span class="cl">$ vi files/usr/bin/ipaddr
</span></span><span class="line"><span class="cl">  <span class="c1"># Script contents see below</span>
</span></span></code></pre></div><p>Obviously, you can add whatever you want to your script, but for the sake of this tutorial, we&rsquo;ll go with a short script to grab the public IP address from a service called <a href="https://www.ipify.org/">ipify</a>. It provides an API to return the IP address in various formats. We&rsquo;ll grab it with <code>curl</code> in JSON and then use <code>jq</code> to parse out the &lsquo;ip&rsquo; field:</p>
<p><em>Create script &lsquo;files/usr/bin/ipaddr&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">curl --silent <span class="s1">&#39;https://api.ipify.org?format=json&#39;</span> <span class="p">|</span> jq .ip --raw-output
</span></span></code></pre></div><p>If we were to rebuild the package now, the <code>dpkg-buildpackage</code> command <strong>wouldn&rsquo;t know which files to include in the package</strong>. So we&rsquo;ll create the <code>debian/install</code> file to list directories to include (e.g. <code>vi debian/install</code>):</p>
<p><em>Create file &lsquo;debian/install&rsquo; to tell dpkg-buildpackage what files to include</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">files/usr/* usr
</span></span></code></pre></div><p>This basically means that everything in the <code>files/usr/</code> folder will be installed at <code>/usr/</code> on the target file system when the package is installed.</p>
<p>Once this is done, we could just rebuild and reinstall the package, but let&rsquo;s go one step further and <strong>update the version of the package to 1.1.0</strong>. Versions are handled by the <code>debian/changelog</code> file. You can update it manually, or use the <code>dch</code> script (short for &ldquo;Debian changelog&rdquo;) to do so:</p>
<p><em>Update debian/changelog</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># We changed the package, let&#39;s update the version and changelog for it to 1.1.0</span>
</span></span><span class="line"><span class="cl">$ dch -im
</span></span><span class="line"><span class="cl">  <span class="c1"># Opens the editor ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ cat debian/changelog 
</span></span><span class="line"><span class="cl">netutils <span class="o">(</span>1.1.0<span class="o">)</span> unstable<span class="p">;</span> <span class="nv">urgency</span><span class="o">=</span>medium
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  * Added <span class="s1">&#39;ipaddr&#39;</span> script
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> -- Philipp Heckel &lt;phil@example.com&gt;  Sun, <span class="m">18</span> Oct <span class="m">2015</span> 15:55:42 +0100
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">netutils <span class="o">(</span>1.0.0<span class="o">)</span> unstable<span class="p">;</span> <span class="nv">urgency</span><span class="o">=</span>low
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  * Initial Release.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> -- Philipp Heckel &lt;phil@example.com&gt;  Sun, <span class="m">18</span> Oct <span class="m">2015</span> 15:06:29 +0100
</span></span></code></pre></div><p>While the changelog looks just like a dumb text file, Debian uses it to manage the version number of the package, as well as to define the distribution(s) that the package is built for (here: &ldquo;unstable&rdquo;). Furthermore, it defines who has to sign the package (name of changelog editor).</p>
<p>After that, let&rsquo;s rebuild and reinstall the package:</p>
<p><em>Rebuild and reinstall the package</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Rebuild &amp; re-install package</span>
</span></span><span class="line"><span class="cl">$ dpkg-buildpackage
</span></span><span class="line"><span class="cl">$ sudo dpkg -i ../netutils_1.1.0_amd64.deb
</span></span><span class="line"><span class="cl">$ dpkg -l <span class="p">|</span> grep netutils
</span></span><span class="line"><span class="cl">ii  netutils       1.1.0          amd64        &lt;insert up to <span class="m">60</span> chars description&gt;
</span></span></code></pre></div><p>Looks like it installed correctly. Let&rsquo;s check if the <code>ipaddr</code> script is where it&rsquo;s supposed to be. And then let&rsquo;s try to run it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Proof that it&#39;s where it&#39;s supposed to be</span>
</span></span><span class="line"><span class="cl">$ which ipaddr
</span></span><span class="line"><span class="cl">/usr/bin/ipaddr
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now let&#39;s run it</span>
</span></span><span class="line"><span class="cl">$ ipaddr
</span></span><span class="line"><span class="cl">/usr/bin/ipaddr: line 3: jq: <span class="nb">command</span> not found
</span></span></code></pre></div><p>Oops! We forgot that not every system might have <code>jq</code> installed. <strong>Let&rsquo;s add it as a dependency!</strong></p>
<h3 id="25-updating-the-description-and-adding-dependencies">2.5. Updating the description and adding dependencies</h3>
<p>Each Debian package can depend on other packages. In the case of our <code>ipaddr</code> script, we use the <code>curl</code> and <code>jq</code> commands, so the &rsquo;netutils&rsquo; package depends on these commands.</p>
<p>Since typically the command name is different from the package name, it might be necessary to find the actual package name. This can be done with the <code>dpkg -S</code> (aka <code>dpkg --search</code>) command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Find the full path for the &#39;curl&#39; command</span>
</span></span><span class="line"><span class="cl">$ which curl
</span></span><span class="line"><span class="cl">/usr/bin/curl
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Seems that the &#39;curl&#39; command is contained in a package called &#39;curl&#39;</span>
</span></span><span class="line"><span class="cl">$ dpkg -S /usr/bin/curl
</span></span><span class="line"><span class="cl">curl: /usr/bin/curl
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The same can be done for &#39;jq&#39; if it&#39;s installed.</span>
</span></span></code></pre></div><p>To add the dependencies to the &rsquo;netutils&rsquo; package, edit the <code>Depends:</code> section in the <code>debian/control</code> file (e.g. via <code>vi debian/control</code>):</p>
<p><em>Edit sections &lsquo;Depends&rsquo; and &lsquo;Description&rsquo; in &lsquo;debian/control&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Source: netutils
</span></span><span class="line"><span class="cl">Section: utils
</span></span><span class="line"><span class="cl">Priority: optional
</span></span><span class="line"><span class="cl">Maintainer: Philipp Heckel &lt;phil@example.com&gt;
</span></span><span class="line"><span class="cl">Build-Depends: debhelper <span class="o">(</span>&gt;<span class="o">=</span> 8.0.0<span class="o">)</span>
</span></span><span class="line"><span class="cl">Standards-Version: 3.9.4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Package: netutils
</span></span><span class="line"><span class="cl">Architecture: any
</span></span><span class="line"><span class="cl">Depends: <span class="si">${</span><span class="nv">shlibs</span><span class="p">:</span><span class="nv">Depends</span><span class="si">}</span>, <span class="si">${</span><span class="nv">misc</span><span class="p">:</span><span class="nv">Depends</span><span class="si">}</span>, curl, jq
</span></span><span class="line"><span class="cl">Description: Network management tools
</span></span><span class="line"><span class="cl">  Includes various tools <span class="k">for</span> network management.
</span></span></code></pre></div><p>That&rsquo;s it. All that&rsquo;s left (again) is to update the version to 1.2.0, rebuild the package and reinstall it:</p>
<p><em>Rebuild and reinstall the package</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Rebuild &amp; re-install package</span>
</span></span><span class="line"><span class="cl">$ dch -im
</span></span><span class="line"><span class="cl">$ dpkg-buildpackage
</span></span><span class="line"><span class="cl">$ sudo dpkg -i ../netutils_1.2.0_amd64.deb 
</span></span><span class="line"><span class="cl">Preparing to unpack ../netutils_1.2.0_amd64.deb ...
</span></span><span class="line"><span class="cl">Unpacking netutils <span class="o">(</span>1.2.0<span class="o">)</span> over <span class="o">(</span>1.1.0<span class="o">)</span> ...
</span></span><span class="line"><span class="cl">dpkg: dependency problems prevent configuration of netutils:
</span></span><span class="line"><span class="cl"> netutils depends on jq<span class="p">;</span> however:
</span></span><span class="line"><span class="cl">  Package jq is not installed.
</span></span><span class="line"><span class="cl">dpkg: error processing package netutils <span class="o">(</span>--install<span class="o">)</span>:
</span></span><span class="line"><span class="cl"> dependency problems - leaving unconfigured
</span></span><span class="line"><span class="cl">Errors were encountered <span class="k">while</span> processing:
</span></span><span class="line"><span class="cl"> netutils
</span></span></code></pre></div><p>Wow. What happened here?</p>
<p>Well, unlike <code>apt-get install</code>, the <code>dpkg -i</code> command <strong>does not automatically resolve and install missing dependencies</strong>. It just complains about them. That is <strong>perfectly normal</strong> and expected. In fact, it gives us the perfect opportunity to check the package state (like we did above):</p>
<p><em>Invalid package state, as indicated by &lsquo;iU&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># The package is in an invalid state:</span>
</span></span><span class="line"><span class="cl">$ dpkg -l <span class="p">|</span> grep netutils
</span></span><span class="line"><span class="cl">  iU  netutils  1.2.0      amd64        Network management tools
</span></span><span class="line"><span class="cl"><span class="c1"># ^^                                     ^</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||                                     |</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ||                                      - Yeyy, the new description</span>
</span></span><span class="line"><span class="cl"><span class="c1"># | - Actual package state is &#39;U&#39; / &#39;Unpacked&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  - Desired package state is &#39;i&#39; / &#39;install&#39;</span>
</span></span></code></pre></div><p>As you can see by the output, the desired state for the package is <strong>&lsquo;i&rsquo;</strong> (= installed), but the actual state is <strong>&lsquo;U&rsquo;</strong> (= Unpacked). That&rsquo;s not good. Luckily though, dependencies can be automatically resolved by <code>apt-get install -f</code> (aka <code>apt-get install --fix-broken</code>):</p>
<p><em>Fixing broken/invalid package state and dependencies</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo apt-get install -f
</span></span><span class="line"><span class="cl">The following extra packages will be installed:
</span></span><span class="line"><span class="cl">  jq
</span></span><span class="line"><span class="cl">The following NEW packages will be installed
</span></span><span class="line"><span class="cl">  jq
</span></span><span class="line"><span class="cl"><span class="m">0</span> to upgrade, <span class="m">1</span> to newly install, <span class="m">0</span> to remove and <span class="m">11</span> not to upgrade.
</span></span><span class="line"><span class="cl"><span class="m">1</span> not fully installed or removed.
</span></span><span class="line"><span class="cl">Selecting previously unselected package jq.
</span></span><span class="line"><span class="cl">Preparing to unpack .../jq_1.3-1.1ubuntu1_amd64.deb ...
</span></span><span class="line"><span class="cl">Unpacking jq <span class="o">(</span>1.3-1.1ubuntu1<span class="o">)</span> ...
</span></span><span class="line"><span class="cl">Setting up jq <span class="o">(</span>1.3-1.1ubuntu1<span class="o">)</span> ...
</span></span><span class="line"><span class="cl">Setting up netutils <span class="o">(</span>1.2.0<span class="o">)</span> ...
</span></span></code></pre></div><p>Finally, it&rsquo;s installed correctly. Now let&rsquo;s test it:</p>
<p><em>Testing the &lsquo;ipaddr&rsquo; script</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># The package is installed correctly (&#39;ii&#39;)</span>
</span></span><span class="line"><span class="cl">$ dpkg -l <span class="p">|</span> grep netutils
</span></span><span class="line"><span class="cl">ii  netutils  1.2.0      amd64        Network management tools
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># And it seems to work!</span>
</span></span><span class="line"><span class="cl">$ ipaddr
</span></span><span class="line"><span class="cl">82.143.12.32
</span></span></code></pre></div><h2 id="3-create-and-use-a-debian-repository">3. Create and use a Debian repository</h2>
<h3 id="31-dpkg--i-vs-apt-get-install">3.1. <code>dpkg -i</code> vs. <code>apt-get install</code></h3>
<p>So why did we use <code>dpkg -i</code> and not <code>apt-get install</code>? Because <code>apt-get install</code> looks in all the configured Debian repositories; it does not look for files. And as of right now, there is no Debian repository serving the <strong>netutils</strong> package:</p>
<p><em>No Debian repository serving the &rsquo;netutils&rsquo; package</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># There is no Debian repo serving the &#39;netutils&#39; package!</span>
</span></span><span class="line"><span class="cl">$ sudo apt-get install netutils
</span></span><span class="line"><span class="cl">Reading package lists... Done
</span></span><span class="line"><span class="cl">Building dependency tree       
</span></span><span class="line"><span class="cl">Reading state information... Done
</span></span><span class="line"><span class="cl">E: Unable to locate package netutils
</span></span></code></pre></div><h3 id="32-locally-configured-debianapt-repositories-etcaptsourceslistdlist">3.2. Locally configured Debian/APT repositories (<code>/etc/apt/sources.list.d/*.list</code>)</h3>
<p>So where does &lsquo;apt-get install&rsquo; look? How does it know where to retrieve/download the packages and its dependencies from?</p>
<p>APT repositories are configured in the files <code>/etc/apt/sources.list</code> and <code>/etc/apt/sources.list.d/*.list</code>. On a typical Debian/Ubuntu system, there are quite a few of them:</p>
<p><em>APT repositories are configured in /etc/apt/sources.list and /etc/apt/sources.list.d/*.list</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ find /etc/apt/sources.list /etc/apt/sources.list.d/
</span></span><span class="line"><span class="cl">/etc/apt/sources.list
</span></span><span class="line"><span class="cl">/etc/apt/sources.list.d/
</span></span><span class="line"><span class="cl">/etc/apt/sources.list.d/syncany.list
</span></span><span class="line"><span class="cl">/etc/apt/sources.list.d/archive.philippheckel.com.list
</span></span><span class="line"><span class="cl">/etc/apt/sources.list.d/official-package-repositories.list
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><p>Each of these files contains a list of Debian repositories:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /etc/apt/sources.list.d/official-package-repositories.list 
</span></span><span class="line"><span class="cl">deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse
</span></span><span class="line"><span class="cl"><span class="c1">#   ^                                ^      ^</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   |                                |      |</span>
</span></span><span class="line"><span class="cl"><span class="c1">#    - Main URL/source               |       - Component list (e.g. main, universe, ...)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#                                     - Distribution (e.g. precise, trusty, release, ...)</span>
</span></span></code></pre></div><h3 id="33-the-apt-cache-and-packagespackagesgz-files">3.3. The APT cache and <code>Packages</code>/<code>Packages.gz</code> files</h3>
<p>Whenever <code>apt-get update</code> is called, all of these repos are checked for new versions. The APT cache (see <code>apt-cache</code> command) is refreshed:</p>
<p><em>Refreshing the APT cache with &lsquo;apt-get update&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ apt-get update <span class="p">|</span> grep Packages
</span></span><span class="line"><span class="cl">Hit http://archive.ubuntu.com trusty/main amd64 Packages
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><p>The &lsquo;Hit&rsquo; actually means that it downloaded/checked the <code>Packages.gz</code> file (in the example above from the URL <a href="http://archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.gz">http://archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.gz</a>). The file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl -s http://archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.gz <span class="p">|</span> zcat <span class="p">|</span> head -n <span class="m">14</span>
</span></span><span class="line"><span class="cl">Package: account-plugin-aim
</span></span><span class="line"><span class="cl">Priority: optional
</span></span><span class="line"><span class="cl">Section: gnome
</span></span><span class="line"><span class="cl">Installed-Size: <span class="m">941</span>
</span></span><span class="line"><span class="cl">Maintainer: Ubuntu Developers &lt;ubuntu-devel-discuss@lists.ubuntu.com&gt;
</span></span><span class="line"><span class="cl">Original-Maintainer: Debian Telepathy maintainers &lt;pkg-telepathy-maintainers@lists.alioth.debian.org&gt;
</span></span><span class="line"><span class="cl">Architecture: amd64
</span></span><span class="line"><span class="cl">Source: empathy
</span></span><span class="line"><span class="cl">Version: 3.8.6-0ubuntu9
</span></span><span class="line"><span class="cl">Replaces: account-plugin-empathy
</span></span><span class="line"><span class="cl">Depends: empathy <span class="o">(=</span> 3.8.6-0ubuntu9<span class="o">)</span>, telepathy-haze, mcp-account-manager-uoa, unity-asset-pool <span class="o">(</span>&gt;&gt; 0.8.24daily13.03.20.1<span class="o">)</span>
</span></span><span class="line"><span class="cl">Breaks: account-plugin-empathy
</span></span><span class="line"><span class="cl">Filename: pool/main/e/empathy/account-plugin-aim_3.8.6-0ubuntu9_amd64.deb
</span></span><span class="line"><span class="cl">Size: <span class="m">8838</span>
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><p>Looks familiar? Yes! A <code>Packages.gz</code> file looks very, very similar to the <code>debian/control</code> file in our package. And that&rsquo;s no coincidence.</p>
<h3 id="34-creating-a-local-debian-repository">3.4. Creating a local Debian repository</h3>
<p>So in a nutshell, all we need to create a Debian repository is a <code>Packages.gz</code> file and a way to expose this file &ndash; either via HTTP or locally. Easy, easy, easy!</p>
<p>For the sake of this tutorial, let&rsquo;s create a local repository at <code>/tmp/repo</code> and copy all the <code>netutils_*.deb</code> files to it. Once that is done, we&rsquo;ll create a <code>Packages.gz</code> file using the <code>dpkg-scanpackages</code> command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ mkdir /tmp/repo
</span></span><span class="line"><span class="cl">$ cp ../netutils_*.deb /tmp/repo/
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> /tmp/repo
</span></span><span class="line"><span class="cl">$ dpkg-scanpackages -m . <span class="p">|</span> gzip --fast &gt; Packages.gz
</span></span></code></pre></div><p>That&rsquo;s it. Let&rsquo;s check what the <code>Packages.gz</code> file looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ zcat /tmp/repo/Packages.gz
</span></span><span class="line"><span class="cl">$ zcat Packages.gz 
</span></span><span class="line"><span class="cl">Package: netutils
</span></span><span class="line"><span class="cl">Version: 1.0.0
</span></span><span class="line"><span class="cl">Architecture: amd64
</span></span><span class="line"><span class="cl">Maintainer: Philipp Heckel &lt;phil@example.com&gt;
</span></span><span class="line"><span class="cl">Installed-Size: <span class="m">2</span>
</span></span><span class="line"><span class="cl">Filename: ./netutils_1.0.0_amd64.deb
</span></span><span class="line"><span class="cl">Size: <span class="m">1994</span>
</span></span><span class="line"><span class="cl">MD5sum: 1de1a751a5bc0c9892e3e7c7740c57a9
</span></span><span class="line"><span class="cl">SHA1: 2ea91ec16519638fcfd371d1530fe13aaa123f49
</span></span><span class="line"><span class="cl">SHA256: c40ea5184715e6d6cdf946fd66e84c19c4b0d7f516fe6842d57da385af7fc180
</span></span><span class="line"><span class="cl">Section: unknown
</span></span><span class="line"><span class="cl">Priority: optional
</span></span><span class="line"><span class="cl">Homepage: &lt;insert the upstream URL, <span class="k">if</span> relevant&gt;
</span></span><span class="line"><span class="cl">Description: &lt;insert up to <span class="m">60</span> chars description&gt;
</span></span><span class="line"><span class="cl"> &lt;insert long description, indented with spaces&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Package: netutils
</span></span><span class="line"><span class="cl">Version: 1.1.0
</span></span><span class="line"><span class="cl">Architecture: amd64
</span></span><span class="line"><span class="cl">Maintainer: Philipp Heckel &lt;phil@example.com&gt;
</span></span><span class="line"><span class="cl">Installed-Size: <span class="m">2</span>
</span></span><span class="line"><span class="cl">Depends: curl, jq
</span></span><span class="line"><span class="cl">Filename: ./netutils_1.1.0_amd64.deb
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><h3 id="35-adding-the-local-debian-repository-and-installing-via-apt-get-install">3.5. Adding the local Debian repository and installing via <code>apt-get install</code></h3>
<p>All that&rsquo;s left is to add that repository to the local APT config by adding a <code>*.list</code> file, e.g. at <code>/etc/apt/sources.list.d/local.list</code>:</p>
<p><em>Local repository definition in file &lsquo;/etc/apt/sources.list.d/local.list&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">deb file:/tmp/repo ./
</span></span></code></pre></div><p>Now we can install the <strong>netutils</strong> package via <code>apt-get install</code>:</p>
<p><em>Installing the &rsquo;netutils&rsquo; package via apt-get</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Update the APT cache and install the package</span>
</span></span><span class="line"><span class="cl">$ apt-get update
</span></span><span class="line"><span class="cl">$ sudo apt-get install netutils
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Yup, it works</span>
</span></span><span class="line"><span class="cl">$ ipaddr
</span></span><span class="line"><span class="cl">82.143.12.32
</span></span></code></pre></div><h3 id="36-listing-and-installing-older-versions">3.6. Listing and installing older versions</h3>
<p>Maybe you noticed that the <code>Packages.gz</code> file contained multiple versions of the package. That means that we can actually list and install specific/older versions of the package if we wanted to.</p>
<p>Let&rsquo;s explore what versions we could install. This can be done with <code>apt-cache policy</code> or <code>apt-cache madison</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># You can use &#39;apt-cache policy&#39; to display all available versions</span>
</span></span><span class="line"><span class="cl">$ apt-cache policy netutils
</span></span><span class="line"><span class="cl">netutils:
</span></span><span class="line"><span class="cl">  Installed: 1.2.0
</span></span><span class="line"><span class="cl">  Candidate: 1.2.0
</span></span><span class="line"><span class="cl">  Version table:
</span></span><span class="line"><span class="cl"> *** 1.2.0 <span class="m">0</span>
</span></span><span class="line"><span class="cl">        <span class="m">500</span> file:/tmp/repo/ ./ Packages
</span></span><span class="line"><span class="cl">        <span class="m">100</span> /var/lib/dpkg/status
</span></span><span class="line"><span class="cl">     1.1.0 <span class="m">0</span>
</span></span><span class="line"><span class="cl">        <span class="m">500</span> file:/tmp/repo/ ./ Packages
</span></span><span class="line"><span class="cl">     1.0.0 <span class="m">0</span>
</span></span><span class="line"><span class="cl">        <span class="m">500</span> file:/tmp/repo/ ./ Packages
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The &#39;apt-cache madison&#39; command does the same</span>
</span></span><span class="line"><span class="cl">$ apt-cache madison netutils
</span></span><span class="line"><span class="cl"> netutils <span class="p">|</span>      1.2.0 <span class="p">|</span> file:/tmp/repo/ ./ Packages
</span></span><span class="line"><span class="cl"> netutils <span class="p">|</span>      1.1.0 <span class="p">|</span> file:/tmp/repo/ ./ Packages
</span></span><span class="line"><span class="cl"> netutils <span class="p">|</span>      1.0.0 <span class="p">|</span> file:/tmp/repo/ ./ Packages
</span></span></code></pre></div><p>By default <code>apt-get install</code> always picks the newest one, but by doing <code>apt-get install netutils=1.0.0</code>, we could tell it to install version 1.0.0:</p>
<p><em>Downgrading a package</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo apt-get install <span class="nv">netutils</span><span class="o">=</span>1.0.0
</span></span><span class="line"><span class="cl">Reading package lists... Done
</span></span><span class="line"><span class="cl">Building dependency tree       
</span></span><span class="line"><span class="cl">Reading state information... Done
</span></span><span class="line"><span class="cl">The following packages will be DOWNGRADED:
</span></span><span class="line"><span class="cl">  netutils
</span></span><span class="line"><span class="cl"><span class="m">0</span> to upgrade, <span class="m">0</span> to newly install, <span class="m">1</span> to downgrade, <span class="m">0</span> to remove and <span class="m">13</span> not to upgrade.
</span></span><span class="line"><span class="cl">Need to get <span class="m">0</span> B/1,994 B of archives.
</span></span><span class="line"><span class="cl">After this operation, <span class="m">0</span> B of additional disk space will be used.
</span></span><span class="line"><span class="cl">Do you want to <span class="k">continue</span>? <span class="o">[</span>Y/n<span class="o">]</span> y
</span></span><span class="line"><span class="cl">dpkg: warning: downgrading netutils from 1.2.0 to 1.0.0
</span></span><span class="line"><span class="cl"><span class="o">(</span>Reading database ... <span class="m">208809</span> files and directories currently installed.<span class="o">)</span>
</span></span><span class="line"><span class="cl">Preparing to unpack ..././netutils_1.0.0_amd64.deb ...
</span></span><span class="line"><span class="cl">Unpacking netutils <span class="o">(</span>1.0.0<span class="o">)</span> over <span class="o">(</span>1.2.0<span class="o">)</span> ...
</span></span><span class="line"><span class="cl">Setting up netutils <span class="o">(</span>1.0.0<span class="o">)</span> ...
</span></span></code></pre></div><h3 id="37-normaltypical-repository-layout">3.7. Normal/typical repository layout</h3>
<p>Our local repository is very basic. It does not have the same sophisticated structure of a normal repository such as the main upstream <a href="http://archive.ubuntu.com/ubuntu/">Ubuntu repository</a>. Normal repositories looks more like this:</p>
<p><em>Layout of a standard Debian repository (excerpt)</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ find dists/trusty/ -maxdepth 3<span class="p">;</span> find pool/ -maxdepth <span class="m">1</span>
</span></span><span class="line"><span class="cl">dists/trusty
</span></span><span class="line"><span class="cl">dists/trusty/Release.gpg
</span></span><span class="line"><span class="cl">dists/trusty/Release
</span></span><span class="line"><span class="cl">dists/trusty/InRelease
</span></span><span class="line"><span class="cl">dists/trusty/main
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64/Packages
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64/Release
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64/Packages.bz2
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64/Packages.diff
</span></span><span class="line"><span class="cl">dists/trusty/main/binary-amd64/Packages.gz
</span></span><span class="line"><span class="cl">dists/trusty/main/Contents-amd64.bz2
</span></span><span class="line"><span class="cl">dists/trusty/main/Contents-amd64
</span></span><span class="line"><span class="cl">dists/trusty/main/Contents-amd64.gz
</span></span><span class="line"><span class="cl">dists/trusty/Contents-amd64.bz2
</span></span><span class="line"><span class="cl">dists/trusty/Contents-amd64
</span></span><span class="line"><span class="cl">dists/trusty/Contents-amd64.gz
</span></span><span class="line"><span class="cl">pool/
</span></span><span class="line"><span class="cl">pool/main
</span></span></code></pre></div><p>If you really wanted to, you could create that structure yourself. For the fun of it, let&rsquo;s do that with our repository at <code>/tmp/repo</code>. Remember, this is what it looks like:</p>
<p><em>Previously created simple structure</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ find /tmp/repo/ <span class="p">|</span> sort
</span></span><span class="line"><span class="cl">/tmp/repo/
</span></span><span class="line"><span class="cl">/tmp/repo/netutils_1.0.0_amd64.deb
</span></span><span class="line"><span class="cl">/tmp/repo/netutils_1.1.0_amd64.deb
</span></span><span class="line"><span class="cl">/tmp/repo/netutils_1.2.0_amd64.deb
</span></span><span class="line"><span class="cl">/tmp/repo/Packages.gz
</span></span></code></pre></div><p>Now to &ldquo;transform&rdquo; it:</p>
<p><em>Creating the typical repository structure (manually)</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Delete the old Packages.gz file</span>
</span></span><span class="line"><span class="cl">$ rm Packages.gz
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create new structure</span>
</span></span><span class="line"><span class="cl">$ mkdir -p pool/main/n dists/trusty/main/binary-<span class="o">{</span>amd64,i386<span class="o">}</span>
</span></span><span class="line"><span class="cl">$ mv *.deb pool/main/n
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create new Packages.gz files</span>
</span></span><span class="line"><span class="cl">$ dpkg-scanpackages -m pool <span class="p">|</span> gzip &gt; dists/trusty/main/binary-amd64/Packages.gz
</span></span><span class="line"><span class="cl">dpkg-scanpackages: info: Wrote <span class="m">3</span> entries to output Packages file.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ dpkg-scanpackages -m pool <span class="p">|</span> gzip &gt; dists/trusty/main/binary-i386/Packages.gz
</span></span><span class="line"><span class="cl">dpkg-scanpackages: info: Wrote <span class="m">3</span> entries to output Packages file.
</span></span></code></pre></div><p>After that, the directory structure of our almost-proper repository looks like this:</p>
<p><em>Almost-proper repository for netutils</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># find /tmp/repo/ | sort</span>
</span></span><span class="line"><span class="cl">/tmp/repo/
</span></span><span class="line"><span class="cl">/tmp/repo/dists
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty/main
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty/main/binary-amd64
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty/main/binary-amd64/Packages.gz
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty/main/binary-i386
</span></span><span class="line"><span class="cl">/tmp/repo/dists/trusty/main/binary-i386/Packages.gz
</span></span><span class="line"><span class="cl">/tmp/repo/pool
</span></span><span class="line"><span class="cl">/tmp/repo/pool/main
</span></span><span class="line"><span class="cl">/tmp/repo/pool/main/n
</span></span><span class="line"><span class="cl">/tmp/repo/pool/main/n/netutils_1.0.0_amd64.deb
</span></span><span class="line"><span class="cl">/tmp/repo/pool/main/n/netutils_1.1.0_amd64.deb
</span></span><span class="line"><span class="cl">/tmp/repo/pool/main/n/netutils_1.2.0_amd64.deb
</span></span></code></pre></div><p>Then, we obviously need to update our sources <code>.list</code> file at <code>/etc/apt/sources.list.d/local.list</code>:</p>
<p><em>New sources file with distribution and component at &lsquo;/etc/apt/sources.list.d/local.list&rsquo;</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">deb file:/tmp/repo trusty main
</span></span></code></pre></div>]]></content:encoded></item><item><title>Snippet 0x0C: Load multiple composer.json files at runtime</title><link>https://heckel.io/blog/load-multiple-composer-json-files-at-runtime/</link><pubDate>Sat, 22 Aug 2015 10:18:13 -0400</pubDate><guid>https://heckel.io/blog/load-multiple-composer-json-files-at-runtime/</guid><description>Remember the times when we copied PHP &amp;ldquo;libraries&amp;rdquo; into our project folder, or we copy and pasted code from some random site into our project? Those times are over. Composer and Packagist are the modern way to manage PHP dependencies. They are great. Almost as good as The Maven repos and …</description><content:encoded><![CDATA[<p>Remember the times when we copied PHP &ldquo;libraries&rdquo; into our project folder, or we copy and pasted code from some random site into our project? Those times are over. <a href="https://getcomposer.org/">Composer</a> and <a href="https://packagist.org/">Packagist</a> are the modern way to manage PHP dependencies. They are great. Almost as good as The Maven repos and their build tools in the Java world. However, while Composer is really good at managing the dependencies of a single project, i.e. <strong>one composer.json file</strong>, it does not play well if you want to plug different projects together at runtime. And by &ldquo;does not play well&rdquo; I mean it simply doesn&rsquo;t work if you have <strong>two or more composer.json files</strong>. This quick post demonstrates a way around this limitation. Quick and dirty. Just like the foundations of PHP :-)</p>
<h2 id="1-scenario-multiple-optional-components">1. Scenario: Multiple optional components</h2>
<p>Imagine you have multiple components of a project, and each of them has their own composer.json file. Maybe something like this:</p>
<p><code>project-core/   composer.json   vendor/   project-ui/   composer.json   vendor/   project-addon-1/   composer.json   vendor/   project-addon-2/   composer.json   vendor/   ...</code></p>
<p>Each of the components has its own dependencies. Normally you would just put them on your own Packagist server and then use Composer&rsquo;s <code>require</code> statement to let them depend on each other. But what if some <strong>dependencies are optional?</strong> Like the add-ons above. Or you want to be able to have different user interfaces and you don&rsquo;t want to distribute the entire project as a whole? What if your components are Debian packages?</p>
<p><code>project-core.deb   project-ui.deb   project-addon-1.deb   project-addon-2.deb   ...</code></p>
<p>You won&rsquo;t have any luck with Composer. Because Composer only loads and initializes dependencies at <strong>build time</strong>. It has no ability to discover components or dependencies <strong>at runtime</strong> and then load them, unfortunately. So even if we do a <code>composer install</code> on all the subprojects/components, there is no easy way to plugin them together dynamically.</p>
<p>To achieve this, we have to be a little creative &hellip;</p>
<h2 id="2-solution-1-merge-at-build-time">2. &ldquo;Solution 1&rdquo;: Merge at build time</h2>
<p>Wait? Didn&rsquo;t I say runtime? Yes, I said runtime. However, if you know at <strong>build time</strong> what components will be shipped, or you can pull them down from Git (as submodule or subtree), you can use a very neat little plugin called the <a href="https://github.com/wikimedia/composer-merge-plugin">composer-merge-plugin</a>. This plugin can extend a composer.json file by including others. However, be aware that it creates only one vendor folder. It does not use multiple composer.json files and vendor directories at runtime!</p>
<h2 id="3-solution-2-merge-at-runtime">3. Solution 2: Merge at runtime</h2>
<p>If the option above is not really an option for you, you can write your own little autoloader instead, or reuse the composer autoloaders of the projects that you want to include. Be aware that while this works if you have no conflicting libraries, things might blow up if you have:</p>
<p><em>Loading classes from namespace of other project</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sd">/** @var Composer\Autoload\ClassLoader $loader */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$loader</span> <span class="o">=</span> <span class="k">require_once</span> <span class="no">__DIR__</span> <span class="o">.</span> <span class="s1">&#39;/../vendor/autoload.php&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$map</span> <span class="o">=</span> <span class="k">require_once</span> <span class="no">__DIR__</span> <span class="o">.</span> <span class="s1">&#39;/../../core/vendor/composer/autoload_psr4.php&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$map</span> <span class="k">as</span> <span class="nv">$namespace</span> <span class="o">=&gt;</span> <span class="nv">$path</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$loader</span><span class="o">-&gt;</span><span class="na">addPsr4</span><span class="p">(</span><span class="nv">$namespace</span><span class="p">,</span> <span class="nv">$path</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The snippet above loads the autoloader of the current project (<code>../vendor/autoload.php</code>) and adds the PSR-4 namespaced classes of the &lsquo;core&rsquo; project to it (<code>../../core/vendor/composer/autoload_psr4.php</code>). The <code>autoload_psr4.php</code> file returns an associative array of namespace-to-folder mappings, which then will be used by the composer autoloader to discover the class file.</p>
<p>I realize that this is not the most elegant solution, but it worked for me. Let me know if any of you have a better solution.</p>
<h2 id="a-about-this-post">A. About this post</h2>
<p>I&rsquo;m trying a new section for my blog. I call it <a href="/blog/categories/code-snippets/">Code Snippets</a>. It&rsquo;ll be very short, code-focused posts of things I recently discovered or find fascinating or helpful. I hope this helps.</p>]]></content:encoded></item></channel></rss>