Kubernetes gives you two objects for handing data to a pod, and the most common credential leak in a cluster is not a stolen Secret but a password sitting in a ConfigMap that half the organisation can read. ConfigMaps and Secrets look almost identical in YAML, are consumed the same way and share the same 1 MiB limit, so the choice between them is often made by habit rather than by risk. The real differences are in who can read each object, how it is logged, how it is handled on the node and whether it is encrypted in etcd. This guide compares the two as a placement decision: what belongs in each, why a Secret is still not protected out of the box, how to stop credentials drifting into ConfigMaps, and when neither object should hold the value at all. It is for platform engineers, Kubernetes security owners and reviewers. The choice of external store is covered in Kubernetes Secrets vs External Secret Stores and hardening of native Secrets in the Kubernetes Secrets Security Guide; this page does not repeat them.
Key takeaways
- The short answer: anything that grants access (passwords, tokens, API keys, private keys, connection strings with credentials) goes in a Secret or an external store; everything else goes in a ConfigMap. The Kubernetes documentation says plainly that a ConfigMap does not provide secrecy or encryption.
- A Secret is a different access class, not a vault. Its value is base64 encoded, which the Kubernetes good practices page calls no additional confidentiality over plain text, and it is stored unencrypted in etcd until you configure encryption at rest.
- The protection comes from RBAC, audit and node handling. The built-in view role excludes Secrets, audit policies log Secrets at Metadata level, and the kubelet keeps mounted Secrets in tmpfs. A credential placed in a ConfigMap loses all three.
- Pod creation and list rights cut through both. Anyone who can create a pod in a namespace can read every Secret there, and list or watch on Secrets returns their contents.
- Misplacement is a detectable, preventable defect. Scan manifests and live ConfigMaps for credential patterns, block them at admission and measure the count down to zero.
The short answer: what goes where
Kubernetes describes a ConfigMap as an API object for storing non-confidential data in key-value pairs, consumed as environment variables, command-line arguments or files in a volume. A Secret holds a small amount of sensitive data such as a password, a token or a key. The test is simple: if a person or process holding this value could authenticate to something, or decrypt something, it is a secret. Feature flags, hostnames, port numbers, log levels, public certificates and CA bundles are configuration. A database URL is configuration until someone appends user:password@ to it.
| Data | Object | Reason |
|---|---|---|
| Log level, feature flags, timeouts, replica tuning | ConfigMap | No access value; wide read access is useful for debugging |
| Service hostnames, ports, public endpoints | ConfigMap | Discoverable anyway; no credential |
| Public CA bundle, public TLS certificate | ConfigMap | Public material; only the private key is secret |
| TLS private key and certificate pair | Secret of type kubernetes.io/tls | Private key grants impersonation |
| Registry pull credentials | Secret of type kubernetes.io/dockerconfigjson | Grants read (often write) to images |
| Database password, API key, webhook signing key | Secret, or an external store mounted or synced into the pod | Grants access; needs rotation and audited reads |
| Connection string with embedded credentials | Split: host and options in a ConfigMap, credential in a Secret | Keeps the credential in the access class that protects it |
| Cloud provider access keys | Neither, where possible: workload identity | Long-lived keys in any object are a standing exposure |
What is actually different between the two objects
The two objects share more than most people expect. Both are namespaced and limited to 1 MiB. Both can be consumed as environment variables or volume files, and both update in mounted volumes eventually but not in subPath mounts. Both support immutable: true, stable since Kubernetes v1.21, which cannot be reverted. The differences that matter for security are these.
| Property | ConfigMap | Secret |
|---|---|---|
| Stated purpose | Non-confidential data; docs say it does not provide secrecy or encryption | Sensitive data such as passwords, tokens and keys |
| Value encoding | data is plain UTF-8; binaryData is base64 | data is base64; stringData accepts plain text on write |
| Typed variants | None | Opaque (default), kubernetes.io/tls, kubernetes.io/basic-auth, kubernetes.io/ssh-auth, kubernetes.io/dockerconfigjson, kubernetes.io/dockercfg, kubernetes.io/service-account-token, bootstrap.kubernetes.io/token |
Built-in view ClusterRole | Readable (view covers most namespaced objects) | Excluded, because reading Secrets can expose service account credentials |
Built-in edit ClusterRole | Read and write | Read and write, plus the ability to run pods as any service account in the namespace |
| Example audit policy in the Kubernetes docs | Request body logged for changes in kube-system; Metadata elsewhere | Metadata only, so values are not written to audit logs |
| On the node | Mounted as ordinary volume data | Sent only to nodes running a pod that needs it; mounted copy held in tmpfs and deleted with the pod |
| Encryption at rest in etcd | Possible (EncryptionConfiguration can list configmaps), rarely configured | Possible and expected; default provider identity means none |
| Tooling expectations | Dashboards, GitOps diffs and kubectl describe show values | Secret-aware tools mask values; scanners and external store operators target Secrets |
The pattern is clear: none of these protections is cryptographic by default. A Secret is safer because the platform treats it as a separate access class, and every safeguard depends on the value being in that class.
The base64 myth and the etcd reality
Base64 exists in Secret manifests so that binary values such as keystores survive YAML. It is reversible by anyone. The good practices page warns that a Secret manifest with base64 data, shared or checked into a repository, makes the secret available to everyone who can read the manifest. The Exposed Kubernetes Secrets 2023 page describes registry credentials in dockercfg and dockerconfigjson Secrets found in public repositories, one of which reached a large artefact repository. If you need Secrets in Git, commit ciphertext or a reference, never the manifest; see SOPS and Sealed Secrets.
In etcd, Kubernetes states that Secrets are stored unencrypted by default and that anyone with API or etcd access can read or modify them. Encryption at rest is configured with an EncryptionConfiguration on the API server. The encryption task page states that the identity provider is the default and provides no confidentiality, recommends KMS v2 (stable from v1.29) where a third-party key manager is used, and notes that encryption applies on write, so existing objects must be rewritten. Encrypted values carry a k8s:enc:<provider>:v1: prefix in etcd, which is how you verify it. Two ConfigMap-specific points from the same page:
- ConfigMaps can be encrypted too: the example configuration lists
secrets,configmapsand a custom resource under one provider, and wildcards (*.*, from v1.27) cover everything. Encrypting ConfigMaps is a compensating control for misplacement, not a reason to accept it, because RBAC and audit exposure remain. - Order decides precedence: a more specific entry, such as
configmaps.with a trailing dot for the core group, must come before a wildcard to override it. Overlapping wildcards are not allowed.
Etcd backups and snapshots carry whatever was in etcd at the time, including plaintext Secrets written before encryption was turned on and every ConfigMap. Treat backup stores as holding secrets. See How to check Kubernetes secrets encryption at rest.
RBAC: the boundary that separates the two
Most of the security difference between the objects is decided in RBAC. Platform teams routinely grant the built-in view role to developers, support staff, monitoring tools and CI service accounts, precisely because it excludes Secrets. Kubernetes explains why: reading Secrets enables access to service account credentials and so API access as any service account in the namespace. A credential in a ConfigMap is therefore readable by everyone with view, and by any custom role that grants get or list on configmaps, which many operators and dashboards request cluster-wide.
For Secrets themselves, three rules from the Kubernetes documentation set the real boundary:
- List and watch reveal contents: granting
liston Secrets lets the subject fetch their values, so restrictlistandwatchto system components and grantgeton named resources where possible. - Pod creation reveals contents: anyone authorised to create a pod, or indirectly a Deployment, in a namespace can read any Secret in that namespace. The
editrole allows exactly this. - Privileged containers reveal the node: a container running with
privileged: truecan access all Secrets used on that node.
So namespaces are the isolation unit. Put workloads with different trust levels in different namespaces, keep pod creation rights tight, and enforce Pod Security admission to stop privileged pods. The Kubernetes RBAC Guide and How to audit Kubernetes cluster admin bindings cover role design and review.
Threat model, failure modes and controls for each object
ConfigMap
- Threat model: the assumed adversary is anyone with read access to the namespace or cluster: a developer with
view, a compromised monitoring agent, a dashboard exposed without authentication, or a CI job that dumps manifests into logs. ConfigMaps are designed to be widely readable, so any secret in one inherits that audience. - Failure modes: passwords in application property files mounted from a ConfigMap; connection strings with embedded credentials; Helm values files that template a key into a ConfigMap; request bodies containing credentials written to audit logs where a policy logs ConfigMap changes at Request level, as the example policy does for
kube-system. - Controls: admission policy that rejects ConfigMaps whose keys or values match credential patterns; manifest scanning in CI; immutable ConfigMaps for configuration that should not change at runtime; read access granted freely only once you have evidence there are no secrets in them.
- Decision criterion: use a ConfigMap only when disclosure of every value to every reader of the namespace would be harmless.
Secret
- Threat model: the adversary is someone who gains etcd or backup access, holds list or watch, can create pods in the namespace, or compromises a node running a privileged container. Base64 offers no barrier to any of them.
- Failure modes: encryption at rest never enabled, or enabled but existing Secrets never rewritten; local
aescbckeys on the control plane host; broadeditbindings; Secret manifests in Git; values passed as environment variables and leaked through crash dumps or debug endpoints. - Controls: KMS v2 or the managed service's envelope encryption; least-privilege RBAC with no human
list; separate namespaces per trust level; volume mounts in preference to environment variables, and only to the container that needs the value; immutable Secrets with versioned names for static values; short-lived credentials from an external store where the target supports them. - Decision criterion: a native Secret is acceptable when the cluster is the only consumer, encryption at rest is verified and RBAC is tight. Shared, high-value or long-lived credentials belong in an external store; see Kubernetes Secrets vs External Secret Stores.
Neither: workload identity
The strongest option for cloud credentials is to store nothing. Cloud workload identity and federated service account tokens let a pod obtain short-lived cloud credentials without a key in any object. Projected service account tokens are audience-bound and expire, unlike legacy kubernetes.io/service-account-token Secrets. See the Workload Identity Federation Guide and the Kubernetes Service Account Token Guide.
Decision guidance
| Situation | Choose | Trade-off |
|---|---|---|
| Value is harmless if disclosed | ConfigMap | Wide visibility helps debugging; no rotation burden |
| Value grants access and only this cluster uses it | Secret with encryption at rest and tight RBAC | Low operational load; rotation is manual |
| Value is shared across clusters, VMs or functions | External store, synced or mounted | One source of truth; adds an operator or driver to secure |
| Cloud API access from a pod | Workload identity, no stored key | Needs provider configuration; removes the secret entirely |
| Third-party chart reads credentials from a ConfigMap | Override the chart to use a Secret, or patch it; if impossible, isolate the namespace and encrypt configmaps | Maintenance cost of chart overrides versus standing exposure |
| Configuration file mixes settings and secrets | Split into two files: ConfigMap for settings, Secret for credentials, mounted side by side | Small application change; clean separation for review |
| Static value that should never change at runtime | Immutable ConfigMap or Secret with a versioned name | Rotation means a new object and a rollout; reduces API server watch load at scale |
Small organisations: a single cluster with a handful of namespaces gets most value from three moves: verified encryption at rest, removing list on Secrets from humans, and a CI scan that fails builds with credentials in ConfigMaps. Large organisations: with many clusters and tenants, standardise on an external store as the system of record, enforce the split at admission in every cluster, and report misplacement as a platform KPI.
Worked scenario: a payments service
A team deploys a payments API with a Helm chart. The chart renders application.properties into a ConfigMap, including spring.datasource.password and a payment provider API key. Developers and the observability agent hold view in the namespace; the GitOps tool shows ConfigMap values in its diff view; audit policy logs ConfigMap writes at Metadata.
- Exposure: every holder of
view, the observability agent's service account and anyone who can see the GitOps diff can read the database password and API key. The values are also in the Git repository that holds the Helm values. - Fix the placement: split the file. Non-secret properties stay in the ConfigMap; the password and API key move to a Secret mounted as a separate file, or are fetched from the external store. The application reads both locations.
- Rotate, do not move: because the old values were readable by a wide audience, issue a new database password and API key, revoke the old ones at the database and provider, then deploy. Moving the same value into a Secret leaves it compromised.
- Clean up history: remove the values from the Helm values repository and its history, and check etcd backups and any log sinks that captured manifests.
- Prevent recurrence: add an admission policy that warns, then denies, ConfigMaps with keys such as
password,secret,tokenorapikeyor values matching key formats; add a CI secret scan over rendered manifests.
The same lesson appears at a larger scale in the Tesla Kubernetes Cryptojacking 2018 page: an unauthenticated Kubernetes console let intruders reach a pod holding AWS credentials. Whatever the object, a credential a workload can read is exposed to anyone who reaches the management plane.
How to implement: phased approach
- Baseline encryption: enable KMS v2 or the managed service's envelope encryption for
secrets; decide whether to addconfigmapsas a compensating control; rewrite existing objects and verify thek8s:enc:prefix. - Inventory: list every ConfigMap and Secret by namespace, owner and consumer. Run a credential scanner over live ConfigMap data and over rendered manifests in Git.
- Triage findings: classify each hit as a real credential, a test value or a false positive. Real credentials are rotated first, then moved.
- Admission in audit mode: deploy a ValidatingAdmissionPolicy (stable since v1.30, written in CEL) or your policy engine with a warn or audit action, matching credential-like keys and value patterns in ConfigMaps and Secret manifests with plaintext
stringDatafrom Git-managed namespaces. - Tighten RBAC: remove human
listandwatchon Secrets, revieweditand pod creation rights, and review who has cluster-widegeton ConfigMaps once secrets are gone. - Enforce: switch admission to deny in pilot namespaces, then by ring. Keep a documented exception path with an owner and expiry for charts that cannot be fixed immediately.
- Roll back safely: if enforcement breaks a deployment, revert the policy binding to warn for that namespace rather than restoring a credential to a ConfigMap; any rollback of credentials uses freshly issued values.
The Kubernetes Admission Control Guide covers policy engine choice and rollout.
Detection and measurement
Monitor: audit events for get, list and watch on secrets, especially cross-namespace lists and reads by human users; the good practices page suggests alerting when a single user reads many Secrets concurrently. Watch creation and update of ConfigMaps in sensitive namespaces, new pods that mount Secrets they did not use before, privileged pod creation, and admission policy warnings for credential-like ConfigMap content. Keep Secrets and ConfigMaps at Metadata level in the audit policy unless you have confirmed that no credential can appear in a request body. See the Kubernetes Audit Log Detection Guide.
- Credential findings in ConfigMaps: count across all clusters, target zero.
- Encryption verified: percentage of clusters with Secrets encryption at rest verified in etcd, target 100%.
- Secret list rights: number of human or non-system subjects with
listorwatchon Secrets. - Admission violations: warn-mode hits per week, trending down before enforcement.
- Time to rotate: median hours from finding a credential in a ConfigMap to revoking it.
- Stored cloud keys: count of Secrets holding cloud provider access keys where workload identity is available.
Common failure patterns
- Credentials in ConfigMaps because "it is internal": the value inherits the wide read audience the ConfigMap was designed for.
- Moving a leaked value instead of rotating it: the credential is placed in a Secret but stays valid for everyone who already read it.
- Treating a Secret as encrypted: base64 manifests committed to Git, as on the Exposed Kubernetes Secrets 2023 page, or etcd left on the
identityprovider. - Encryption turned on, old data left plaintext: objects written before the change stay unencrypted, and so do backups taken earlier.
- Broad edit and pod creation rights: users who cannot read Secrets can still mount them in a pod.
- Verbose audit of ConfigMaps: Request or RequestResponse level on namespaces where ConfigMaps contain credentials writes the values into the log pipeline.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| MITRE ATT&CK | T1552.007 Container API | Reading credentials through the Kubernetes API, including from ConfigMaps and Secrets |
| MITRE ATT&CK | T1552.001 Credentials In Files | Credentials in manifests, mounted property files and Helm values |
| MITRE ATT&CK | T1613 Container and Resource Discovery | Enumerating cluster objects, the step before harvesting values |
| NIST SP 800-53 Rev 5 | IA-5(7) No Embedded Unencrypted Static Authenticators | Credentials in configuration objects and manifests |
| NIST SP 800-53 Rev 5 | SC-28(1) Cryptographic Protection; AC-6 Least Privilege; CM-6 Configuration Settings; AU-12 Audit Record Generation | Encryption at rest, RBAC on Secrets, controlled configuration, audit of reads |
| NIST CSF 2.0 | PR.DS-01; PR.AA-05; PR.PS-01 | Data at rest, least-privilege access, configuration management |
| ISO/IEC 27001:2022 Annex A | A.5.17 Authentication information; A.8.9 Configuration management; A.8.24 Use of cryptography | Handling of credentials, configuration control and encryption |
Practitioner checklist
- Write a one-line placement rule for your platform: anything that grants access is never in a ConfigMap.
- Verify encryption at rest for Secrets on every cluster and confirm existing objects were rewritten.
- Scan all live ConfigMaps and rendered manifests for credentials; rotate every real finding before moving it.
- Deploy an admission policy against credential-like ConfigMap content, in warn mode first, then deny.
- Remove human
listandwatchon Secrets and revieweditand pod creation bindings. - Mount Secrets as files, only into the container that needs them, rather than as environment variables.
- Keep Secrets and ConfigMaps at Metadata level in the audit policy.
- Replace stored cloud keys with workload identity wherever the provider supports it.
- Use immutable ConfigMaps and Secrets with versioned names for static values.
Standards and references
- Kubernetes: ConfigMaps
- Kubernetes: Secrets
- Kubernetes: Good practices for Kubernetes Secrets
- Kubernetes: Encrypting Confidential Data at Rest
- Kubernetes: Using RBAC Authorization
- Kubernetes: Auditing
- Kubernetes: Validating Admission Policy
- MITRE ATT&CK: T1552.007 Unsecured Credentials: Container API
Related NHI Mgmt Group resources: Kubernetes Secrets Security Guide · Kubernetes Secrets vs External Secret Stores · Environment Variables vs Secrets Manager · Kubernetes NHI Security Guide · How to migrate Kubernetes secrets to an external Secrets Manager