← Übersicht  ·  Skripte & Dateien  ·  cloud-host · OpenShift · Betrieb

Secrets in OpenShift

Das Modell steht in den Atlas-Diagrammen B9 und B10. Hier spielst du es von Hand durch: Typen anlegen, als env und als Mount konsumieren, den Klartext-Weg, Verschlüsselung at-rest einschalten, Pull-Secrets, Rotation ohne Ausfall – und der Weg raus aus Git.

Stand: 4. September 2026 OKD 4.x / OCP 4.x ns library

Voraussetzung: ein Cluster mit oc login, Namespace library (oc project library). Der at-rest-Teil (Schritt 4) ändert Cluster-Konfiguration und dauert einige Minuten – auf der SNO problemlos, aber nicht mitten in einer anderen Aufgabe.

1 Die Typen

Der type eines Secrets erzwingt, welche Schlüssel drin sein müssen – und wofür OpenShift es benutzen darf.

typeerzwingtwofür
Opaquenichts – beliebige Schlüsselder Normalfall: DB-Passwort, API-Token
kubernetes.io/tlstls.crt + tls.keyRoute-Zertifikate, serving-cert (F5)
kubernetes.io/dockerconfigjson.dockerconfigjsonPull-Secret für eine private Registry (Schritt 5)
kubernetes.io/basic-authusername + passwordz. B. ein Git-Repo-Zugang für einen BuildConfig
kubernetes.io/ssh-authssh-privatekeyein Deploy-Key
kubernetes.io/service-account-tokenAnnotation kubernetes.io/service-account.nameein nicht ablaufendes Token für eine SA (A6) – z. B. pipeline-token
Laptop · Bash
# Opaque - der einfache Weg:
oc create secret generic catalog-db -n library \
  --from-literal=username=catalog \
  --from-literal=password='s3hr-geheim'

# aus einer Datei (der Schluessel wird der Dateiname):
oc create secret generic app-config -n library --from-file=./application.yaml

# ein TLS-Secret:
oc create secret tls catalog-tls -n library --cert=tls.crt --key=tls.key

# ansehen (Typ + Schluessel-Namen, nicht die Werte):
oc get secret catalog-db -n library -o yaml | grep -E 'type:|^  [a-z]'

immutable: true im Secret schützt vor versehentlicher Änderung (und spart etwas API-Last). Maximalgröße 1 MiB, weil alles in etcd landet.

2 env vs Mount – live

Der praktisch wichtigste Unterschied. Ein Pod nimmt ein Secret auf zwei Wegen, und sie verhalten sich verschieden.

als envals Volume-Mount
im ManifestenvFrom.secretRef oder valueFrom.secretKeyRefvolumes[].secret + volumeMounts
Änderung am Secretwirkt nicht – der Wert ist beim Pod-Start eingefrorendie Datei aktualisiert sich nach ~1 min (kubelet-Sync)
AusnahmesubPath-Mounts aktualisieren nicht
sichtbar inoc set env --list, ein Crash-Dump, /proc/<pid>/environnur als Datei im Container (tmpfs, 0644, root – ausser fsGroup/defaultMode)

Zum Selbst-Sehen: ein Secret als Mount in einen Test-Pod, dann den Wert ändern und im Pod warten:

Laptop · Bash
oc create secret generic demo -n library --from-literal=k=eins
oc run demo --image=registry.access.redhat.com/ubi9/ubi-minimal -n library --restart=Never -- \
  sh -c 'while true; do cat /s/k; echo; sleep 5; done'
oc set volume pod/demo -n library --add --type=secret --secret-name=demo --mount-path=/s
oc logs -f demo -n library &
oc create secret generic demo -n library --from-literal=k=zwei --dry-run=client -o yaml | oc apply -f -
# nach ~1 min zeigt das Log 'zwei' - ein env haette weiter 'eins' gezeigt
oc delete pod demo -n library ; oc delete secret demo -n library

Faustregel: Zertifikate und Dateien als Mount (sie sollen sich rotieren lassen), einzelne Werte oft als env (einfacher im Code) – dann aber wissen, dass eine Rotation einen Pod-Neustart braucht (Schritt 6).

3 Der Klartext-Weg

Was in etcd liegt, ist per Default nur base64. Wer get secret darf, hat den Klartext – das ist die Rolle admin/edit, nicht view (B9).

Laptop · Bash
# ein Feld:
oc get secret catalog-db -n library -o jsonpath='{.data.password}' | base64 -d ; echo

# alle Felder in Dateien:
oc extract secret/catalog-db -n library --to=./out --confirm

# eine aenderbare Vorlage (fuer Git NUR nach dem Versiegeln, Schritt 7):
oc create secret generic catalog-db -n library \
  --from-literal=password=neu --dry-run=client -o yaml

oc describe pod zeigt nur die env-Namen, nicht die Werte – aber oc get secret -o yaml > all.yaml oder ein Crash-Dump leakt alles. Deshalb: Secret-YAML nie in ein Ticket, nie in einen Chat, nie unverschlüsselt in Git.

4 Verschlüsselung at-rest

Standardmässig ist jedes etcd-Backup und jedes Disk-Image der Node Klartext. Ein Cluster-Schalter ändert das.

Laptop · Bash
# einschalten:
oc patch apiserver cluster --type=merge -p '{"spec":{"encryption":{"type":"aescbc"}}}'

# der kube-apiserver-Operator rollt eine EncryptionConfiguration aus und
# schreibt alle bestehenden Secrets/ConfigMaps/Token neu. Fortschritt:
oc get apiserver cluster -o jsonpath='{.status.conditions}' | tr ',' '\n' | grep -i encrypt
oc get openshiftapiserver cluster -o jsonpath='{range .status.conditions[?(@.type=="Encrypted")]}{.reason}{"\n"}{end}'
# fertig, wenn reason = EncryptionCompleted
Wertbedeutet
aescbcAES-CBC mit PKCS#7, der bewährte Default
aesgcmAES-GCM – muss man manuell rotieren, sonst Nonce-Reuse-Risiko
(leer) / identitykeine Verschlüsselung (Ausschalten schreibt auch alles neu)

Der Schlüssel liegt in einem Secret im Namespace openshift-config-managed, der Operator rotiert ihn automatisch (wöchentlich). Das ist der OpenShift-Komfort – auf reinem Kubernetes ist das eine EncryptionConfiguration- Datei auf jedem Control-Plane-Node, von Hand.

Wichtig: at-rest schützt vor gestohlenem Backup / gestohlener Disk. Es schützt nicht vor jemandem mit get secret-Recht – der kube-apiserver entschlüsselt beim Lesen. Dafür ist RBAC da.

5 Pull-Secrets

Wie ein Pod ein Image aus einer privaten Registry zieht.

Fallwie
die interne Registry (C5)nichts tun – die default- und builder-SA jedes Namespace haben schon ein Pull-Secret dafür
eine private externe Registry, nur für einen NamespaceSecret anlegen + an die SA hängen (siehe unten)
eine private Registry clusterweit (z. B. registry.redhat.io)den globalen pull-secret in openshift-config ergänzen
ein Image aus einem anderen Namespace ziehender ziehenden SA die Rolle system:image-puller auf den Quell-Namespace geben
Laptop · Bash
# private externe Registry, nur ns library:
oc create secret docker-registry regcred -n library \
  --docker-server=ghcr.io --docker-username=USER --docker-password=TOKEN

oc secrets link default regcred --for=pull -n library     # an die default-SA haengen
# oder direkt im Deployment: spec.template.spec.imagePullSecrets: [{name: regcred}]

# clusterweit (registry.redhat.io o.ae.):
oc get secret pull-secret -n openshift-config -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d > ps.json
#   ... ps.json um den neuen Eintrag ergaenzen ...
oc set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson=ps.json

6 Rotation ohne Ausfall

Einen Secret-Wert austauschen, ohne dass etwas bricht.

1den neuen Wert zusätzlich gültig machen (die DB akzeptiert beide Passwörter, das Cert hat Überlappung) – erst danach das Secret ändern
2Secret ersetzen: oc create secret ... --dry-run=client -o yaml | oc apply -f -
3ein env-Konsument übernimmt nichtsoc rollout restart deploy/<dienst> -n library. Ein Mount holt die neue Datei nach ~1 min, aber die App muss sie neu lesen
4den alten Wert ungültig machen – erst wenn alle Pods neu sind

Automatisch: TLS-Secrets vom service-ca-Operator (F5) und von cert-manager erneuern sich vor Ablauf selbst. Damit der Pod die neue Datei auch benutzt, löst ein Reloader (z. B. stakater/reloader) den rollout restart aus – oder man setzt eine checksum/config-Annotation am Pod-Template, die sich mit dem Secret ändert (macht der Helm-Chart, B12).

7 Raus aus Git

Der Kernkonflikt: die App-Config gehört versioniert in Git – ein Klartext-Secret nicht (base64 ist lesbar, jeder mit Repo-Zugriff hat die Zugangsdaten). Drei Modelle lösen das.

Modellwas in Git liegtwer entschlüsseltder Schlüssel liegt
Sealed Secrets (Bitnami)ein SealedSecret-CR, mit dem Public Key des Controllers verschlüsseltder Controller im Cluster, macht daraus ein normales Secretim Cluster (im Snapshot sichern)
External Secrets Operatorein ExternalSecret-CR, der nur einen Verweis trägtder Operator holt den Wert aus Vault / AWS SM / Azure KV und synct ihn in ein Secretausserhalb (der Store)
SOPS (+ age/PGP)eine verschlüsselte Werte-Datei (Werte chiffriert, Schlüsselnamen lesbar)ein GitOps-Plugin (Argo CD / Flux) beim Applybeim Deployer (der age-Key)

Die Vorlage baut man immer gleich: oc create secret ... --dry-run=client -o yaml – und dann versiegeln (kubeseal), referenzieren (ESO) oder verschlüsseln (sops -e). Vergleich der drei im Atlas-Diagramm E5, der SOPS-Weg im Compose-Kontext in Secrets-Management.

8 Wie die Bibliothek es macht

Heute noch von Hand – GitOps (und damit ein Modell aus Schritt 7) ist Atlas-Block E.

SecretHerkunft
<svc>-db-credentials (Opaque)die Jenkins-Prod-Stage legt sie per oc create secret ... --dry-run | oc apply an – Werte aus Jenkins-Credentials, nichts in Git
keycloak / keycloak-postgresqlvom Bitnami-Helm-Chart erzeugt (Klartext im Chart-Wert bzw. auto-generiert)
pipeline-token (service-account-token)von Hand angelegt, nicht ablaufendes Token für die SA pipeline – nach sno.ps1 up weg, der Post-up-Hook legt es neu an
die internen TLS-Secrets– gibt es noch nicht, die Dienste reden intern HTTP (der service-ca-Weg wäre F5)

Jedes Secret gehört genau einem der 8 Dienste, alles im Namespace library – ein kompromittierter Pod sieht nur seine eigenen und die des Namespace, nicht die des ganzen Clusters.

+ Die Kurzfassung

Typerzwingt die Schlüssel und den Zweck – Opaque ist der Normalfall
Konsumenv = eingefroren (Rotation braucht rollout restart), Mount = ~1 min live (ausser subPath)
at-restDefault ist base64 – apiserver clusterencryption.type: aescbc verschlüsselt etcd. Schützt das Backup, nicht vor RBAC
lesenwer get secret hat, hat den Klartext (oc extract) – deshalb view statt edit für alle, die es nicht sehen sollen
aus GitSealed Secrets (Key im Cluster) · ESO (Store aussen) · SOPS (Key beim Deployer) – die Vorlage ist immer oc create secret --dry-run