Kubernetes Secrets Are Just Base64 Not Encryption. Here's What That Actually Means
If you've run Kubernetes for more than a day, you've seen this:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: c3VwZXJzZWNyZXQ=
And somewhere in the back of your mind you filed it under "encrypted credentials." It isn't. Those values are Base64, and Base64 is encoding, not encryption. YWRtaW4= is just admin written in a different alphabet - reversible instantly, by anyone, with no key. This trips up an astonishing number of teams, so let's clear it up for good.
Prove it in one command:
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 --decode # supersecret
No key. No password. No "decryption." Base64 is a binary-to-text encoding - its entire job is to represent arbitrary bytes using a safe 64-character alphabet so they survive transport and storage in text-based systems (etcd, YAML, JSON, HTTP headers). Kubernetes encodes Secret data values purely so binary values (certs, keys, gzip blobs) can live inside a YAML/JSON object. That's it. Security was never the point.
If you want to eyeball a whole Secret at once instead of decoding fields one by one, I built a small in-browser tool for exactly this - paste the YAML and it decodes every data: value locally (nothing is uploaded): Kubernetes Secret Decoder. (Disclosure: it's my free, no-ads tool.)
data vs stringData
A quick related gotcha: data expects Base64, but stringData expects plain text and Kubernetes Base64-encodes it for you on write:
stringData:
password: supersecret # plain text; k8s encodes it into data.password
Both end up identically un-secret at rest.
So what actually protects a Secret?
Base64 gets you nothing here. Real protection is layered:
- Encryption at rest for etcd - configure a KMS provider (AWS/GCP/Azure KMS) or at minimum
aescbc/secretboxvia anEncryptionConfiguration. Without this, Secrets sit in etcd Base64-only. - Sealed Secrets (Bitnami) - encrypt secrets before they hit Git; only the in-cluster controller can decrypt. Safe to commit.
- SOPS (+ age/KMS) - encrypt values in your manifests/GitOps repo.
- External secret stores - HashiCorp Vault, AWS Secrets Manager, etc., pulled in via the External Secrets Operator.
- RBAC - lock down who can
get/listSecrets. If a ServiceAccount can read the Secret, it can read the plaintext.
Encoding vs encryption vs hashing (the mental model)
| Encoding (Base64) | Encryption (AES-GCM) | Hashing (SHA-256) | |
|---|---|---|---|
| Purpose | Safe transport of bytes | Confidentiality | Integrity / fingerprint |
| Needs a key? | No | Yes | No |
| Reversible? | Yes, trivially | Yes, with the key | No (one-way) |
| Hides data? | No | Yes | N/A |
Base64 is in the left column. Kubernetes Secrets, out of the box, are in the left column. If you want to go deeper on that distinction, here is a helpful article: Is Base64 Encryption?
TL;DR
- A Kubernetes Secret's data is Base64, not encrypted - decode it with one
base64 -d. - Treat any Secret YAML as plaintext credentials: don't commit it, lock down RBAC.
- For actual protection: encryption at rest (KMS), Sealed Secrets, SOPS, or an external secret store.
The next time someone says "it's fine, it's in a Secret," you'll know to ask the real question: is etcd encrypted, and who has RBAC to read it? What's bitten you with Kubernetes Secrets? Drop it in the comments.
Comments
No comments yet. Start the discussion.