Portfolio |

Container Secrets – warum und wie?

Sensible Informationen sind Teil eines jeden Computersystems. Container bilden hier keine Ausnahme. Benutzer müssen sensible Informationen innerhalb von Containern nutzen und gleichzeitig sicher aufbewahren.

Secrets können verwendet werden, um alle sensiblen Daten zu verwalten, die ein Container zur Laufzeit benötigt, wie zum Beispiel:

  •  Benutzernamen und Passwörter
  •  TLS-Zertifikate und -Schlüssel
  •  SSH-Schlüssel
  •  Andere wichtige Daten wie der Name einer Datenbank oder eines internen Servers
  •  Allgemeine Zeichenketten oder binäre Inhalte

Bisherige Methoden

Keine Sicherheit bietet Klartext. Jeder kann die Daten direkt einsehen. Daher sollten sich vertrauliche Daten nicht im Application Code befinden, weder in einer Pod-Spezifikation noch in einem Container-Image. Dasselbe gilt für Environment Variables (Umgebungsvariablen). Auch ConfigMaps, die als Umgebungsvariablen, Befehlszeilenargumente oder als Konfigurationsdateien in einem Volume verwendet werden, sind ebenfalls nicht für die Verwaltung von sensiblen Informationen geeignet. Sie bieten keine Geheimhaltung oder Verschlüsselung.

Besondere Vorsicht geboten ist bei Source Code Management. Es ist sehr einfach, den Überblick über die dort abgelegten sensiblen Informationen zu verlieren. Diese können fest in den Quellcode kodiert, als Textdatei gespeichert oder in einem Debug-Anwendungsprotokoll versteckt sein. Dadurch können sie versehentlich in das SCM geladen und somit von Dritten eingesehen werden. Am Ende des Artikels wird jedoch eine Variante vorgestellt, mit der Kubernetes-Secrets sicher in SCM gespeichert werden können. SealedSecrets werden verschlüsselt im SCM abgelegt und können nur von dem Controller entschlüsselt werden, der im Zielcluster läuft.

Eine weitere Variante für das Einbinden von sensiblen Daten bietet das Volume Mounting. Bei dem Einhängen eines Volumes in einen Container kann dieses Verzeichnis vom Container verwendet werden. Dies kann auch für Verzeichnisse mit sensiblen Daten erfolgen.

Praxisbeispiel SSH-Keys

SSH (Secure Shell) ist ein verschlüsseltes Protokoll, das zur Administration und Kommunikation mit Servern verwendet wird. SSH-Schlüsselpaare bestehen aus einem öffentlichen Schlüssel und einem privaten Schlüssel. Der private Schlüssel wird vom Client aufbewahrt und sollte absolut geheim gehalten werden. Eine Kompromittierung des privaten Schlüssels ermöglicht es dem Angreifer, sich ohne zusätzliche Authentifizierung bei Servern anzumelden, die mit dem zugehörigen öffentlichen Schlüssel konfiguriert sind.

Schematische Darstellung einer SSH-Verschlüsselung

Der erste Schritt zur Konfiguration der SSH-Schlüsselauthentifizierung besteht darin, ein SSH-Schlüsselpaar zu erzeugen. Unter Linux kann das Programm ssh-keygen verwenden werden:

$ ssh-keygen -t rsa -b 4096 -f ./id_rsa


Der private Schlüssel wird in ./id_rsa gespeichert, der zugehörige öffentliche Schlüssel in ./id_rsa.pub. Diese beiden Schlüssel werden in den nachfolgenden Code-Beispielen verwendet und in die Container eingebunden.

Docker Secrets

Docker ist eine Containerisierungstechnologie, welche die Erstellung und den Betrieb von Containern ermöglicht.

Durch die Verwendung von Docker Secrets kann ein Image erstellt werden, das lokale SSH-Schlüssel verwendet. Ein Secret mit dem Nahmen ssh_secret für den vorher erstellten privaten SSH-Schlüssel ./id_rsa kann wie folgt angelegt werden:

docker secret create ssh_secret ./id_rsa


Mit inspect kann das angelegte Secret eingesehen werden. Standardmäßig werden alle Informationen in einem JSON-Array ausgegeben. Das gerade angelegte Secret enthält folgende Informationen:

docker secret inspect ssh_secret

[
  {
    "ID": "eo7jnzguqgtpdah3cm5srfb97",
    "Version": {
      "Index": 17
    },
    "CreatedAt": "2021-09-24T08:15:09.735271783Z",
    "UpdatedAt": "2021-09-24T08:15:09.735271783Z",
    "Spec": {
      "Name": " ssh_secret",
      "Labels": {
        "env": "dev",
        "rev": "20210924"
      }
    }
  }
]


Secrets, die einem Container zur Verfügung gestellt werden, werden entschlüsselt in den Arbeitsspeicher geladen. Der Speicherort innerhalb des Containers ist standardmäßig <strong>/run/secrets/<secret_name></strong>.

Ein vereinfachtes Dockerfile kann das Secret wie folgt verwenden:

FROM ubuntu:18.04
RUN apt-get update && apt-get install -y openssh-client
RUN useradd -m user
RUN mkdir -p /home/user/.ssh && ln -s /run/secrets/ssh_secret /home/user/.ssh/id_rsa
RUN chown -R user:user /home/user/.ssh
USER user
CMD ["/bin/bash"]


Mit dem Befehl ln -s /run/secrets/ssh_secret /home/user/.ssh/id_rsa wird ein Softlink auf den privaten SSH-Schlüssel aus dem Secret erzeugt und in der Datei ./ssh/id_rsa abgespeichert.

Der Befehl docker secret kann nur in Verbindung mit Docker Swarm verwendet werden, nicht für eigenständige Container. Um diese Funktion nutzen zu können, muss der Container so angepasst werden, dass er als einzelner Service läuft. Stateful-Container können in der Regel mit einer Skalierung von 1 betrieben werden, ohne dass der Container-Code geändert werden muss.

Der Docker-Schwarmmodus ist in die aktuellen Docker-Engines integriert. Standardmäßig ist der Swarm Modus deaktiviert. Initialisiert wird der Modus mit dem Befehl docker swarm init. Danach wird mit dem Konzept der Services gearbeitet, die über den Befehl docker service gesteuert werden.

Podman Secrets

podman secret ist im Podman release 3.1.0 enthalten. Genauso wie für docker secret können die Befehle <strong>create</strong>, <strong>inspect</strong>, <strong>ls</strong> und rm verwendet werden.

Kürzlich wurde <strong>podman secrets</strong> erweitert, um noch mehr Funktionen zu unterstützen, z. B. User-ID (UID), Group-ID (GID), Mode options und environment variable secrets. Weitere Informationen dazu hier: https://www.redhat.com/sysadmin/sensitive-data-containers.

Docker Compose

Compose ist ein Tool zur Definition und Ausführung von Multi-Container-Docker-Anwendungen. Es wird eine YAML-Datei angelegt, um die Dienste der Anwendung zu konfigurieren. Mit einem einzigen Befehl können alle Dienste aus der Konfiguration erstellt und gestartet werden. Docker Compose Secrests sind damit eine Alternative zu Docker Secrets, da in dem compose-file Secrets ohne Swarm verwendet werden können.

Die Compose-Datei für die Verwendung des vorher erstellten Docker Secrets ssh_secret kann wie folgt aussehen:

version: '3.3'
services:
  test:
    image: ssh-test
    secrets:
      - ssh_secret
secrets:
  ssh_secret:
    file: id_rsa


Der Name des Secrets wird in der Datei angegeben. Dies gewährt dem Container Zugriff auf das Secret und hängt es unter <strong>/run/secrets/<secret_name></strong> innerhalb des Containers ein. Somit hat er Zugriff auf die Datei id_rsa aus dem Secret.

Kubernetes Secrets

Kubernetes, auch bekannt als K8s, ist ein Open Source-System zur Automatisierung der Bereitstellung, Skalierung und Verwaltung von containerisierten Anwendungen.

Kubernetes-Secrets werden standardmäßig unverschlüsselt im zugrunde liegenden Datenspeicher des API-Servers gespeichert. Secrets in Kubernetes sind Base64-kodiert. Diese Kodierung ist keine Verschlüsselungsmethode und wird daher als Klartext betrachtet. Jeder mit API-Zugang und Zugriff auf dem Namespace, in dem das Secret liegt, kann das Secret abrufen und ändern. Darüber hinaus kann jeder, der berechtigt ist, einen Namespace auszulesen, diesen Zugriff nutzen, um ein beliebiges Secret in diesem Namespace zu lesen.

Um ein Secret zu verwenden, muss ein Pod auf das Secret verweisen. Es kann auf zwei Arten mit einem Pod verwendet werden:

  • Variante 1: Durch das Kubelet beim pullen von Images für den Pod
  • Variante 2: Als Container-Umgebungsvariable

Variante 1

Mit kubectl create secret wird ein Secret erstellt, welches den privaten und öffentlichen Schlüssel enthält:

kubectl create secret generic ssh_secret --from-file=ssh-privatekey=.ssh/id_rsa --from-file=ssh-publickey=.ssh/id_rsa.pub


Nun kann ein Pod erstellt werden, der das Secret mit dem ssh-Schlüssel referenziert und es in einem Volume konsumiert:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh_secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"


Bei den vorherigen Beispielen war der MountPath mit /run/secrets/ fest vorgegeben. Bei Kubernetes kann dieser direkt angegeben werden. In diesem Fall der Path /etc/secret-volume.

Wenn der Befehl des Containers ausgeführt wird, sind die Keys in den folgenden Verzeichnissen zu finden:

/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey


Sobald das Secret erfolgreich abgerufen wurde, erstellt das Kubelet ein Volume, welches das Secret enthält. Keiner der Container des Pods wird gestartet, bevor nicht alle Volumes des Pods eingebunden sind. Verweise auf Secrets, die nicht existieren, verhindern damit den Start des Pods.

Varainte 2

Alternativ kann man Secrets auch aus Umgebungsvariablen verwenden. Die Pod-Definition muss in jedem Container abgeändert werden, der den Wert des Secrets nutzen soll. Für die Umgebungsvariable, die das Secret nutzen soll, sollte der Namen und der Key des Secrets in env[].valueFrom.secretKeyRef angegeben werden. Danach muss das Image und/oder die Befehlszeile so abgeändert werden, dass das Programm nach Werten in den angegebenen Umgebungsvariablen sucht.

Nachfolgend ein Beispiel für einen Pod, der das Secret ssh_secret aus Umgebungsvariablen verwendet:

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: ssh-test-container
    image: mySshImage
    env:
      - name: SECRET_SSH
        valueFrom:
          secretKeyRef:
            name: ssh_secret
            key: ssh


Umgebungsvariablen werden nach einem Secret-Update nicht aktualisiert. Wenn ein Container bereits ein Secret in einer Umgebungsvariablen nutzt, wird ein Secret-Update vom Container nicht wahrgenommen, es sei denn, er wird neu gestartet. Es gibt Lösungen von Drittanbietern, um Neustarts auszulösen, wenn sich Secrets ändern.

Ausblick: „Sealed Secrets“ for Kubernetes

Um Kubernetes-Secrets auch in einem Source Code Management System speichern zu können, können SealedSecrets verwendet werden. Diese können sicher gespeichert werden, auch in einem öffentlichen Repository. Das SealedSecret kann nur von dem Controller entschlüsselt werden, der im Zielcluster läuft, und von niemanden sonst. Nicht einmal der ursprüngliche Autor ist in der Lage, das Secret aus dem SealedSecret zu erhalten. Allerdings ist dies derzeit kein Teil von K8S Vanilla und muss extern in den Cluster installiert werden. (Quelle: https://github.com/bitnami-labs/sealed-secrets)

Sealed Secrets bestehen aus zwei Teilen:

  • Einem cluster-seitigen Controller/Operator
  • Einem client-seitigen Tool: kubeseal

Zuerst wird das Kubernetes Secrets ssh_secret wie vorhin erstellt und im JSON-Format gespeichert:

kubectl create secret generic ssh_secret --from-file=ssh-privatekey=.ssh/id_rsa -o json >ssh_secret.json


Mit kubeseal wird das SealedSecret erstellt:

kubeseal <ssh_secret.json >ssh_sealedSecret.json


Das Secret wird in einer SealedSecret-Ressource verschlüsselt. Dies sieht wie folgt aus:

apiVersion: v1
kind: SealedSecret
metadata:
  name: mysecret
  namespace: mynamespace
spec:
  encryptedData:
    foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....


Das normale Kubernetes-Secret erscheint nach ein paar Sekunden im Cluster und kann wie jedes andere Secret verwendet werden. Die erzeugte Secret-Ressource ist ein abhängiges Objekt des SealedSecret-Objekts und wird als solches aktualisiert und gelöscht, wenn das SealedSecret-Objekt aktualisiert oder gelöscht wird.

Related Posts