Neuigkeiten von trion.
Immer gut informiert.

JWK zu PEM konvertieren in der Shell

Im Rahmen eines Integrationsprojekts von Kubernetes und Vault (bzw. OpenBao ) sollten Secrets aus Vault in Kubernetes bereitgestellt werden. Dabei sollten die verschiedenen direkten Vault Integrationen durch Infrastruktur in Kubernetes abgelöst werden, und die Vault Secrets als Kubernetes Secrets im Cluster verfügbar gemacht werden, um diese dann als Volume oder Umgebungsvariablen bereitzustellen. Damit soll zum einen das Token-Zero-Problem addressiert werden, also wenn Vault zur Verwaltung von Geheimnissen dient, wie werden die Anwendungen mit einem Geheimnis zur Authentifizierung am Vault versorgt, als auch die individuelle Vault Anbindung der verschiedenen Programmiersprachen und Frameworks.

Vault bietet mit dem Vault Secrets Operator auch direkt eine Integration an, die auf unterschiedliche Weise die Authentifizierung gegenüber Vault umsetzt, als auch für das Mapping der Vault Secrets. Ziel war jedoch nicht nur, ein entsprechendes Setup zu implementieren, sondern eine automatisierte Testbarkeit herzustellen. Dazu wird ein stabiles, reproduzierbares Setup benötigt, um die Kubernetes-Vault-Integration jederzeit in der CI Umgebung frisch herzustellen.

In dem Zuge wurde ein Verfahren benötigt, JWK (JSON Web Keys) im PEM Format bereitzustellen - und das am besten automatisiert durch ein einfaches Shell Script z.B. mit der Bash. Eine Umsetzung auf Basis von Kubernetes OIDC JWKS in PEM formatiertes Material zu erhalten wird im folgenden beschrieben.

Der folgende Schritt ist Kubernetes spezifisch und kann ausgelassen werdne, wenn das JWK Schlüsselmaterial, dass in der Shell in das PEM Format übersetzt werden soll, bereits vorliegt. Es geht hierbei darum, vom Kubernetes API Server die OpenID Connect Konfiguration abzufragen, um die URL für das JWKS zu erhalten. Kubernetes verhält sich hir komplett OIDC konform und ein analoges Vorgehen wäre z.B. mit Keycloak möglich.

Nachdem die URL für das JWKS ermittelt wurde, wird das JSON Web Key Material abgefragt.

Abfrage von Kubernetes JWKS aus der OpenID Konfiguration
OPENID_CONFIG_URL=$(kubectl get --raw /.well-known/openid-configuration | jq -r '.jwks_uri')
if [ -z "$OPENID_CONFIG_URL" ]; then
  echo "Error fetching jwks_uri from OpenID configuration."
  exit 1
fi

JWKS_JSON=$(kubectl get --raw "$OPENID_CONFIG_URL")
if [ -z "$JWKS_JSON" ]; then
  echo "Error fetching JWKs JSON."
  exit 1
fi

Kubernetes verwendet unterschiedliches Schlüsselmaterial zur kryptografischen Absicherung der Vertrauensbeziehungen und der Kommunikation der verschiedenen Komponenten. In diesem Fall geht es um die Schlüssel, mit denen die JWT Tokens von ServiceAccounts signiert werden. Damit kann Kubernetes Workload, z.B. ein Pod, seine Identität externen Systemen gegenüber nachweisen.
Genau dies wird bei Vault/OpenBao genutzt.

Es gibt unterschiedliche Algorithmen, die im Kontext von JWK eingesetzt werden können. Typischerweise sind dies RSA und Elliptische Kurven.
Dies Beispiel zur Konvertierung von JWKS zu PEM in der Shell beschränkt sich auf RSA Keys. Zur Sicherheit wird deswegen einmal validiert, dass es sich um einen RSA Schlüssel handelt. JWKS steht wir JSON Web Key Set, d.h. es können mehrere Schlüssel enthalten sein. Das wird zum Beispiel zur Rotation der aktiv genutzten Schlüssel bei gleichzeitiger Kompatibilität mit vorherigen Schlüsseln genutzt. Hier machen wir es uns auch etwas leichter indem wir lediglich einen, und zwar den neuesten Schlüssel, verwenden.

Prüfung des Schlüsseltyps auf RSA Verfahren
KEY_TYPE=$(echo "$JWKS_JSON" | jq -r '.keys[0].kty')

if [ "$KEY_TYPE" != "RSA" ]; then
  echo "Error: Key type is not RSA (found: $KEY_TYPE). Aborting."
  exit 1
fi

Der eigentliche Trick zur PEM Konvertierung in der Shell extrahiert die relevanten Komponenten "n" und "e" aus dem JWK und verwendet diese, um mittels OpenSSL einen neuen RSA Schlüssel aufzubauen. Die beiden Komponenten lassen sich mit dem Kommandozeilenwerkzeug jq (JSON Query) aus dem JWK extrahieren. Anschließend muss das Base64URL-Encoding noch rückgängig gemacht werden und die Daten als Hex-Encoding aufbereitet werden, damit OpenSSL damit arbeiten kann.

Dekodierung der "n" und "e" RSA Komponenten aus dem JWK als Hex-Werte
N=$(echo "$JWKS_JSON" | jq -r '.keys[0].n')
E=$(echo "$JWKS_JSON" | jq -r '.keys[0].e')

# Decode base64url (RFC 4648)
N_BIN=$(echo "$N" | tr '_-' '/+')
E_BIN=$(echo "$E" | tr '_-' '/+')

echo "Convert key to hex ..."

N_HEX=$(echo -n "$N_BIN" | base64 -d 2>/dev/null | od -An -tx1 | tr -d ' \n') || true
E_HEX=$(echo -n "$E_BIN" | base64 -d 2>/dev/null | od -An -tx1 | tr -d ' \n') || true

Mit den so vorliegenden Daten lässt sich ein RSA Key im DER (Distinguished Encoding Rules) Format generieren. Das DER Format ist ein binäres Format, in dem genau die einzelnen Komponenten des RSA Schlüssels binär enthalten sind.

Erzeugung eines RSA Schlüssels im DER Format aus den Komponenten.
echo "Build DER key..."

openssl asn1parse -genconf <(
  echo "
asn1=SEQUENCE:pubkeyinfo

[pubkeyinfo]
algorithm=SEQUENCE:rsa_alg
pubkey=BITWRAP,SEQUENCE:rsapubkey

[rsa_alg]
algorithm=OID:rsaEncryption
parameter=NULL

[rsapubkey]
n=INTEGER:0x${N_HEX}
e=INTEGER:0x${E_HEX}
"
) -noout -out /tmp/rsa_key.der

Mit dem so erzeugten Schlüssel kann OpenSSL wiederum gefüttert werden und den Schlüssel in das PEM Format konvertieren.

Konvertierung in das PEM Format
echo "Convert PEM key..."

# Convert DER to PEM
openssl rsa -in /tmp/rsa_key.der -inform der -pubin -out ./public_key.pem

# Clean up
rm /tmp/rsa_key.der

Nun liegt der primäre JWK Key aus dem JWKS im PEM Format vor und kann mit den üblichen Werkzeugen weiter genutzt werden.




Zu den Themen Kubernetes, Docker und Cloudarchitektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Zur Desktop Version des Artikels