<p>I would like to deploy an ssh bastion jumper as a deployment in a Kubernetes cluster. This should receive its sshd_config as well as the authorized_keys via a ConfigMap or Secret. These can of course change over time, so that a reload of the sshd service becomes necessary.</p>
<p>How can I automate this process? Existing ssh connections should not be killed when updating the config or authorized_keys file.</p>
<p>My dockerfile is:</p>
<pre><code>FROM docker.io/alpine:latest
RUN apk add --no-cache openssh-server

EXPOSE 22/tcp
CMD [&quot;/usr/sbin/sshd&quot;, &quot;-D&quot;, &quot;-e&quot;]
</code></pre>
<p>My deployment looks like:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: sshd-server
  namespace: sshd
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: sshd-server
    spec:
      containers:
      - name: my-sshd-server
        image: my-sshd-server-image:latest
        imagePullPolicy: Always
        ports:
          - containerPort: 22
        volumeMounts:
        - mountPath: /etc/ssh/sshd_config
          name: sshd_config
        - mountPath: /user/.ssh/authorized_keys
          name: authorized_keys
...
</code></pre>

user5580578
<p>If you mount a <code>ConfigMap</code> as a directory, the directory contents will update when you update the <code>ConfigMap</code> (possibly after a short delay).</p>
<p>That means if you were just concerned about your <code>authorized_keys</code> file, you could do something like this:</p>
<p>Create the following <code>ConfigMap</code>:</p>
<pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: ssh-config
data:
  authorized_keys: |
    ssh-rsa ...
    ssh-rsa ...
  sshd_config: |
    StrictModes no
    AuthorizedKeysFile  /config/authorized_keys
</code></pre>
<p>And deploy your ssh pod using something like:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: sshtest
spec:
  replicas: 1
  template:
    spec:
      containers:
        - image: quay.io/larsks/alpine-sshd:5
          imagePullPolicy: Always
          name: sshtest
          ports:
            - containerPort: 22
              name: ssh
          volumeMounts:
            - name: ssh-config
              mountPath: /config
            - name: ssh-config
              mountPath: /etc/ssh/sshd_config
              subPath: sshd_config
            - name: ssh-data
              mountPath: /etc/ssh

      volumes:
        - name: ssh-config
          configMap:
            name: ssh-config
            defaultMode: 0440
        - name: ssh-data
          emptyDir: {}
</code></pre>
<p>Where <code>quay.io/larsks/alpine-sshd:5</code> is simply alpine + sshd + an
<code>ENTRYPOINT</code> that runs <code>ssh-keygen -A</code>. You should build your own
rather than random some random person's image :).</p>
<p>This will work on straight Kubernetes but will <em>not</em> run on OpenShift
without additional work.</p>
<p>With this configuration (and an appropriate <code>Service</code>) you can ssh
into the container <em>as root</em> using the private key that corresponds to
one of the public keys contained in the <code>authorized_keys</code> part of the
<code>ssh-config</code> <code>ConfigMap</code>.</p>
<p>When you update the <code>ConfigMap</code>, the container will eventually see the
updated values, no restarts required.</p>
<hr />
<p>If you really want to respond to changes in <code>sshd_config</code>, that
becomes a little more complicated. <code>sshd</code> itself doesn't have any
built-in facility for responding to changes in the configuration file,
so you'll need to add a sidecar container that watches for config file
updates and then sends the appropriate signal (<code>SIGHUP</code>) to <code>sshd</code>.
Something like this:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: sshtest
spec:
  replicas: 1
  template:
    spec:
      shareProcessNamespace: true
      containers:
        - image: docker.io/alpine:latest
          name: reloader
          volumeMounts:
            - name: ssh-config
              mountPath: /config
            - name: ssh-data
              mountPath: /etc/ssh
          command:
            - /bin/sh
            - -c
            - |
              while true; do
                if [ -f /etc/ssh/sshd_config ] &amp;&amp; [ -f /etc/ssh/sshd.pid ]; then
                  if ! diff -q /config/sshd_config /etc/ssh/sshd_config; then
                    cp /config/sshd_config /etc/ssh/sshd_config
                    kill -HUP $(cat /etc/ssh/sshd.pid)
                  fi
                fi

                sleep 10
              done
        - image: quay.io/larsks/alpine-sshd:6
          imagePullPolicy: Always
          name: sshd
          ports:
            - containerPort: 22
              name: ssh
          volumeMounts:
            - name: ssh-config
              mountPath: /config
            - name: ssh-data
              mountPath: /etc/ssh
      volumes:
        - name: ssh-config
          configMap:
            name: ssh-config
            defaultMode: 0600
        - name: ssh-data
          emptyDir: {}
</code></pre>
<p>This requires a slightly modified container image that includes the
following <code>ENTRYPOINT</code> script:</p>
<pre><code>#!/bin/sh

if [ -f /config/sshd_config ]; then
    cp /config/sshd_config /etc/ssh/sshd_config
fi

ssh-keygen -A
exec &quot;$@&quot;
</code></pre>
<p>With this configuration, the <code>reloader</code> container watches for changes in the configuration file supplied by the <code>ConfigMap</code>. When it detects a change, it copies the updated file to the correct location and then sends a <code>SIGHUP</code> to <code>sshd</code>, which reloads its configuration.</p>
<p>This does not interrupt existing ssh connections.</p>
