In Kubernetes, the storage layer is where a container most easily reaches the node, another tenant's data or the cloud account behind the cluster. Volumes, PersistentVolumes, StorageClasses, CSI drivers and snapshots are configured by different teams, mostly with defaults, and those defaults favour convenience: dynamically provisioned volumes are deleted with their claim, encryption is often an opt-in driver parameter, and a hostPath mount can expose kubelet credentials. This guide covers the storage objects and their trust boundaries, hostPath and volume type policy, CSI driver privilege and credentials, encryption at rest, tenant isolation, snapshots and data lifecycle. It is written for platform engineers, cluster administrators and security architects running multi-team or multi-tenant clusters on cloud or on-premises storage.
Key takeaways
- Forbid hostPath for workloads: the Pod Security Standards Baseline profile already does, and the Restricted profile limits Pods to eight volume types; grant exceptions only to named system namespaces.
- Treat CSI drivers as privileged infrastructure: node plugins run privileged on every node and controller plugins hold cloud or array credentials that can create, attach and delete any volume.
- Encryption is a StorageClass decision: Kubernetes Secrets encryption does not encrypt volumes; set the driver's encryption and customer-managed key parameters in every StorageClass and remove classes that lack them.
- Isolate tenants by StorageClass, quota and RBAC: PersistentVolumes, StorageClasses and VolumeSnapshotContents are cluster-scoped, so only administrators should create them.
- Decide deletion deliberately: the default reclaim policy is Delete, which is good for data minimisation and bad for ransomware recovery; protect production data with Retain plus independent backups.
The storage objects and where trust sits
Kubernetes storage is a chain of API objects and components, each with a different owner and scope. Understanding which are namespaced and which are cluster-scoped is the basis of every access decision in this guide.
| Object or component | Scope | Normally created by | Security relevance |
|---|---|---|---|
| Pod volume (emptyDir, configMap, secret, projected, hostPath) | Pod spec | Application teams | hostPath reaches the node filesystem; Secret and ConfigMap volumes are always mounted read-only |
| PersistentVolumeClaim (PVC) | Namespaced | Application teams | Request for storage; access mode, size and class; subject to namespace quota |
| PersistentVolume (PV) | Cluster | Provisioner or administrator | Points at a real disk, share or LUN; a hand-made PV can point at someone else's data |
| StorageClass | Cluster | Platform team | Holds driver parameters: encryption, key ID, reclaim policy, mount options, credential references |
| CSIDriver and CSI plugins | Cluster, DaemonSet and Deployment | Platform team or managed service | Privileged node plugin on every node; controller plugin with backend credentials |
| VolumeSnapshot / VolumeSnapshotContent / VolumeSnapshotClass | Namespaced / cluster / cluster | Teams / controller / platform team | Copies of data with their own deletion policy |
The PersistentVolume documentation lists four access modes: ReadWriteOnce (RWO), ReadOnlyMany (ROX), ReadWriteMany (RWX) and ReadWriteOncePod (RWOP). RWO restricts a volume to one node, not one Pod, so several Pods on the same node can still mount it. Use ReadWriteOncePod where a single writer is a security or integrity requirement, and treat RWX shared file systems as shared data stores that need their own permission model.
hostPath and volume type policy
The Kubernetes volumes documentation carries an explicit warning: hostPath "presents many security risks" and should be avoided, with a local PersistentVolume as the alternative. It names the risks: host files can expose privileged credentials such as the kubelet's, or privileged APIs such as the container runtime socket, which can be used for container escape (MITRE ATT&CK T1611 Escape to Host). The same page warns that restricting hostPath to certain directories at admission is only effective when the mount is also read-only, because a read-write host mount can be subverted by the Pod.
- Enforce Pod Security Standards: the Baseline profile forbids
spec.volumes[*].hostPath. The Restricted profile goes further and permits only configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected and secret volumes. Label namespaces withpod-security.kubernetes.io/enforce=restrictedwhere workloads allow it. See the Kubernetes Admission Control Guide. - Handle the real exceptions: log collectors, CNI and CSI node plugins and monitoring agents legitimately need host paths. Keep them in dedicated system namespaces, allow-list the exact paths in a policy engine (Kyverno, Gatekeeper or ValidatingAdmissionPolicy), and require
readOnly: truewherever the agent only reads. - Block raw device access:
volumeMode: Blockpresents an unformatted device to the Pod, and privileged containers can read node disks directly (ATT&CK T1006 Direct Volume Access). Restrict privileged Pods and block-mode claims to approved workloads. - Watch subPath: CVE-2021-25741 allowed a symlink exchange through a subPath mount to reach the host filesystem. It was fixed in kubelet 1.19.15, 1.20.11, 1.21.5 and 1.22.2; the advisory's interim mitigation was to disable the VolumeSubpath feature gate and stop less-trusted users running containers as root. Old node pools are where this still matters.
- Generic ephemeral volumes create PVCs: the documentation notes that anyone who can create Pods can create PVCs indirectly through an
ephemeralvolume, even without PVC permissions. Namespace PVC quotas still apply; reject them by admission if that does not fit your model.
CSI drivers: privilege and credentials
The Container Storage Interface moved storage code out of Kubernetes into drivers maintained by storage and cloud vendors. Most in-tree drivers are gone: the volumes documentation records, for example, that the in-tree gcePersistentDisk driver was deprecated in v1.17 and removed in v1.28. A CSI driver has two halves with very different risk.
| Component | Runs as | Holds | Hardening |
|---|---|---|---|
| Node plugin (DaemonSet) | Privileged container on every node with host mounts of the kubelet plugin directories and devices | Ability to mount, format and read any volume attached to the node | Pin images by digest, scan and patch them like node components, restrict who can edit the DaemonSet |
| Controller plugin and sidecars (external-provisioner, external-attacher, external-snapshotter, external-resizer) | Deployment, usually in kube-system or a vendor namespace | Cloud IAM role or storage array credentials that create, attach, snapshot and delete volumes | Workload identity instead of static keys, least-privilege IAM scoped to tagged volumes, network policy to the array API |
| StorageClass secret references | Kubernetes Secrets read by the sidecars | Per-operation credentials | Separate namespace, RBAC limited to the driver service account, encryption at rest for Secrets |
The kubernetes-csi documentation defines StorageClass parameters that point sidecars at Secrets for each operation: csi.storage.k8s.io/provisioner-secret-name, controller-publish-secret-name, node-stage-secret-name, node-publish-secret-name, controller-expand-secret-name and controller-modify-secret-name, each with a matching -namespace key. Values can be templates such as ${pvc.namespace}, and node-publish values can even use PVC annotations. That is powerful and risky: a template that resolves to a namespace the tenant controls lets the tenant choose which credential the driver uses. Prefer fixed secret names in an administrator-owned namespace unless per-tenant credentials are a deliberate design.
The controller's cloud identity is a non-human identity with broad data reach. If it is a static access key in a Secret, anyone who can read that Secret can snapshot and copy every volume in the account (ATT&CK T1552.007 Container API, then T1530 Data from Cloud Storage). In the Tesla Kubernetes cryptojacking incident, an exposed cluster console gave attackers cloud credentials held in the cluster. Use the provider's workload identity for the controller service account. See the Kubernetes NHI Security Guide and the Kubernetes Secrets Security Guide.
Encryption at rest and in transit
Encrypting Secrets in etcd with an EncryptionConfiguration protects API objects only. Volume data is encrypted, or not, by the storage backend, and the CSI driver exposes that choice as StorageClass parameters.
| Driver | Parameter | Behaviour per driver documentation |
|---|---|---|
| AWS EBS CSI | encrypted, kmsKeyId | encrypted defaults to false; without kmsKeyId the region's default AWS-managed key is used. ext4EncryptionSupport only enables the fscrypt feature and does not encrypt files by itself |
| Azure Disk CSI | diskEncryptionSetID, diskEncryptionType | Points the disk at a disk encryption set for customer-managed keys, optionally with platform and customer keys together |
| Google Compute Engine PD CSI | disk-encryption-kms-key | Encrypts new disks with a customer-managed Cloud KMS key |
| On-premises (Ceph RBD, arrays) | Driver specific | Ceph CSI supports per-volume encryption with a key management service; arrays often encrypt drives below the CSI layer. See the Ceph Security guide |
- Encryption on by default in the class, not the account: account-level defaults (such as EBS encryption by default) are a useful backstop, but the StorageClass is what auditors and admission policy can see.
- Customer-managed keys where you need revocation: a key you control lets you crypto-shred a decommissioned tenant and see every decrypt in key logs. Grant the CSI controller identity use of the key, not administration of it. See the Database and Storage Encryption Guide.
- In transit: network file systems (NFS, SMB, CephFS) carry data across the network. Use NFS with Kerberos (krb5p) or TLS where the driver supports it, SMB 3 encryption, and Ceph msgr2 secure mode, and keep storage traffic on a segmented network.
- Application-level encryption for the most sensitive data, so that a node plugin or backup operator with volume access still sees ciphertext.
Tenant isolation
Namespaces do not isolate storage on their own. Four controls do most of the work:
- RBAC on cluster-scoped objects: only the platform team creates PersistentVolumes, StorageClasses, VolumeSnapshotClasses and VolumeSnapshotContents. A user who can create a PV can bind a pre-existing disk or NFS export they should not see. Audit
createandupdateon these resources in every ClusterRole. See the Kubernetes RBAC Guide. - Per-tenant StorageClasses and quota: ResourceQuota supports
requests.storage,persistentvolumeclaimsand per-class limits (<class>.storageclass.storage.k8s.io/requests.storage). Set the quota for privileged classes to zero in namespaces that should not use them, and decide whether the default StorageClass (annotationstorageclass.kubernetes.io/is-default-class) is acceptable for every tenant, since PVCs without a class receive it. - File ownership and labels:
fsGroupmakes Kubernetes change group ownership of volume contents;fsGroupChangePolicy: OnRootMismatchlimits this to when the root does not match. On SELinux nodes, per-Pod MCS labels separate tenants' files;seLinuxChangePolicycontrols whether relabelling is recursive or done at mount. - Backend separation: for hard multi-tenancy, give each tenant a separate storage pool, cloud account or array virtual server with its own credentials, rather than relying on Kubernetes objects alone.
Snapshots, reclaim and data lifecycle
The storage-classes documentation states that reclaimPolicy may be Delete or Retain and defaults to Delete; dynamically provisioned PVs inherit it. Recycle is deprecated. With Retain, deleting the PVC leaves the PV and the underlying disk, and the administrator must clean up by hand. VolumeSnapshotClasses have their own deletionPolicy of Delete or Retain.
| Setting | Security benefit | Risk | Use when |
|---|---|---|---|
| reclaimPolicy Delete | No orphaned disks holding old data | One kubectl delete pvc or namespace deletion destroys data (ATT&CK T1485) | Caches, scratch and rebuildable data |
| reclaimPolicy Retain | Data survives accidental or malicious claim deletion | Released PVs and disks accumulate and keep sensitive data | Production databases and records, with a reclaim runbook |
| Snapshot deletionPolicy Retain | Snapshot survives deletion of the VolumeSnapshot object | Snapshots outlive retention schedules | Recovery points that must outlast the namespace |
| Snapshots in the same account | Fast restore | Same credentials can delete volume and snapshot together (T1490 Inhibit System Recovery) | Never as the only copy |
The finalizers kubernetes.io/pvc-protection and kubernetes.io/pv-protection stop a volume in active use being removed under a running Pod, but they do not protect data once the Pod is gone. Snapshots are not backups unless they are copied out of the blast radius of the CSI controller's credentials. See the Kubernetes Backup and Recovery guide. For decommissioning, delete the backend asset, its snapshots and replicas, and where possible destroy the customer-managed key.
Implementation: a phased rollout
- Inventory (week 1 to 2): list StorageClasses with their parameters, CSI drivers and versions, PVs with reclaim policy and class, VolumeSnapshotClasses, and every Pod using hostPath or block mode. Flag classes without encryption parameters and released PVs.
- Audit-mode policy: label namespaces with
pod-security.kubernetes.io/audit=baselineandwarn=restricted, and run policy-engine rules for hostPath allow-lists and encrypted classes in audit mode. Collect the violations for two to four weeks. - Fix the classes: create encrypted StorageClasses with customer-managed keys, make one the default, and migrate workloads. Existing unencrypted volumes need a snapshot and restore or an application-level copy into an encrypted class.
- Move CSI controllers to workload identity and scope their IAM policies to volumes carrying the cluster's tag. Restrict the namespaces holding driver credentials.
- Enforce in rings: enforce Baseline everywhere, then Restricted in new namespaces, then existing ones; deny unencrypted classes at admission. Roll back by switching a namespace label from
enforcetowarn, which takes effect for new Pods without redeploying. - Lifecycle: set Retain on production classes, add copies of snapshots to a separate account or immutable store, and schedule a monthly review of released PVs and orphaned disks.
Small clusters run by one team can stop at Baseline, encrypted default classes and backups. Large, multi-tenant or regulated platforms need the full set, including per-tenant classes and keys.
Detection and measurement
Kubernetes audit logs record the storage API activity; the cloud or array logs record what the driver did with it. Correlate both. See the Kubernetes Audit Log Detection Guide.
- Alert on: creation or change of PersistentVolumes, StorageClasses, CSIDriver objects and VolumeSnapshotContents by anyone other than the platform pipeline; Pods with hostPath outside allow-listed namespaces; PVCs bound to manually created PVs; mass deletion of PVCs or snapshots; reads of CSI credential Secrets by identities other than the driver.
- Backend signals: snapshot sharing or copying to another account, key disable or schedule-deletion events, volumes attached to instances outside the cluster.
| Metric | Target |
|---|---|
| Percentage of PVs in classes with encryption and a customer-managed key | 100% for production |
| Pods using hostPath outside allow-listed system namespaces | Zero |
| Namespaces enforcing Baseline or Restricted Pod Security | 100% Baseline; rising Restricted share |
| Released PVs and orphaned backend disks older than 30 days | Zero (NHI Mgmt Group recommendation) |
| CSI controllers using static cloud keys | Zero |
| Production volumes with a tested restore from an off-account copy in the last quarter | 100% |
Common failure patterns
- Trusting Secrets encryption for volumes: teams configure etcd encryption and assume the data volumes are covered. They are not; check the StorageClass parameters.
- Default class without encryption: a cluster created years ago keeps an unencrypted default class, and every PVC without a class lands there.
- hostPath for convenience: a team mounts
/var/runor/var/lib/kubeletto debug, leaving a direct route to the runtime socket and kubelet credentials. - Broad PV create rights: developers with a custom ClusterRole that includes persistentvolumes can bind an existing disk or NFS export from another team.
- Static keys in the CSI controller: one Secret read gives snapshot and copy rights over every volume in the cloud account.
- Snapshots as backups: the same identity that can delete volumes can delete their snapshots, so a ransomware actor or a mistaken script removes both.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A | CIS Controls v8.1 |
|---|---|---|---|---|
| Volume encryption and keys | SC-28, SC-28(1), SC-12 | PR.DS-01 | A.8.24 | Control 3 |
| hostPath, volume types, CSI privilege | CM-7, AC-6 | PR.PS-01 | A.8.9 | Control 4 |
| RBAC on storage objects, tenant isolation | AC-6 | PR.AA-05 | A.8.3 | Control 6 |
| Snapshots, reclaim and backups | CP-9 | PR.DS-11 | A.8.13 | Control 11 |
| Decommissioning and deletion | MP-6 | PR.DS-01 | A.8.10 | Control 3 |
Relevant ATT&CK techniques: T1611 Escape to Host, T1006 Direct Volume Access, T1552.007 Container API, T1530 Data from Cloud Storage, T1485 Data Destruction and T1490 Inhibit System Recovery.
Practitioner checklist
- Enforce Pod Security Standards Baseline in every namespace and Restricted where workloads allow.
- Allow-list hostPath per system namespace and path, read-only wherever possible.
- Make every StorageClass set the driver's encryption and customer-managed key parameters, and deny classes that do not.
- Limit create and update on PersistentVolumes, StorageClasses and snapshot classes to the platform pipeline.
- Run CSI controllers with workload identity and IAM scoped to the cluster's volumes.
- Keep CSI credential Secrets in an administrator namespace, with fixed names rather than tenant-controlled templates.
- Set Retain for production data and copy snapshots outside the CSI controller's account.
- Patch CSI driver images and node kubelets on the same cadence as other node components.
- Review released PVs, orphaned disks and snapshots monthly, and crypto-shred decommissioned tenants.
Standards and references
- Kubernetes: Volumes
- Kubernetes: Persistent Volumes
- Kubernetes: Storage Classes
- Kubernetes: Pod Security Standards
- Kubernetes CSI: StorageClass Secrets
- Kubernetes SIGs: AWS EBS CSI Driver CreateVolume Parameters
- Kubernetes: CVE-2021-25741 Symlink Exchange Can Allow Host Filesystem Access
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Kubernetes Security Guide · Kubernetes Secrets Security Guide · Kubernetes Backup and Recovery · Container Runtime Security Guide