ConfigMaps and Secrets are how Kubernetes applications get their configuration, but the way they’re used can be a huge security hole.
Let’s see how a simple web application gets its configuration injected.
apiVersion: v1
kind: Pod
metadata:
name: my-web-app
spec:
containers:
- name: web
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
- name: secret-volume
mountPath: /etc/secrets
volumes:
- name: config-volume
configMap:
name: nginx-config
- name: secret-volume
secret:
secretName: database-credentials
Here, we’re mounting a ConfigMap named nginx-config into /etc/nginx/conf.d and a Secret named database-credentials into /etc/secrets. Nginx will then pick up its configuration from the mounted files.
This is powerful, but it means that any pod with access to the ConfigMap or Secret can potentially read its contents. For ConfigMaps, this is usually fine – they’re for non-sensitive data like feature flags or basic settings. But for Secrets, this is a major risk. By default, Kubernetes RBAC allows pods to read Secrets in the same namespace.
The real danger isn’t just reading the secret. It’s when you mount secrets as environment variables.
apiVersion: v1
kind: Pod
metadata:
name: my-app-env
spec:
containers:
- name: app
image: my-custom-app:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: database-credentials
key: password
If a pod is compromised, an attacker can easily inspect its environment variables and see the sensitive data. This is much easier than trying to find mounted files. The kubectl exec command can list environment variables, and if the pod is running a shell, they can be directly inspected.
To mitigate this, you should avoid mounting Secrets as environment variables whenever possible. Prefer mounting them as volumes. When a Secret is mounted as a volume, the data is stored in tmpfs (an in-memory filesystem) and is not written to disk. This makes it harder for an attacker to persist the data after a compromise.
Here’s how you’d create a Secret with the database credentials:
kubectl create secret generic database-credentials \
--from-literal=username='admin' \
--from-literal=password='supersecretpassword123' \
--from-literal=dbhost='db.example.com'
And the corresponding ConfigMap:
kubectl create configmap nginx-config \
--from-file=nginx.conf=./my-nginx.conf
The my-nginx.conf file might look like this:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
The core principle is least privilege for your configuration data. ConfigMaps are for general configuration, Secrets are for sensitive data. Don’t mix them, and don’t expose Secrets more widely than necessary.
A common, but often overlooked, security risk is that ConfigMaps and Secrets are namespaced resources. If your application needs to access configuration or secrets from multiple namespaces, you’ll need to grant your pod the necessary permissions to get and list those resources across namespaces. This can inadvertently expose sensitive data if not carefully managed.
When you use envFrom to load all keys from a Secret into environment variables, you’re essentially making all of those keys available. This is convenient but can lead to more sensitive data being exposed than strictly necessary for a given container. It’s better to explicitly define valueFrom for each specific key you need.
The most insidious way Secrets get leaked is through container images themselves. If you’re building a custom container image and embedding sensitive configuration (like API keys or database connection strings) directly into the image layers, that data is permanently baked in and visible to anyone who can inspect the image. Always use ConfigMaps and Secrets to inject this data at runtime.
Finally, remember that Secrets are base64 encoded, not encrypted. Anyone with access to the Kubernetes API and the Secret object can decode it easily. The security of Secrets relies heavily on Kubernetes RBAC and network policies to restrict access.
The next hurdle you’ll face is managing Secrets that need to be rotated or updated without restarting your application pods.