Kubernetes' built-in Secret object has a naming problem: it doesn't encrypt anything. Values are base64-encoded — a reversible encoding scheme, not a cipher — and by default the API server writes them to etcd through the identity provider, meaning anyone with etcd read access or get/list permission on the secrets resource can retrieve every credential in near-plaintext form. Encryption at rest exists, but it isn't on by default: cluster admins must hand kube-apiserver an EncryptionConfiguration file via --encryption-provider-config, choosing between providers like aescbc, secretbox, or a KMS-backed envelope scheme. That KMS story has also shifted recently — KMS v1 has been deprecated since Kubernetes v1.28 and disabled by default since v1.29, with KMS v2 now the recommended path for envelope encryption, per Kubernetes' own encrypting-data-at-rest documentation. Meanwhile, GitOps workflows created a second, unrelated problem: teams wanted to commit Secret manifests to Git without leaking them, which is what Bitnami's Sealed Secrets controller was built to solve. And a growing number of platform teams route around etcd entirely with HashiCorp Vault or AWS Secrets Manager. These three approaches solve different problems and are frequently confused for competing solutions to the same one. This post breaks down what each actually protects, what it doesn't, and how they combine in a production-grade setup.
Are native Kubernetes Secrets actually encrypted?
No — by default, native Secrets are base64-encoded and stored in etcd via the identity provider, which performs no encryption at all. Base64 is trivially reversible with a single command; it exists so binary and non-UTF8 data can travel safely inside JSON/YAML manifests, not to provide confidentiality. Kubernetes' own documentation is explicit that encryption at rest for Secrets is opt-in: an administrator must define an EncryptionConfiguration resource and pass it to the API server. Without that step, anyone who can read etcd's data directory — a backup file, a volume snapshot, a compromised etcd node — has the credentials in a form one base64 -d away from plaintext. Even with encryption at rest configured, a user or service account with RBAC permission to get or list Secrets through the Kubernetes API sees the decoded value regardless of how it's stored on disk, because the API server decrypts on read. This is the core point: base64 was never a security boundary, and RBAC plus etcd access controls — not encoding — define the real perimeter around who can read a Secret.
What does encryption at rest actually change, and why did KMS v2 replace v1?
Encryption at rest changes what an attacker gets from a stolen etcd snapshot or disk image — nothing usable — without changing anything about API-level access control. The aescbc and secretbox providers encrypt Secret data with a locally stored key before writing to etcd, while the kms provider uses envelope encryption: each resource gets a unique data encryption key, which is itself wrapped by a key held in an external KMS (AWS KMS, GCP Cloud KMS, Hashicorp Vault's transit engine, etc.), so the raw key material never sits on cluster disk. Kubernetes deprecated the original KMS v1 plugin API starting in v1.28 and disabled it by default in v1.29, according to the project's KMS provider documentation, because v1 lacked a way to detect stale or unhealthy KMS plugin connections and had known performance ceilings under load. KMS v2 addresses both, adding health probing and a caching model that reduces per-request KMS round trips. None of this changes RBAC exposure — a properly authenticated kubectl get secret -o yaml still returns the decoded value either way.
What problem do Sealed Secrets actually solve?
Sealed Secrets solve a GitOps problem, not an etcd problem: how do you commit a Secret manifest to a Git repository without handing every repo reader the plaintext value. The Bitnami-maintained sealed-secrets controller runs in-cluster holding an asymmetric keypair; a developer uses the kubeseal CLI to encrypt a Secret client-side with the controller's public key, producing a SealedSecret custom resource that is safe to store in Git because only the controller's private key can decrypt it. On apply, the controller decrypts the SealedSecret and writes out an ordinary Kubernetes Secret — which then lives in etcd exactly as before, base64-encoded and subject to whatever encryption-at-rest configuration (or lack of one) the cluster already has. That's the detail teams miss: Sealed Secrets protects the supply chain from Git checkout to cluster apply, but it does not change what happens to the resulting Secret once it lands in etcd, and it introduces a new single point of trust — the controller's private key — that itself needs backup and rotation discipline.
Why do teams move to Vault or AWS Secrets Manager instead of etcd?
External secret stores move the source of truth out of etcd entirely, which addresses the access-control and lifecycle gaps that neither base64 nor Sealed Secrets touch. HashiCorp Vault and AWS Secrets Manager both provide centralized audit logging of every read (who accessed which secret, when), fine-grained per-identity leasing, and dynamic, short-lived credentials — Vault can generate a database password with a five-minute TTL that's unique per requesting pod, instead of one static credential shared across a fleet indefinitely. Delivery into pods typically happens through a sidecar pattern (Vault Agent injecting secrets into a shared volume at pod startup) or the Kubernetes CSI Secrets Store driver, which mounts secrets from Vault or AWS Secrets Manager as a volume without ever writing a native Secret object to etcd at all. Automatic rotation is the other major gain: AWS Secrets Manager can rotate a database credential on a schedule via a Lambda function, and Vault's dynamic secrets engines generate credentials that expire and never need manual rotation in the first place. The tradeoff is operational: both introduce an external dependency your cluster must reach at pod-startup time, and Vault specifically requires you to run and secure the vault service itself, including its own unseal/HA story.
How Safeguard Helps
None of these three layers — encryption at rest, Sealed Secrets, or an external store — stops a credential from being hardcoded into a Dockerfile, baked into a container image layer, or committed in a plaintext Helm values file that nobody thought to seal. Safeguard's secrets scanning covers exactly that gap: it scans source code, full Git history, and every layer of a container image for known secret patterns and high-entropy strings, and for issuer-specific findings it verifies whether the credential is actually live — for example confirming an AWS key with a low-privilege sts:GetCallerIdentity call before it ever reaches Critical severity. That verification step matters most in Kubernetes contexts, where a leaked AWS key in a Helm chart or a hardcoded database URI in a manifest committed "just for testing" is a common real-world failure mode that sits alongside, not instead of, whatever secrets-management architecture the cluster runs. When Safeguard finds a verified secret, its remediation playbook can revoke it at the issuer, rotate it through AWS Secrets Manager or Vault directly, and flag the offending commit for history cleanup — closing the loop between your secret store's design and what actually ends up committed to Git.