<?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>Gpg on ln --help</title>
    <link>https://blog.mei-home.net/tags/gpg/</link>
    <description>Recent content in Gpg on ln --help</description>
    <generator>Hugo -- 0.152.2</generator>
    <language>en</language>
    <lastBuildDate>Mon, 07 Apr 2025 23:50:43 +0200</lastBuildDate>
    <atom:link href="https://blog.mei-home.net/tags/gpg/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Securing K8s Credentials</title>
      <link>https://blog.mei-home.net/posts/securing-k8s-credentials/</link>
      <pubDate>Mon, 07 Apr 2025 23:50:43 +0200</pubDate>
      <guid>https://blog.mei-home.net/posts/securing-k8s-credentials/</guid>
      <description>Using pass and gpg-agent to secure kubectl access credentials</description>
      <content:encoded><![CDATA[<p>Wherein I will explain how to use pass and GnuPG to secure k8s credentials.</p>
<p>Since I migrated my <a href="https://www.vaultproject.io/">HashiCorp Vault</a> instance
into my Kubernetes cluster, I started to feel a bit uncomfortable with the
Kubernetes access credentials just sitting in the <code>~/.kube/config</code> file in
plain text. Anyone who somehow gets access to my Command &amp; Control host would
be able to access them and do whatever they like with the Kubernetes cluster,
including the Vault deployment containing a lot of my secrets.</p>
<p>So I asked around on the Fediverse, and <a href="https://microblog.shivering-isles.com/@sheogorath">Sheogorath@shivering-isles.com</a>
came back with two interesting blog posts. <a href="https://shivering-isles.com/2024/11/kubernetes-oidc-keycloak">The first one</a>,
using OIDC, was interesting, but it would require some additional infrastructure
that would need to be up whenever I wanted to do something in Kubernetes. Which
would have also meant that I couldn&rsquo;t run that infrastructure in Kubernetes
itself.</p>
<p>But the <a href="https://shivering-isles.com/2022/03/store-kubernetes-credentials-pass">second post</a>
was very interesting, showing how to use <a href="https://www.passwordstore.org/">pass</a>
to store the k8s credentials.</p>
<p>I&rsquo;m already using pass as my password manager on my desktop and phone, so this
sounded like an excellent idea.</p>
<p>In short, pass is a pretty simple bash script which uses <a href="https://gnupg.org/">GnuPG</a>
do encrypt and decrypt files containing passwords, or really any data at all,
sitting in my home directory. The initial setup is a little bit
more involved due to needing GnuPG keys, but afterwards it&rsquo;s pretty easy to
use. Its main interface is a command line script with the possibility of
entering new passwords and showing existing ones, as well as moving passwords
around.
But there&rsquo;s also an Android app and a Firefox browser extension which both
work very nicely.</p>
<p>There was only one problem: I didn&rsquo;t want to set up a whole different set of
GnuPG keys to use on my Command &amp; Control host. After some searching, I figured
out that <a href="https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html">gpg-agent</a>
has some forwarding options, similar to ssh-agent. And I already had gpg-agent
running on my desktop.</p>
<p>Using a remote gpg-agent for access to the secret key also has an additional
advantage: Even if an attacker can get into my Command &amp; Control server, the
key necessary to decrypt the Kubernetes credentials is not physically present
on the machine. One more hurdle for an attacker to overcome.</p>
<h2 id="setting-up-gnupg-on-the-command--control-machine">Setting up GnuPG on the Command &amp; Control machine</h2>
<p>The first thing to do is to set up the public key of the secret key that will
later be used by pass to encrypt the Kubernetes credentials.
Note that only the public key is needed here - the private key stays on the
original machine, in my case my desktop computer.</p>
<p>First, list the keys on the original host:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gpg --list-public-keys
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>pub   rsa4096 2022-06-23 <span style="color:#f92672">[</span>SC<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>      3BBC8F8D9E7CB515338C6F0B34BBBD3D676F000F
</span></span><span style="display:flex;"><span>uid        <span style="color:#f92672">[</span> ultimativ <span style="color:#f92672">]</span> Foo Bar &lt;mail@example.com&gt;
</span></span><span style="display:flex;"><span>uid        <span style="color:#f92672">[</span> ultimativ <span style="color:#f92672">]</span> Baz Bar <span style="color:#f92672">(</span>Private<span style="color:#f92672">)</span> &lt;mail2@example.com&gt;
</span></span><span style="display:flex;"><span>sub   rsa4096 2022-06-23 <span style="color:#f92672">[</span>E<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>...<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>In this output, the important part is the keyhash in the line after the <code>pub</code>
line: <code>3BBC8F8D9E7CB515338C6F0B34BBBD3D676F000F</code>.
That&rsquo;s the identifier for the key.</p>
<p>Next, I needed to transfer the public key over to my Command &amp; Control host:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gpg --export 3BBC8F8D9E7CB515338C6F0B34BBBD3D676F000F | ssh myuser@candchost gpg --import
</span></span></code></pre></div><p>With that done, I could go ahead and set up the GnuPG agent forwarding. I followed
<a href="https://wiki.gnupg.org/AgentForwarding">this documentation</a> and did not have
any issues.</p>
<p>In short, I added these lines to the SSHD server configuration on the <code>candchost</code>:</p>
<pre tabindex="0"><code>Match User myuser
  StreamLocalBindUnlink yes
</code></pre><p>In addition, I also had to add these lines to my own SSH config for my user on
my desktop from where I&rsquo;m accessing the Command &amp; Control host, at <code>~/.ssh/config</code>:</p>
<pre tabindex="0"><code>Host candchost
  RemoteForward  /run/user/1000/gnupg/S.gpg-agent /run/user/1000/gnupg/S.gpg-agent.extra
</code></pre><p>As the documentation notes, the following commands can be used. For the
second path in the <code>RemoteForward</code> option, which is the local (on my desktop)
gpg-agent &ldquo;extra&rdquo; socket:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gpgconf --list-dir agent-extra-socket
</span></span></code></pre></div><p>And then to get the socket on the <code>candchost</code>, for the first argument of <code>RemoteForward</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gpgconf --list-dir agent-socket
</span></span></code></pre></div><p>This is just the path of the standard GnuPG socket on that host.</p>
<p>And that&rsquo;s all there was to it. When I reconnected to the <code>candchost</code> via SSH, I
was able to use gpg-agent and got access to my remote agent on my desktop.</p>
<p>One last thing to do was to trust the public key transferred to the <code>candchost</code>.
This is only possible after the forwarding has been configured, because I didn&rsquo;t
have, and don&rsquo;t need, a private key to do any trusting with on the <code>candchost</code>.</p>
<p>Trusting a key works like this:</p>
<pre tabindex="0"><code>gpg --edit-key 3BBC8F8D9E7CB515338C6F0B34BBBD3D676F000F
Secret key is available.

[...]

gpg&gt; trust
[...]

Please decide how far you trust this user to correctly verify other users&#39; keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don&#39;t know or won&#39;t say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

[...]
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg&gt; q
</code></pre><p>This procedure uses the private key from the gpg-agent, meaning the key from
my desktop system, which was a nice confirmation that the forwarding setup
worked.</p>
<h2 id="setup-pass">Setup pass</h2>
<p>The next step is to setup pass. First, install it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>apt install --no-install-recommends --no-install-suggests pass
</span></span></code></pre></div><p>The <code>--no-install-suggests</code> and <code>--no-install-recommends</code> flags are very much
required, otherwise you&rsquo;re going to get pieces of X11 installed on an Ubuntu
system.</p>
<p>To initialize pass, the <code>init</code> command is used, with the public key&rsquo;s keyhash
used as input:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>pass init 3BBC8F8D9E7CB515338C6F0B34BBBD3D676F000F
</span></span></code></pre></div><p>This creates the password store in the default location at <code>~/.password-store</code>.</p>
<h2 id="setup-kubernetes">Setup Kubernetes</h2>
<p>Following Sheogorath&rsquo;s <a href="https://shivering-isles.com/2022/03/store-kubernetes-credentials-pass">blog post</a>,
I first extracted the keys from the Kube config file with these commands:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>kubectl config view --minify --raw --output <span style="color:#e6db74">&#39;jsonpath={..user.client-certificate-data}&#39;</span> | base64 -d | sed -e <span style="color:#e6db74">&#39;s/$/\\n/g&#39;</span> | tr -d <span style="color:#e6db74">&#39;\n&#39;</span> &gt; client-cert
</span></span><span style="display:flex;"><span>kubectl config view --minify --raw --output <span style="color:#e6db74">&#39;jsonpath={..user.client-key-data}&#39;</span> | base64 -d | sed -e <span style="color:#e6db74">&#39;s/$/\\n/g&#39;</span> | tr -d <span style="color:#e6db74">&#39;\n&#39;</span> &gt; client-key
</span></span></code></pre></div><p>Then I added the values to an <a href="https://kubernetes.io/docs/reference/config-api/client-authentication.v1beta1/#client-authentication-k8s-io-v1beta1-ExecCredential">ExecCredential</a>
I stored in pass by running this command first:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>pass edit k8s/credentials
</span></span></code></pre></div><p>This will open the editor in the <code>EDITOR</code> environment variable. Then I pasted
this into it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;apiVersion&#34;</span>: <span style="color:#e6db74">&#34;client.authentication.k8s.io/v1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;kind&#34;</span>: <span style="color:#e6db74">&#34;ExecCredential&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;status&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;clientCertificateData&#34;</span>: <span style="color:#e6db74">&#34;-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;clientKeyData&#34;</span>: <span style="color:#e6db74">&#34;-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I replaced the <code>clientCertificateData</code> with the content of the <code>client-cert</code>
file extracted with the previous command and the <code>clientKeyData</code> with the
content of the <code>client-key</code> file. Finally, the entire file content should be
squashed into a single line of text, and then the editor can be closed.</p>
<p>If everything worked as expected, pass has now stored that file content at
<code>~/.password-store/k8s/credentials</code>, encrypted with the public key given in the
<code>pass init</code> command. Try it out by running this command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>pass show k8s/credentials
</span></span></code></pre></div><p>If you haven&rsquo;t run any commands which require decryption up to now, a popup should
appear from your pinentry program asking you to unlock your GnuPG private key.
This will even appear when you&rsquo;ve previously unlocked that same private key
for local use on your desktop machine, as GnuPG treats the local and remote
machine as two different instances, for security reasons.</p>
<p>The final step is to adapt the <code>~/.kube/config</code> file to use the credentials from
pass. For that, I opened the file and edited it to look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">clusters</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">cluster</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">certificate-authority-data</span>: <span style="color:#ae81ff">&lt;Cluster CA CERT&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server</span>: <span style="color:#ae81ff">https://k8s.example.com:6443</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-kube-cluster</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">contexts</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">context</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cluster</span>: <span style="color:#ae81ff">my-kube-cluster</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">user</span>: <span style="color:#ae81ff">my-kube-user</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-kube-user@my-kube-cluster</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">current-context</span>: <span style="color:#ae81ff">my-kube-user@my-kube-cluster</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Config</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">preferences</span>: {}
</span></span><span style="display:flex;"><span><span style="color:#f92672">users</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-kube-user</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">user</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">exec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">client.authentication.k8s.io/v1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">command</span>: <span style="color:#ae81ff">pass</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#ae81ff">show</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ae81ff">k8s/credentials</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">interactiveMode</span>: <span style="color:#ae81ff">IfAvailable</span>
</span></span></code></pre></div><p>The only change necessary is in the <code>users</code> array, where the <code>user:</code> entry for
your user should be changed to contain the <code>exec</code> section shown, instead of the
<code>client-certificate-data</code> and <code>client-key-data</code> entries.</p>
<p>And with that, kubectl will execute the command <code>pass show k8s/credentials</code>
to access the credentials. And this doesn&rsquo;t just work for kubectl, but I&rsquo;ve also
tested it with the <a href="https://docs.ansible.com/ansible/latest/collections/kubernetes/core/docsite/kubernetes_scenarios/k8s_intro.html">Ansible k8s modules</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
