Improve about page
Remove cache and compiled site
This commit is contained in:
parent
77d432ad8b
commit
03cf79e8f0
82 changed files with 321 additions and 1405 deletions
|
@ -0,0 +1,170 @@
|
|||
I"µ:<p>Ever SSH’ed into a freshly installed server and gotten the following annoying message?</p>
|
||||
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The authenticity of host 'host.tld (1.2.3.4)' can't be established.
|
||||
ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
|
||||
Are you sure you want to continue connecting (yes/no)?
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>Or even more annoying:</p>
|
||||
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
|
||||
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
|
||||
It is also possible that a host key has just been changed.
|
||||
The fingerprint for the ED25519 key sent by the remote host is
|
||||
SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
|
||||
Please contact your system administrator.
|
||||
Add correct host key in /home/user/.ssh/known_hosts to get rid of this message.
|
||||
Offending ED25519 key in /home/user/.ssh/known_hosts:3
|
||||
remove with:
|
||||
ssh-keygen -f "/etc/ssh/ssh_known_hosts" -R "1.2.3.4"
|
||||
ED25519 host key for 1.2.3.4 has changed and you have requested strict checking.
|
||||
Host key verification failed.
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>Could it be that the programmers at OpenSSH simply like to annoy us with these confusing messages?
|
||||
Maybe, but these warnings also serve as a way to notify users of a potential Man-in-the-Middle (MITM) attack.
|
||||
I won’t go into the details of this problem, but I refer you to <a href="https://blog.g3rt.nl/ssh-host-key-validation-strict-yet-user-friendly.html">this excellent blog post</a>.
|
||||
Instead, I would like to talk about ways to solve these annoying warnings.</p>
|
||||
|
||||
<p>One obvious solution is simply to add each host to your <code class="language-plaintext highlighter-rouge">known_hosts</code> file.
|
||||
This works okay when managing a handful of servers, but becomes unbearable when managing many servers.
|
||||
In my case, I wanted to quickly spin up virtual machines using Duncan Mac-Vicar’s <a href="https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs">Terraform Libvirt provider</a>, without having to accept their host key before connecting.
|
||||
The solution? Issuing SSH host certificates using an SSH certificate authority.</p>
|
||||
|
||||
<h2 id="ssh-certificate-authorities-vs-the-web">SSH Certificate Authorities vs. the Web</h2>
|
||||
|
||||
<p>The idea of an SSH certificate authority (CA) is quite easy to grasp, if you understand the web’s Public Key Infrastructure (PKI).
|
||||
Just like with the web, a trusted party can issue certificates that are offered when establishing a connection.
|
||||
The idea is, just by trusting the trusted party, you trust every certificate they issue.
|
||||
In the case of the web’s PKI, this trusted party is bundled and trusted by <a href="https://wiki.mozilla.org/CA">your browser</a> or operating system.
|
||||
However, in the case of SSH, the trusted party is you! (Okay you can also trust your own web certificate authority)
|
||||
With this great power, comes great responsibility which we will abuse heavily in this article.</p>
|
||||
|
||||
<h2 id="ssh-certificate-authority-for-terraform">SSH Certificate Authority for Terraform</h2>
|
||||
|
||||
<p>So, let’s start with a plan.
|
||||
I want to spawn virtual machines with Terraform which which are automatically provisioned with a SSH host certificate issued by my CA.
|
||||
This CA will be another host on my private network, issuing certificates over SSH.</p>
|
||||
|
||||
<h3 id="fetching-the-ssh-host-certificate">Fetching the SSH Host Certificate</h3>
|
||||
|
||||
<p>First we generate an SSH key pair in Terraform.
|
||||
Below is the code for that:</p>
|
||||
<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"tls_private_key"</span> <span class="s2">"debian"</span> <span class="p">{</span>
|
||||
<span class="nx">algorithm</span> <span class="p">=</span> <span class="s2">"ED25519"</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">data</span> <span class="s2">"tls_public_key"</span> <span class="s2">"debian"</span> <span class="p">{</span>
|
||||
<span class="nx">private_key_pem</span> <span class="p">=</span> <span class="nx">tls_private_key</span><span class="p">.</span><span class="nx">debian</span><span class="p">.</span><span class="nx">private_key_pem</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>Now that we have an SSH key pair, we need to somehow make Terraform communicate this with the CA.
|
||||
Lucky for us, there is a way for Terraform to execute an arbitrary command with the <code class="language-plaintext highlighter-rouge">external</code> data feature.
|
||||
We call this script below:</p>
|
||||
<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">data</span> <span class="s2">"external"</span> <span class="s2">"cert"</span> <span class="p">{</span>
|
||||
<span class="nx">program</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"bash"</span><span class="p">,</span> <span class="s2">"</span><span class="k">${</span><span class="nx">path</span><span class="p">.</span><span class="k">module}</span><span class="s2">/get_cert.sh"</span><span class="p">]</span>
|
||||
|
||||
<span class="nx">query</span> <span class="p">=</span> <span class="p">{</span>
|
||||
<span class="nx">pubkey</span> <span class="p">=</span> <span class="nx">trimspace</span><span class="p">(</span><span class="k">data</span><span class="p">.</span><span class="nx">tls_public_key</span><span class="p">.</span><span class="nx">debian</span><span class="p">.</span><span class="nx">public_key_openssh</span><span class="p">)</span>
|
||||
<span class="nx">host</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">name</span>
|
||||
<span class="nx">cahost</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">ca_host</span>
|
||||
<span class="nx">cascript</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">ca_script</span>
|
||||
<span class="nx">cakey</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">ca_key</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>These query parameters will end up in the script’s stdin in JSON format.
|
||||
We can then read these parameters, and send them to the CA over SSH.
|
||||
The result must as well be in JSON format.</p>
|
||||
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
|
||||
<span class="nb">set</span> <span class="nt">-euo</span> pipefail
|
||||
<span class="nv">IFS</span><span class="o">=</span><span class="s1">$'</span><span class="se">\n\t</span><span class="s1">'</span>
|
||||
|
||||
<span class="c"># Read the query parameters</span>
|
||||
<span class="nb">eval</span> <span class="s2">"</span><span class="si">$(</span>jq <span class="nt">-r</span> <span class="s1">'@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"'</span><span class="si">)</span><span class="s2">"</span>
|
||||
|
||||
<span class="c"># Fetch certificate from the CA</span>
|
||||
<span class="c"># Warning: extremely ugly code that I am to lazy to fix</span>
|
||||
<span class="nv">CERT</span><span class="o">=</span><span class="si">$(</span>ssh <span class="nt">-o</span> <span class="nv">ConnectTimeout</span><span class="o">=</span>3 <span class="nt">-o</span> <span class="nv">ConnectionAttempts</span><span class="o">=</span>1 root@<span class="nv">$CAHOST</span> <span class="s1">'"'</span><span class="s2">"</span><span class="nv">$CASCRIPT</span><span class="s2">"</span><span class="s1">'" host "'</span><span class="s2">"</span><span class="nv">$CAKEY</span><span class="s2">"</span><span class="s1">'" "'</span><span class="s2">"</span><span class="nv">$PUBKEY</span><span class="s2">"</span><span class="s1">'" "'</span><span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span><span class="s1">'".dmz'</span><span class="si">)</span>
|
||||
|
||||
jq <span class="nt">-n</span> <span class="nt">--arg</span> cert <span class="s2">"</span><span class="nv">$CERT</span><span class="s2">"</span> <span class="s1">'{"cert":$cert}'</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>We see that a script is called on the remote host that issues the certificate.
|
||||
This is just a simple wrapper around <code class="language-plaintext highlighter-rouge">ssh-keygen</code>, which you can see below.</p>
|
||||
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
|
||||
<span class="nb">set</span> <span class="nt">-euo</span> pipefail
|
||||
<span class="nv">IFS</span><span class="o">=</span><span class="s1">$'</span><span class="se">\n\t</span><span class="s1">'</span>
|
||||
|
||||
host<span class="o">()</span> <span class="o">{</span>
|
||||
<span class="nv">CAKEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
|
||||
<span class="nv">PUBKEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
|
||||
<span class="nv">HOST</span><span class="o">=</span><span class="s2">"</span><span class="nv">$4</span><span class="s2">"</span>
|
||||
|
||||
<span class="nb">echo</span> <span class="s2">"</span><span class="nv">$PUBKEY</span><span class="s2">"</span> <span class="o">></span> /root/ca/<span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span>.pub
|
||||
ssh-keygen <span class="nt">-h</span> <span class="nt">-s</span> /root/ca/keys/<span class="s2">"</span><span class="nv">$CAKEY</span><span class="s2">"</span> <span class="nt">-I</span> <span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span> /root/ca/<span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span>.pub
|
||||
<span class="nb">cat</span> /root/ca/<span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span><span class="nt">-cert</span>.pub
|
||||
<span class="nb">rm</span> /root/ca/<span class="s2">"</span><span class="nv">$HOST</span><span class="s2">"</span><span class="k">*</span>.pub
|
||||
<span class="o">}</span>
|
||||
|
||||
<span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<h3 id="appeasing-the-terraform-gods">Appeasing the Terraform Gods</h3>
|
||||
|
||||
<p>So nice, we can fetch the SSH host certificate from the CA.
|
||||
We should just be able to use it right?
|
||||
We can, but it brings a big annoyance with it: Terraform will fetch a new certificate every time it is run.
|
||||
This is because the <code class="language-plaintext highlighter-rouge">external</code> feature of Terraform is a data source.
|
||||
If we were to use this data source for a Terraform resource, it would need to be updated every time we run Terraform.
|
||||
I have not been able to find a way to avoid fetching the certificate every time, except for writing my own resource provider which I’d rather not.
|
||||
I have, however, found a way to hack around the issue.</p>
|
||||
|
||||
<p>The idea is as follows: we can use Terraform’s <code class="language-plaintext highlighter-rouge">ignore_changes</code> to, well, ignore any changes of a resource.
|
||||
Unfortunately, we cannot use this for a <code class="language-plaintext highlighter-rouge">data</code> source, so we must create a glue <code class="language-plaintext highlighter-rouge">null_resource</code> that supports <code class="language-plaintext highlighter-rouge">ignore_changes</code>.
|
||||
This is shown in the code snipppet below.
|
||||
We use the <code class="language-plaintext highlighter-rouge">triggers</code> property simply to copy the certificate in; we don’t use it for it’s original purpose.</p>
|
||||
|
||||
<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"null_resource"</span> <span class="s2">"cert"</span> <span class="p">{</span>
|
||||
<span class="nx">triggers</span> <span class="p">=</span> <span class="p">{</span>
|
||||
<span class="nx">cert</span> <span class="p">=</span> <span class="k">data</span><span class="p">.</span><span class="nx">external</span><span class="p">.</span><span class="nx">cert</span><span class="p">.</span><span class="nx">result</span><span class="p">[</span><span class="s2">"cert"</span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="nx">lifecycle</span> <span class="p">{</span>
|
||||
<span class="nx">ignore_changes</span> <span class="p">=</span> <span class="p">[</span>
|
||||
<span class="nx">triggers</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>And voilà , we can now use <code class="language-plaintext highlighter-rouge">null_resource.cert.triggers["cert"]</code> as our certificate, that won’t trigger replacements in Terraform.</p>
|
||||
|
||||
<h3 id="setting-the-host-certificate-with-cloud-init">Setting the Host Certificate with Cloud-Init</h3>
|
||||
|
||||
<p>Terraform’s Libvirt provider has native support for Cloud-Init, which is very handy.
|
||||
We can give the host certificate directly to Cloud-Init and place it on the virtual machine.
|
||||
Inside the Cloud-Init configuration, we can set the <code class="language-plaintext highlighter-rouge">ssh_keys</code> property to do this:</p>
|
||||
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">ssh_keys</span><span class="pi">:</span>
|
||||
<span class="na">ed25519_private</span><span class="pi">:</span> <span class="pi">|</span>
|
||||
<span class="s">${indent(4, private_key)}</span>
|
||||
<span class="na">ed25519_certificate</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${host_cert}"</span>
|
||||
</code></pre></div></div>
|
||||
|
||||
<p>I hardcoded this to ED25519 keys, because this is all I use.</p>
|
||||
|
||||
<p>This works perfectly, and I never have to accept host certificates from virtual machines again.</p>
|
||||
|
||||
<h3 id="caveats">Caveats</h3>
|
||||
|
||||
<p>A sharp eye might have noticed the lifecycle of these host certificates is severely lacking.
|
||||
Namely, the deployed host certificates have no expiration date nore is there revocation function.
|
||||
There are ways to implement these, but for my home lab I did not deem this necessary at this point.
|
||||
In a more professional environment, I would suggest using <a href="https://www.vaultproject.io/">Hashicorp’s Vault</a>.</p>
|
||||
|
||||
<p>This project did teach me about the limits and flexibility of Terraform, so all in all a success!
|
||||
All code can be found on the git repository <a href="https://git.kun.is/home/tf-modules/src/branch/master/debian">here</a>.</p>
|
||||
:ET
|
Loading…
Add table
Add a link
Reference in a new issue