Prefer Using Secrets Files Over Secrets Environment
More Info:
If possible, rewrite application code to read secrets from mounted secret files, rather than from environment variables.
Risk Level
High
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS OKE
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List workloads that reference Secrets in env vars
- Run on: any machine with kubectl access
- Command:
kubectl get pods --all-namespaces -o json \| jq -r '.items[]| {ns: .metadata.namespace, pod: .metadata.name, containers: .spec.containers}| select([.containers[].env[]? | select(.valueFrom.secretKeyRef!=null)] | length > 0)'
- Purpose: Identify Pods whose containers consume Secrets via
env.valueFrom.secretKeyRef.
-
Identify which Secrets and keys are used as env vars
- Run on: any machine with kubectl access
- Command (tabular view):
kubectl get pods --all-namespaces -o json \| jq -r '.items[]| .metadata.namespace as $ns| .metadata.name as $pod| .spec.containers[]| .name as $cname| .env[]?| select(.valueFrom.secretKeyRef!=null)| [$ns, $pod, $cname, .name, .valueFrom.secretKeyRef.name, .valueFrom.secretKeyRef.key]| @tsv' \| column -t \| sed '1iNAMESPACE POD CONTAINER ENV_VAR SECRET_NAME SECRET_KEY'
- Purpose: Build an inventory of env vars backed by Secrets to review with application owners.
-
Review application capability to use mounted Secret files
- Run: out-of-band review with application teams, using output from step 2
- Questions to decide per env var:
- Can the application be modified/configured to read this value from a file path instead of an environment variable?
- Is the value only needed at startup (still better as file, but lower urgency) or at runtime (file strongly preferred)?
- Are there existing Secret volumes or CSI Secret Store integrations that can be reused?
-
Plan manifest changes to use Secret volumes instead of env vars
- Run on: any machine with kubectl access
- Action: For each container where the app can read from files:
- Edit the Deployment/StatefulSet/Job manifest and:
- Add a
volumesentry referencing the Secret:volumes:- name: my-secret-volsecret:secretName: my-secret - Add a
volumeMountsentry in the container:volumeMounts:- name: my-secret-volmountPath: /var/run/secrets/my-secretreadOnly: true - Remove the corresponding
enventries that usevalueFrom.secretKeyRef, and update application config to read from the mounted file path (e.g./var/run/secrets/my-secret/<SECRET_KEY>).
- Add a
- Edit the Deployment/StatefulSet/Job manifest and:
-
Apply and roll out updated manifests
- Run on: any machine with kubectl access
- Commands (example; adjust file/resource names):
kubectl apply -f path/to/your-workload.yamlkubectl rollout status deployment/your-deployment -n your-namespace
- Operational impact: Updating workload manifests will trigger rollouts; ensure you coordinate with app owners and have roll-back plans.
-
Verify that Secrets are no longer exposed as env vars
- Run on: any machine with kubectl access
- Command:
kubectl get pods --all-namespaces -o json \| jq -r '.items[]| {ns: .metadata.namespace, pod: .metadata.name, containers: .spec.containers}| select([.containers[].env[]? | select(.valueFrom.secretKeyRef!=null)] | length > 0)'
- Success criterion: For workloads you remediated, no Pods should appear in this output; remaining hits indicate env-based Secret usage that must be accepted by risk decision or further refactored.
Using kubectl
Using kubectl
1. List workloads that reference Secrets as environment variables
Run on: any machine with kubectl access.
kubectl get deploy,sts,ds,job,cronjob -A -o yaml | \
awk '
/kind: Deployment|kind: StatefulSet|kind: DaemonSet|kind: Job|kind: CronJob/ {kind=$2}
/name:/ && $1=="name:" {name=$2}
/namespace:/ {ns=$2}
/envFrom:/ {in_envfrom=1}
in_envfrom && /secretRef:/ {sr=1}
in_envfrom && sr && /name:/ && $1=="name:" {
printf "%s %s/%s uses secret %s via envFrom\n", kind, ns, name, $2
}
/env:/ {in_env=1}
in_env && /valueFrom:/ {vf=1}
in_env && vf && /secretKeyRef:/ {skr=1}
in_env && skr && /name:/ && $1=="name:" {
printf "%s %s/%s uses secret %s via env (secretKeyRef)\n", kind, ns, name, $2
}
'
What indicates a problem:
- Lines reporting
uses secret ... via envFromorvia env (secretKeyRef)mean that Secret data is injected into containers as environment variables. - These workloads should be reviewed to see whether they can instead consume the same secrets via a volume mount (files) and application code changes.
2. Inspect a specific workload’s environment configuration
Pick an item from the list above and inspect it in detail.
Deployment example:
kubectl get deployment -n <namespace> <name> -o yaml
Look for:
- Under each container:
envFrom:withsecretRef:(entire Secret exposed as env vars).env:entries withvalueFrom.secretKeyRef.
What indicates a problem:
- Any
envFrom.secretReforenv.valueFrom.secretKeyRefentries show that secrets are provided via environment variables. - Compare with
volumeMounts:andvolumes:: if there is no corresponding secret volume mount that the application could use instead, the pod is likely depending solely on env-based secrets.
3. List Secrets actually referenced by Pods
This helps correlate which Secret objects are used by which pods as env vars.
kubectl get pods -A -o yaml | \
awk '
/kind: Pod/ {kind=$2}
/name:/ && $1=="name:" {name=$2}
/namespace:/ {ns=$2}
/envFrom:/ {in_envfrom=1}
in_envfrom && /secretRef:/ {sr=1}
in_envfrom && sr && /name:/ && $1=="name:" {
printf "Pod %s/%s uses secret %s via envFrom\n", ns, name, $2
}
/env:/ {in_env=1}
in_env && /valueFrom:/ {vf=1}
in_env && vf && /secretKeyRef:/ {skr=1}
in_env && skr && /name:/ && $1=="name:" {
printf "Pod %s/%s uses secret %s via env (secretKeyRef)\n", ns, name, $2
}
'
What indicates a problem:
- Any reported use of
secret ... via envFromorvia env (secretKeyRef)is a candidate for refactoring to use mounted secret files instead of environment variables, subject to application and operational constraints.
4. Compare with Secret volume mounts (for context)
To see which pods already use Secrets as files:
kubectl get pods -A -o yaml | \
awk '
/kind: Pod/ {kind=$2}
/name:/ && $1=="name:" {name=$2}
/namespace:/ {ns=$2}
/volumes:/ {in_vol=1}
in_vol && /secret:/ {sv=1}
in_vol && sv && /name:/ && $1=="name:" {
printf "Pod %s/%s mounts secret %s as a volume (files)\n", ns, name, $2
}
'
How to use this with the earlier outputs:
- Secrets used only as env vars (and not shown here as mounted volumes) are the strongest candidates for change.
- For each such workload, human review is required to decide if the application can be modified to read from files instead of environment variables; kubectl cannot make that decision.
5. Verification after any manual refactor
Once you have updated application code and pod specs (outside this step):
kubectl get deploy,sts,ds,job,cronjob -A -o yaml | \
grep -E "envFrom:|secretRef:|secretKeyRef:"
You should confirm, by inspecting the YAML, that:
- Secrets are no longer referenced under
envFrom.secretReforenv.valueFrom.secretKeyRefwhere you intended to migrate to files. - The same or equivalent Secrets are instead referenced under
volumes[].secretandvolumeMounts[], and your application reads those files.
Automation
#!/usr/bin/env bash
#
# Report pods that consume Kubernetes Secrets via environment variables
# (vs mounted secret files) across the entire cluster.
#
# Run on: any machine with kubectl access and current kubeconfig context set.
set -euo pipefail
echo "Scanning all namespaces for pods using Secrets as environment variables..."
echo
# 1) List all pods and inspect env / envFrom for secretRefs
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
containers: [
.spec.containers[]? as $c
| {
name: $c.name,
envSecrets: [
$c.env[]?
| select(.valueFrom.secretKeyRef != null)
| {
envName: .name,
secretName: .valueFrom.secretKeyRef.name,
secretKey: .valueFrom.secretKeyRef.key
}
],
envFromSecrets: [
$c.envFrom[]?
| select(.secretRef != null)
| {
secretName: .secretRef.name,
optional: ( .secretRef.optional // false )
}
]
}
]
}
| select([ .containers[].envSecrets[], .containers[].envFromSecrets[] ] | length > 0)
| @json
' | jq -r '
. as $pod |
"NAMESPACE: \($pod.ns)\nPOD: \($pod.pod)\n" +
(
$pod.containers[]
| select((.envSecrets | length > 0) or (.envFromSecrets | length > 0))
| " CONTAINER: \(.name)\n"
+ (
if (.envSecrets | length) > 0 then
" env secrets (valueFrom.secretKeyRef):\n"
+ (
.envSecrets[]
| " - env: \(.envName)\n secret: \(.secretName)\n key: \(.secretKey)\n"
)
else "" end
)
+ (
if (.envFromSecrets | length) > 0 then
" envFrom secrets (secretRef):\n"
+ (
.envFromSecrets[]
| " - secret: \(.secretName)\n optional: \(.optional)\n"
)
else "" end
)
)
+ "\n-----------------------------\n"
'
echo "Scan complete."
echo
echo "Interpretation:"
echo "- Any listed pod/container is using Kubernetes Secrets as environment variables."
echo "- For CIS OKE 4.4.1, these are the workloads to review and, where feasible,"
echo " refactor to consume secrets from mounted secret files instead of env vars."
What output indicates a problem
- Any
NAMESPACE/POD/CONTAINERblock printed by this script is a candidate finding. - Lines under:
env secrets (valueFrom.secretKeyRef):orenvFrom secrets (secretRef):show specific environment variables orenvFromblocks that rely on Secrets.
- For each such workload, review the application and manifests to see if you can replace those environment-variable–based secrets with volume-mounted secret files.