Why this matters
What you need to know already: ConfigMaps and how pods consume them (15.29), base64 (9.17), tmpfs and /proc/<pid>/environ (3.14, 5.3), TLS certificates (9.15), registries (10.54), jsonpath (15.38 uses it more).
Database passwords, API tokens and TLS keys must reach the app too - but you do not want them readable by everyone who can list config. Kubernetes has a separate kind for them, the Secret. The trap is believing the name: this lesson shows how little a Secret protects by itself, and how to handle it carefully.
A Secret is a ConfigMap with better manners
Same shape, same consumption modes (secretKeyRef, secretRef in envFrom, secret: volumes), with three differences: values are base64-encoded in data, there are typed variants, and the kubelet keeps secret volumes on tmpfs (RAM), never on the node's disk.
k create secret generic NAME --from-literal=K=V makes one (generic = the plain key/value type, Opaque):
$ k create secret generic db-creds --from-literal=username=orders --from-literal=password='S3cr3t!pw'
secret/db-creds created
$ k get secret db-creds -o yaml
apiVersion: v1
data:
password: UzNjcjN0IXB3
username: b3JkZXJz
kind: Secret
metadata:
name: db-creds
namespace: default
...
type: Opaque
base64 is not encryption
$ k get secret db-creds -o jsonpath='{.data.password}' | base64 -d
S3cr3t!pw
(-o jsonpath='{.data.password}' prints just that one field of the object; base64 -d decodes it.) That is all it takes. base64 is an encoding so binary data survives JSON; it provides no confidentiality at all. Anyone who can get the Secret can read it - and list returns full objects, so granting list on secrets is granting read on every secret in the namespace. Also anyone who can create a pod in the namespace can mount it. Permissions on who may read Secrets are where their protection really lives.
Later (Ch 17): RBAC, Kubernetes' permission system, decides who may get, list or mount Secrets.
describe is polite - it shows sizes, not values:
$ k describe secret db-creds
Name: db-creds
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 9 bytes
username: 6 bytes
The classic encoding bug:
$ echo 'S3cr3t!pw' | base64 # WRONG: echo adds a newline
UzNjcjN0IXB3Cg==
$ echo -n 'S3cr3t!pw' | base64 # right
UzNjcjN0IXB3
The first one decodes to the password plus \n, and the database login fails with a correct-looking password. --from-literal and stringData avoid it:
apiVersion: v1
kind: Secret
metadata:
name: db-creds
type: Opaque
stringData: # plain text in, stored base64 in data:
username: orders
password: S3cr3t!pw
stringData is write-only - reading the object back shows only data. And a manifest with stringData in git is a plaintext password in git.
Types
Opaque arbitrary key/values (the default)
kubernetes.io/tls tls.crt + tls.key - a certificate and its key (used in Ch 16)
kubernetes.io/dockerconfigjson registry credentials for imagePullSecrets
kubernetes.io/service-account-token legacy long-lived SA tokens (avoid)
kubernetes.io/basic-auth, ssh-auth conventions, validated keys
k create secret tls web-tls --cert=tls.crt --key=tls.key
k create secret docker-registry regcred --docker-server=registry.lab \
--docker-username=ci --docker-password="$TOKEN"
(--cert/--key take the certificate and key files; --docker-server, --docker-username, --docker-password are the registry login.) A pod pulls from a private registry with that secret referenced in spec.imagePullSecrets: [{name: regcred}].
Consuming
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: password
volumeMounts:
- name: creds
mountPath: /etc/creds
readOnly: true
volumes:
- name: creds
secret:
secretName: db-creds # note: secretName, not name
defaultMode: 0400
# app = a pod with the mounts above (the next mission builds one)
k exec app -- ls -la /etc/creds
lrwxrwxrwx 1 root root 15 Sep 23 10:01 password -> ..data/password
lrwxrwxrwx 1 root root 15 Sep 23 10:01 username -> ..data/username
k exec app -- cat /etc/creds/password
S3cr3t!pw
Files are decoded - the app reads plain text. Prefer files to env vars for secrets: env vars leak into crash dumps, /proc/<pid>/environ (3.14), child processes, and "print the environment" debug endpoints. The same env-vs-volume update rule applies as for ConfigMaps.
What a careful company actually does
Secrets in etcd (the cluster's database, 15.5) are only base64 unless you turn on encryption at rest: an EncryptionConfiguration file given to the API server (--encryption-provider-config), ideally backed by a KMS (key management service - a cloud key store that holds the encryption key), so etcd and its backups hold ciphertext.
Better still, keep the secret out of Kubernetes objects entirely: a driver mounts secrets straight from an external secret store (like the Key Vault of 12.3) into the pod as files. Rotation happens in the store; nothing sensitive sits in git or etcd. The other common pattern is an operator that copies secrets from the store into Secret objects.
immutable: true works for Secrets exactly as for ConfigMaps: no edits, recreate instead, and no kubelet watch.
The Downward API
The Downward API lets a container learn facts about its own pod - its name, namespace, node, IP - without calling the API server. Each fieldRef names a field of the pod object:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MEM_LIMIT
valueFrom:
resourceFieldRef:
resource: limits.memory
As a volume (downwardAPI: with items), labels and annotations are available as files that update when the labels change. The logging library that tags every log line with pod and node name is the Downward API at work. (limits.memory is the container's memory limit - Ch 17.)
What you can now do:
- Create, read and decode a Secret, and avoid the
echonewline bug. - Mount a Secret as read-only files (preferred) or as env vars.
- Explain why base64 is not protection, and pass pod facts with the Downward API.