Don’t Hardcode – Managing Secrets the Right Way

Duration: ~20 minutes

Overview

Keep passwords out of Git and YAML. Store them as Secrets, mount or inject them at runtime, and rotate without rewriting every deployment.

Replace a hardcoded credential with a Secret injected as an env var and as a file mount, then rotate it and compare how each injection updates.

Why it matters

Credentials in manifests or images land in Git history and travel with every clone. Secrets keep the values out of source, limit who can read them with RBAC, and make rotation a Secret update instead of a code change. File mounts refresh on update; env vars usually need a restart.

What does it solve

  • Git history leakage

  • Rotation friction

  • Broad manifest visibility

  • Credential reuse temptation

Your Mission

Credential hunters love passwords parked in Git and Deployment YAML—they survive forever in history. Catch the insecure pattern, strip it, then inject Secrets in ways attackers cannot scrape from manifests, and prove rotation still works.

Click each step only if you need a hint.

Open a secrets sandbox
oc new-project 101-06-s-secrets
Simulate the leak — hardcoded credential in the Deployment

Show how easy loot is when passwords live in env literals.

oc create deployment insecure-app --image=registry.access.redhat.com/ubi9/ubi -- sleep infinity
oc set env deployment/insecure-app DB_PASSWORD=SuperSecret123
oc get deploy insecure-app -o yaml | grep -n 'SuperSecret'
Tear down the leaky workload
oc delete deployment insecure-app
Move the credential into a Secret
oc create secret generic db-credentials --from-literal=DB_PASSWORD=SuperSecret123
Defend with Secret-as-env (no plaintext in the Deployment)
oc create deployment secure-app-env --image=registry.access.redhat.com/ubi9/ubi -- sleep infinity
oc set env deployment/secure-app-env --from=secret/db-credentials
oc exec deploy/secure-app-env -- printenv | grep DB_PASSWORD
oc get deploy secure-app-env -o yaml | grep -n 'SuperSecret'
Defend with Secret-as-file (rotation-friendly)
oc create deployment secure-app-file --image=registry.access.redhat.com/ubi9/ubi -- sleep infinity
oc set volume deployment/secure-app-file --add --name=creds --type=secret --secret-name=db-credentials --mount-path=/opt/creds --read-only
oc exec deploy/secure-app-file -- ls /opt/creds
oc exec deploy/secure-app-file -- cat /opt/creds/DB_PASSWORD
Rotate under pressure — prove file updates vs env restart

After compromise suspicion, rotation must stick. File mounts refresh; env needs a restart—know which path you chose.

oc create secret generic db-credentials --from-literal=DB_PASSWORD=NewValue456 -o yaml --dry-run=client | oc apply -f -
oc exec deploy/secure-app-file -- cat /opt/creds/DB_PASSWORD
oc rollout restart deployment/secure-app-env
oc exec deploy/secure-app-env -- printenv | grep DB_PASSWORD
Cleanup
oc delete project 101-06-s-secrets --wait=false

Debrief

You moved a hardcoded credential into a Kubernetes Secret, consumed it as env and as a file mount, and compared rotation behavior under pressure.

What breaks without this:

  • Credentials in Git, Dockerfiles, or Deployment YAML → trivial loot after a foothold

  • Env-only mounts → rotation often needs a restart

  • Secrets without RBAC / etcd encryption → broad read and disk exposure

Controls that matter: API Secrets (not plaintext in manifests), prefer file mounts for rotation, restrict get/list, enable etcd encryption, and plan external secret backends for mature environments.

Quick facts: values are base64-encoded, not encrypted, unless etcd encryption (or an external store) is on. Secrets alone do not stop exfiltration—pair with least-privilege RBAC.

giphy

Cleanup

Before moving to the next module, run the lab cleanup script to reset transient resources from this module.

cd ~/openshift-security-roadshow
bash setup/lab-cleanup.sh --module 101-06