More Info:
Verifies secret-like env vars are not set as literal values. Literal values land in the pod manifest, logs and kubectl describe.Risk Level
MediumAddress
SecurityCompliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
Manual Steps
-
On any machine with kubectl access, list offending Pods and pick one to fix:
kubectl get pods --all-namespaces -o json | jq -r ' [ .items[] | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not) | .metadata as $m | ((.spec.containers // []) + (.spec.initContainers // []))[] | .name as $c | (.env // [])[] | select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i"))) | "ns=\($m.namespace) pod=\($m.name) container=\($c) env=\(.name)" ] | unique[]' -
For each offending Pod, identify whether it is managed by a higher‑level controller (Deployment, StatefulSet, etc.) and capture that object for editing:
# Example for one offending pod; replace NAMESPACE and POD_NAME from step 1 kubectl get pod POD_NAME -n NAMESPACE -o jsonpath='{.metadata.ownerReferences}' | jq # If owned by a Deployment (most common), get the Deployment manifest kubectl get deployment DEPLOYMENT_NAME -n NAMESPACE -o yaml > /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml -
Create or update a Secret object that will hold the sensitive value, in the same namespace:
# Replace placeholders with your actual namespace/secret/key and value kubectl create secret generic app-secrets \ -n NAMESPACE \ --from-literal=DB_PASSWORD='ACTUAL_PASSWORD_VALUE' \ --dry-run=client -o yaml > /tmp/app-secrets.yaml kubectl apply -f /tmp/app-secrets.yaml -
Edit the controller manifest to replace literal
value:withvalueFrom.secretKeyReffor each sensitive env var (do not edit Pods directly, as they will be recreated):# Edit the saved Deployment manifest sed -i 's/DB_PASSWORD:.*/DB_PASSWORD:/g' /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml # optional cleanup # Open the file in an editor and, under spec.template.spec.containers[].env[], change, for example: # - name: DB_PASSWORD # value: "ACTUAL_PASSWORD_VALUE" # to: # - name: DB_PASSWORD # valueFrom: # secretKeyRef: # name: app-secrets # key: DB_PASSWORD # # Repeat for all env vars whose names match PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY. kubectl apply -f /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml -
Wait for the updated Pods to roll out and ensure the old ones are gone:
kubectl rollout status deployment/DEPLOYMENT_NAME -n NAMESPACE kubectl get pods -n NAMESPACE -o wide -
Verify the cluster is now compliant by rerunning the audit command from any machine with kubectl access; it should print
is_compliant=trueand no individual violations:kubectl get pods --all-namespaces -o json | jq -r ' [ .items[] | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not) | .metadata as $m | (.spec.nodeName // "") as $node | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own | ((.spec.containers // []) + (.spec.initContainers // []))[] | .name as $c | (.env // [])[] | select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i"))) | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1" + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end) + (if $node == "" then "" else " node=\($node)" end) + (if $labels == "" then "" else " labels=\($labels)" end) + (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end) + " container=\($c) env=\(.name) is_compliant=false" ] as $rows | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Using kubectl
Using kubectl
On any machine with kubectl access:Pick one violating Pod line and note: with:Ensure indentation is valid and that you modify all containers / initContainers that used the literal value.GKE will roll out new Pods with the Secret-based env var.
- Identify the offending Pod and env var
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| .name as $c
| (.env // [])[]
| select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i")))
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\($c) env=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
ns, name, container, and the env name.- Export the owning workload manifest
NAMESPACE="example-namespace"
DEPLOYMENT="example-deployment"
kubectl get deployment "${DEPLOYMENT}" -n "${NAMESPACE}" -o yaml > /tmp/deployment-secured.yaml
- Create a Secret to hold the sensitive value
MY_SECRET_ENV, my-secret-name, and actual-secret-value appropriately:NAMESPACE="example-namespace"
kubectl create secret generic my-secret-name \
-n "${NAMESPACE}" \
--from-literal=MY_SECRET_ENV=actual-secret-value
- Edit the manifest to use
valueFrom.secretKeyRef
/tmp/deployment-secured.yaml and in the relevant container’s env section, replace:env:
- name: MY_SECRET_ENV
value: "actual-secret-value"
env:
- name: MY_SECRET_ENV
valueFrom:
secretKeyRef:
name: my-secret-name
key: MY_SECRET_ENV
- Apply the updated manifest
kubectl apply -f /tmp/deployment-secured.yaml
- Verification
is_compliant=true is printed or that the specific Pod/env no longer appears:kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| .name as $c
| (.env // [])[]
| select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i")))
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\($c) env=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
Automation
#!/usr/bin/env bash
#
# Automation: Replace sensitive literal env vars in Pods with Secret references
#
# WARNING:
# - This script cannot safely modify existing running Pods without knowing where
# the secret values should come from or what Secret names/keys to use.
# - It focuses on identifying all violations and generating manifest patches
# you can edit to wire to appropriate Secret objects.
#
# REQUIREMENTS:
# - Run on any machine with kubectl access to the cluster and jq installed.
# - kubectl current-context must point to the target GKE cluster.
#
# USAGE:
# 1) Review dry-run output and generated patches.
# 2) Create Secrets containing the sensitive data.
# 3) Edit patches to reference those Secrets (valueFrom.secretKeyRef).
# 4) Apply the patches.
# 5) Re-run the verification step at the end of this script.
#
# This script is safe to re-run; it overwrites its own temp files.
set -euo pipefail
WORKDIR="./fix_sensitive_envvars_$(date +%Y%m%d_%H%M%S)"
mkdir -p "${WORKDIR}/violations" "${WORKDIR}/patches"
echo "Work directory: ${WORKDIR}"
echo "Step 1: Detecting Pods with sensitive literal env vars (excluding kube-* system namespaces)..."
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| .name as $c
| (.env // [])[]
| select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i")))
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\($c) env=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end' \
| tee "${WORKDIR}/violations/raw.txt"
if grep -q '^is_compliant=true$' "${WORKDIR}/violations/raw.txt"; then
echo "Cluster is already compliant; no literal sensitive env vars detected."
exit 0
fi
echo "Step 2: Extracting unique violating Pod owner resources to patch (Deployments, StatefulSets, etc.)..."
# Collect owner references (controllers) from the violation output.
grep ' owner=' "${WORKDIR}/violations/raw.txt" | sed 's/.* owner=//' | awk '{print $1}' | sort -u > "${WORKDIR}/violations/owners.txt" || true
if [[ ! -s "${WORKDIR}/violations/owners.txt" ]]; then
echo "Violations found only on bare Pods (no controller)."
echo "Manual remediation required: edit individual Pod manifests or (preferably) their higher-level workload definitions."
else
echo "Controllers with violations:"
cat "${WORKDIR}/violations/owners.txt"
fi
echo "Step 3: Dumping controller manifests for review and patch preparation..."
# For each controller (e.g., Deployment/ns/name/uid), dump the controller manifest.
while read -r owner; do
kind=$(echo "${owner}" | cut -d'/' -f1)
ns=$(echo "${owner}" | cut -d'/' -f2)
name=$(echo "${owner}" | cut -d'/' -f3)
# Map ReplicaSet/Job/DaemonSet to their editable top-level type when possible.
# NOTE: We do not attempt complex owner resolution; we simply dump the resource by kind/ns/name.
out_file="${WORKDIR}/violations/${kind}_${ns}_${name}.yaml"
echo " - Saving ${kind} ${ns}/${name} to ${out_file}"
# If resource no longer exists, skip gracefully.
if ! kubectl get "${kind}" "${name}" -n "${ns}" >/dev/null 2>&1; then
echo " WARNING: ${kind} ${ns}/${name} not found; it may have been deleted. Skipping."
continue
fi
kubectl get "${kind}" "${name}" -n "${ns}" -o yaml > "${out_file}"
done < "${WORKDIR}/violations/owners.txt"
cat <<'EOF'
Step 4: Prepare patches (manual editing required)
For each saved manifest under violations/, locate containers or initContainers
with env entries like:
- name: DB_PASSWORD
value: supersecret
Change them to reference a Secret, e.g.:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db_password
Or mount a Secret volume and consume via files instead.
You can start patches from the full manifests you just saved. For example:
cp violations/Deployment_default_myapp.yaml patches/Deployment_default_myapp_patch.yaml
Then edit patches/Deployment_default_myapp_patch.yaml to:
- Remove fields you do not want to change (keep metadata.name, metadata.namespace, and spec.template.* relevant to env vars).
- Replace each sensitive "value:" with "valueFrom.secretKeyRef" as above.
After editing, apply all patches:
for f in patches/*.yaml; do
echo "Applying patch $f"
kubectl apply -f "$f"
done
EOF
echo "Step 5: Verification command (run after you have applied your patches)"
cat <<'EOF'
To verify remediation, re-run the benchmark audit:
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| .name as $c
| (.env // [])[]
| select((.value != null) and (.name | test("PASSWORD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|PRIVATE_KEY"; "i")))
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\($c) env=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Compliance is achieved when the output is exactly:
is_compliant=true
EOF

