Secrets Management
Imagine you are deploying an application that requires access to a database. You have non-sensitive configuration data (like the database’s port number) and highly sensitive data (like the database password). Storing both in the same place—or worse, hardcoding the password in your container image—is a major security risk.
In Kubernetes, while ConfigMaps handle non-sensitive configuration, Secrets are specifically designed to store and manage sensitive information such as passwords, OAuth tokens, and SSH keys.
1. Secrets vs. ConfigMaps: The “Keycard” and the “Manual”
If a ConfigMap is an “Instruction Manual” left on a desk for anyone to read, a Secret is the “Keycard” hidden in your pocket. They share a similar Key-Value structure, but Secrets have crucial differences:
- Obfuscation (Not Encryption): By default, K8s Secrets are simply Base64 encoded, not encrypted. Base64 is an encoding scheme, meaning it can be easily reversed by anyone.
- RAM-Backed Storage: When a Secret is mounted into a Pod as a volume, it is stored in
tmpfs(a RAM-backed filesystem) on the Node. It is never written to the Node’s physical disk, reducing the risk of a compromised disk exposing secrets. - Strict RBAC: You can (and should) use Role-Based Access Control (RBAC) to restrict who can read
Secretresources independently ofConfigMapresources.
[!WARNING] Base64 is NOT Encryption. Anyone with permission to run
kubectl get secret <name> -o yamlcan decode your secrets. By default, Secrets are stored unencrypted in the cluster’s underlying ETCD database.
Secret Types
Kubernetes provides built-in types to handle common secret formats:
Opaque: The default type for arbitrary user-defined key-value pairs.kubernetes.io/tls: specifically for storing TLS certificates and private keys.kubernetes.io/dockerconfigjson: Used to store credentials for pulling images from private container registries.
2. Interactive: The Base64 “Encryption” Illusion
Prove to yourself that Kubernetes Secrets are merely encoded strings. Type a secret below to see how it looks in Kubernetes, and notice how easily it can be reversed.
Base64 Encoder/Decoder
3. Consuming Secrets in Pods
Just like ConfigMaps, Secrets can be exposed to Pods in two main ways: Environment Variables or Volume Mounts.
Option A: Environment Variables
Injecting secrets as environment variables is simple but has a major drawback: they do not hot-reload. If the Secret gets updated, the Pod must be restarted to pick up the new value. Additionally, poorly written applications might dump environment variables to logs during a crash.
Option B: Volume Mounts (Recommended)
Mounting a Secret as a volume places the values into files (e.g., /etc/secrets/db-password). Kubernetes dynamically updates these files when the Secret changes, allowing applications to hot-reload credentials without a restart.
4. Production Security Patterns
The default state of Kubernetes Secrets is insufficient for production. How do you securely manage Secrets when following GitOps practices (where all K8s manifests are stored in a Git repository)? Committing Base64-encoded passwords to GitHub is a critical security violation.
Here are the industry-standard solutions:
1. External Secrets Operator (ESO)
The Enterprise Standard: Instead of creating K8s Secrets directly, you store your passwords in an external Enterprise Vault (like AWS Secrets Manager, Azure KeyVault, or HashiCorp Vault).
You then deploy the External Secrets Operator in your Kubernetes cluster. ESO authenticates with the external vault, fetches the secret, and automatically generates a native K8s Secret inside the cluster.
2. Sealed Secrets (Bitnami)
The Asymmetric Encryption Approach:
- A controller runs inside the K8s cluster and generates a public/private key pair.
- Developers use a CLI tool (
kubeseal) on their local machines, leveraging the public key to encrypt the Secret into aSealedSecretmanifest. - This
SealedSecretis fully encrypted and safe to commit to Git. - Once applied to the cluster, the controller uses its private key to decrypt the
SealedSecretand generate a standard K8sSecret.
3. Encryption at Rest (ETCD)
Even if you use ESO or Sealed Secrets, the resulting native Secret is still stored in the cluster’s ETCD database as plain Base64. To protect against infrastructure-level breaches (e.g., someone stealing the ETCD backup files), you must enable Encryption at Rest at the API server level.
This requires passing an --encryption-provider-config flag to the kube-apiserver, pointing to a configuration file that dictates which resources (like secrets) should be encrypted using a cryptographic provider (like AES-CBC, or a KMS).
5. Solutions: Secret Manifest & Pod Usage
Below is a complete example of defining an Opaque Secret and securely injecting it into a Pod using environment variables.
apiVersion: v1
kind: Secret
metadata:
name: db-auth
# Opaque is the default for arbitrary key/value pairs
type: Opaque
data:
# Values must be explicitly base64 encoded before applying
# echo -n 'admin' | base64 -> YWRtaW4=
username: YWRtaW4=
# echo -n 'p@ssword' | base64 -> cEBzc3dvcmQ=
password: cEBzc3dvcmQ=
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-auth
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-auth
key: password