OCI OKE Should Mount Secrets as Files Instead of
More Info:
Secrets injected as environment variables show up in process listings, container metadata, and many crash dumps. Mount them as files (tmpfs volumes) so they remain inside the containers view of /proc and not in the wider environment.
Risk Level
Medium
Address
Compliance, Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Using Console
To remediate this in OCI OKE, you don’t change a “setting” in the OKE console; you change how your Pods use secrets (volume mounts instead of env). You’ll do it by:
- Ensuring your cluster is accessible.
- Creating/updating Kubernetes Secrets.
- Updating your Pod/Deployment manifests to use those secrets as files via volumes.
Below are step‑by‑step instructions, starting from the OCI Console.
1. Get kubeconfig for your OKE cluster via OCI Console
- Sign in to OCI Console.
- Open the Navigation menu → Developer Services → Kubernetes Clusters (OKE).
- Choose the correct Compartment.
- Click your Cluster.
- On the cluster details page, click Access Cluster.
- Choose your method:
- If you’re on Cloud Shell: click Cloud Shell Access → follow on-screen instructions to set
KUBECONFIG. - If you’re on your local machine:
- Click Local Access.
- Download the
kubeconfigusing the providedoci ce cluster create-kubeconfigcommand. - Export
KUBECONFIGenvironment variable to point to that file.
- If you’re on Cloud Shell: click Cloud Shell Access → follow on-screen instructions to set
Example (local machine):
oci ce cluster create-kubeconfig \
--cluster-id <your-cluster-ocid> \
--file $HOME/.kube/config-oke \
--region <your-region> \
--token-version 2.0.0
export KUBECONFIG=$HOME/.kube/config-oke
kubectl get nodes
2. Create or verify the Kubernetes Secret
If you currently inject secrets as env vars, you already have K8s Secrets. You can re-use them. If not, create one.
Example: create a secret from literal values:
kubectl create secret generic my-app-secret \
--from-literal=db-username=myuser \
--from-literal=db-password=mypassword \
-n my-namespace
Or from a file:
kubectl create secret generic my-app-secret \
--from-file=db-username=./db-username.txt \
--from-file=db-password=./db-password.txt \
-n my-namespace
Verify:
kubectl get secrets -n my-namespace
kubectl describe secret my-app-secret -n my-namespace
3. Update Deployment / Pod spec to mount secrets as files
Identify the workloads currently using env vars like:
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: my-app-secret
key: db-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secret
key: db-password
You’ll change them to a volume and volumeMount.
3.1. Edit the existing Deployment using kubectl (from OCI console access)
From Cloud Shell or your local environment (after step 1):
kubectl -n my-namespace edit deployment my-app-deployment
Replace the env: usage with a secret volume. Example:
spec:
template:
spec:
volumes:
- name: secret-vol
secret:
secretName: my-app-secret # the K8s Secret name
containers:
- name: my-app-container
image: <your-image>
volumeMounts:
- name: secret-vol
mountPath: "/etc/myapp/secrets"
readOnly: true
Your application will see:
/etc/myapp/secrets/db-username/etc/myapp/secrets/db-password
Update your application configuration to read from those files instead of environment variables.
Save and exit the editor; Kubernetes will roll out a new ReplicaSet with the change.
4. (Optional) Remove secret environment variables
Once your application is confirmed working using files:
-
Edit the Deployment again:
kubectl -n my-namespace edit deployment my-app-deployment -
Remove any
env:entries that reference Secrets.
This ensures secrets are not present in the environment anymore.
5. (Optional) Use OCI Vault via Secrets Store CSI Driver
If you’re using OCI Vault and want dynamic mount from Vault instead of K8s Secret:
- In OCI Console: ensure Vault and Secrets are created:
- Identity & Security → Vault → Create vault and secrets.
- Install and configure Secrets Store CSI Driver for OKE (follow OCI docs).
- Configure a
SecretProviderClassand a Pod volume like:
volumes:
- name: vault-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "oci-vault-spc"
This way secrets are only mounted as files from Vault.
6. Validate
From a running Pod:
kubectl -n my-namespace exec -it <pod-name> -- ls /etc/myapp/secrets
kubectl -n my-namespace exec -it <pod-name> -- cat /etc/myapp/secrets/db-username
env | grep DB_USERNAME # should be empty once you’ve removed env vars
If you paste a current Deployment manifest, I can show an exact before/after YAML diff tailored to your setup.
Using CLI
Below is how to remediate this in OCI OKE using the OCI CLI plus kubectl (which is how you actually modify Kubernetes objects).
Goal: stop injecting secrets as environment variables and instead mount them as files from Kubernetes Secrets.
1. Get kubeconfig for the OKE cluster (using OCI CLI)
If you don’t already have kubectl access to the cluster, generate kubeconfig via OCI CLI:
# Variables
REGION="<your-region-identifier>" # e.g. us-ashburn-1
CLUSTER_OCID="<your-oke-cluster-ocid>"
KUBECONFIG_PATH="$HOME/.kube/config"
# Create/merge kubeconfig
oci ce cluster create-kubeconfig \
--region $REGION \
--cluster-id $CLUSTER_OCID \
--file $KUBECONFIG_PATH \
--token-version 2.0.0 \
--kube-endpoint PUBLIC_ENDPOINT \
--auth instance_principal \
--overwrite
Verify access:
kubectl get nodes
2. Identify workloads using Secrets as environment variables
List all namespaces and check workloads:
kubectl get deploy,sts,ds -A
For a particular Deployment (example):
NAMESPACE="my-namespace"
DEPLOYMENT="my-app"
kubectl get deploy $DEPLOYMENT -n $NAMESPACE -o yaml > deployment.yaml
Look for patterns like:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: db_password
# or
envFrom:
- secretRef:
name: my-secret
These must be removed and replaced with a volume/volumeMount.
3. Create or confirm the Secret
If the Secret already exists, you can reuse it. To check:
kubectl get secret -n $NAMESPACE
kubectl describe secret my-secret -n $NAMESPACE
To create/update a Secret:
kubectl create secret generic my-secret \
--from-literal=db_password='<actual-password>' \
--namespace $NAMESPACE \
--dry-run=client -o yaml | kubectl apply -f -
4. Modify the workload to mount the Secret as files
Edit the YAML you exported (deployment.yaml) and change:
-
Remove env/envFrom entries referencing the Secret.
Example to delete:env:- name: DB_PASSWORDvalueFrom:secretKeyRef:name: my-secretkey: db_password -
Add a volume that uses the Secret:
Under
spec.template.spec.volumes:volumes:- name: my-secret-volumesecret:secretName: my-secret -
Mount the volume in the container:
Under
spec.template.spec.containers[].volumeMounts:containers:- name: my-containerimage: your-imagevolumeMounts:- name: my-secret-volumemountPath: "/etc/secrets"readOnly: true
After this, your application should read the secret from files like:
/etc/secrets/db_password
5. Apply the updated manifest
Apply the edited file back to the cluster:
kubectl apply -f deployment.yaml
Verify:
kubectl get deploy $DEPLOYMENT -n $NAMESPACE -o yaml | grep -A5 "volumeMounts"
kubectl get deploy $DEPLOYMENT -n $NAMESPACE -o yaml | grep -A5 "volumes"
Confirm that env / envFrom entries referencing Secrets are gone and the Secret is only used as a volume.
6. Optional: Scripted remediation (using kubectl + yq)
For many Deployments, you may script transformation, but conceptually it remains:
- Use OCI CLI to get kubeconfig for each OKE cluster.
- For each namespace/workload:
- Export YAML
- Remove
env/envFromwithsecretKeyRef/secretRef - Add
volumes[].secretandvolumeMounts[] - Reapply YAML
Summary:
Using OCI CLI you obtain cluster credentials; the actual remediation is done via kubectl by replacing env/secretKeyRef or envFrom/secretRef with Secret-backed volumes and volumeMounts, so secrets are consumed as files instead of environment variables.
Using Python
Below are the key steps and a Python example using the Kubernetes Python client to ensure OCI OKE pods mount secrets as files instead of using environment variables.
1. Prerequisites
- Have
kubectlworking against your OKE cluster. - Python 3.8+.
- Install Kubernetes Python client:
pip install kubernetes
- Ensure your kubeconfig is accessible (usually
~/.kube/config) or you are running in a pod with in-cluster credentials.
2. Create / Ensure the Secret Exists
If you already have a Secret, skip to step 3.
Example: create a secret with two keys (username, password):
kubectl create secret generic my-app-secret \
--from-literal=username=myuser \
--from-literal=password=mypassword \
-n my-namespace
3. Python: Create a Deployment That Mounts the Secret as Files
This example shows a Deployment whose pods mount my-app-secret at /etc/myapp-secrets instead of using env vars.
from kubernetes import client, config
# 1. Load kubeconfig or in-cluster config
try:
config.load_kube_config() # local
except:
config.load_incluster_config() # if running inside OKE
apps_v1 = client.AppsV1Api()
namespace = "my-namespace"
deployment_name = "my-app-deployment"
# 2. Define the volume that uses the secret
secret_volume = client.V1Volume(
name="my-app-secret-volume",
secret=client.V1SecretVolumeSource(
secret_name="my-app-secret",
# optional: items to control file names/keys
# items=[client.V1KeyToPath(key="username", path="username"),
# client.V1KeyToPath(key="password", path="password")]
)
)
# 3. Define the volume mount inside the container
secret_volume_mount = client.V1VolumeMount(
name="my-app-secret-volume",
mount_path="/etc/myapp-secrets", # path where files will appear
read_only=True
)
# 4. Define the container (no secret-based env vars)
container = client.V1Container(
name="my-app-container",
image="ghcr.io/myorg/my-app:latest",
volume_mounts=[secret_volume_mount],
# Example: your app reads from files like /etc/myapp-secrets/username
# env=[] # do NOT set secrets as env vars
)
# 5. Pod template
pod_template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "my-app"}),
spec=client.V1PodSpec(
containers=[container],
volumes=[secret_volume]
)
)
# 6. Deployment spec
deployment_spec = client.V1DeploymentSpec(
replicas=2,
selector=client.V1LabelSelector(match_labels={"app": "my-app"}),
template=pod_template
)
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(name=deployment_name),
spec=deployment_spec
)
# 7. Create the Deployment
apps_v1.create_namespaced_deployment(
namespace=namespace,
body=deployment
)
print(f"Deployment {deployment_name} created with secret mounted as files.")
4. Python: Migrating an Existing Deployment from Env Vars to Secret Files
If your existing Deployment uses secret-based env vars like:
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: my-app-secret
key: username
you should:
- Remove those
enventries. - Add a
volumereferencing the secret. - Add
volumeMountsin containers. - Update your application to read the secret from files.
Example patch in Python (simplified: assumes single container):
from kubernetes import client, config
config.load_kube_config()
apps_v1 = client.AppsV1Api()
namespace = "my-namespace"
deployment_name = "my-app-deployment"
# Get existing deployment
dep = apps_v1.read_namespaced_deployment(deployment_name, namespace)
container = dep.spec.template.spec.containers[0]
# 1. Remove secret-based env vars (keep non-secret envs)
new_env = []
for e in container.env or []:
if e.value_from and e.value_from.secret_key_ref:
# skip secret-based env var
continue
new_env.append(e)
container.env = new_env
# 2. Add volume for the secret (if not already present)
volumes = dep.spec.template.spec.volumes or []
if not any(v.name == "my-app-secret-volume" for v in volumes):
volumes.append(
client.V1Volume(
name="my-app-secret-volume",
secret=client.V1SecretVolumeSource(secret_name="my-app-secret")
)
)
dep.spec.template.spec.volumes = volumes
# 3. Add volumeMount to the container (if not already present)
volume_mounts = container.volume_mounts or []
if not any(vm.name == "my-app-secret-volume" for vm in volume_mounts):
volume_mounts.append(
client.V1VolumeMount(
name="my-app-secret-volume",
mount_path="/etc/myapp-secrets",
read_only=True
)
)
container.volume_mounts = volume_mounts
# 4. Apply the patch/update
apps_v1.patch_namespaced_deployment(
name=deployment_name,
namespace=namespace,
body=dep
)
print(f"Deployment {deployment_name} updated to mount secrets as files.")
5. Application-Side Change
Inside the container, read secrets from files, for example (Python app):
from pathlib import Path
username = Path("/etc/myapp-secrets/username").read_text().strip()
password = Path("/etc/myapp-secrets/password").read_text().strip()
This pattern satisfies “mount secrets as files instead of environment variables” for OCI OKE using Python-based automation.
Using Terraform
# This finding cannot be remediated on the oci_containerengine_cluster
# (OKE cluster) resource itself: how secrets are consumed (env vars vs
# mounted files) is defined in Kubernetes Pod/Deployment specs, not on
# the cluster.
# You must change the Kubernetes workload manifests (or Helm charts)
# that Terraform applies, so that:
#
# 1) Secrets are mounted as volumes (files) …
#
# apiVersion: v1
# kind: Pod
# metadata:
# name: EXAMPLE_POD_NAME
# spec:
# containers:
# - name: EXAMPLE_CONTAINER_NAME
# image: EXAMPLE_IMAGE
# volumeMounts:
# - name: secret-volume
# mountPath: "/var/run/secrets/myapp" # path inside container
# readOnly: true
# volumes:
# - name: secret-volume
# secret:
# secretName: EXAMPLE_K8S_SECRET_NAME # substitute your Secret name
#
# 2) …instead of being injected as environment variables:
#
# env:
# - name: DB_PASSWORD
# valueFrom:
# secretKeyRef:
# name: EXAMPLE_K8S_SECRET_NAME
# key: password
# If you manage these manifests with Terraform, use the Kubernetes provider:
provider "kubernetes" {
host = var.OKE_CLUSTER_ENDPOINT # substitute OKE endpoint
cluster_ca_certificate = base64decode(var.OKE_CLUSTER_CA) # substitute CA data
token = var.OKE_CLUSTER_TOKEN # or use exec/auth
}
resource "kubernetes_secret" "app" {
metadata {
name = "EXAMPLE_K8S_SECRET_NAME" # substitute
namespace = "EXAMPLE_NAMESPACE" # substitute
}
data = {
"password" = var.APP_DB_PASSWORD # substitute
}
type = "Opaque"
}
resource "kubernetes_deployment" "app" {
metadata {
name = "EXAMPLE_DEPLOYMENT_NAME" # substitute
namespace = "EXAMPLE_NAMESPACE"
}
spec {
replicas = 1
selector {
match_labels = {
app = "EXAMPLE_APP_LABEL" # substitute
}
}
template {
metadata {
labels = {
app = "EXAMPLE_APP_LABEL"
}
}
spec {
container {
name = "EXAMPLE_CONTAINER_NAME"
image = "EXAMPLE_IMAGE"
# Mount secret as files instead of env vars
volume_mount {
name = "secret-volume"
mount_path = "/var/run/secrets/myapp" # substitute
read_only = true
}
# DO NOT define env from secretKeyRef for these secrets
# env { ... } # remove any secretKeyRef-based vars
}
volume {
name = "secret-volume"
secret {
secret_name = kubernetes_secret.app.metadata[0].name
}
}
}
}
}
}
This change is applied at the workload level; the oci_containerengine_cluster resource does not expose any argument that can force “secrets as files” across the cluster. No cluster replacement is required; only affected Pods/Deployments are recreated. After updating, terraform plan should show changes only to the kubernetes_* workload resources that drop secret-based env vars and add secret volumes.