The KCSA curriculum lists eleven items under Kubernetes Cluster Component Security: API server, controller manager, scheduler, kubelet, container runtime, kube-proxy, Pod, etcd, container networking, client security and storage. Most study material covers the first ten and waves at the eleventh. That is a mistake, because storage is where several of the exam’s other domains collide: a hostPath volume is the single most common container-escape primitive in the Threat Model domain, the Pod Security Standards in Security Fundamentals are largely a list of volume rules, and CSI drivers are the most privileged third-party software most clusters run.
Our KCSA cluster component security guide covers the API server, etcd, kubelet and the rest of the component map. This article is the storage chapter that guide does not have. It explains what each volume type can reach, why some of them are dangerous, how the storage control plane is wired and what privileges it holds, how persistent data can leak across namespaces, and how every one of those risks maps to a control the exam expects you to name. The mechanics of PersistentVolumes, claims and StorageClasses are assumed; if you need them, read the CKA storage guide first.
The Storage Trust Map
Before the risks, the actors. Kubernetes storage is not one component but a small distributed system, and every arrow in it is a trust boundary.
| Actor | Where it runs | What it can do | Why it matters for security |
|---|---|---|---|
| kubelet | every node | mounts volumes into Pod sandboxes, calls CSI node plugins over a UNIX socket under /var/lib/kubelet/plugins/, fetches Secrets and ConfigMaps for Pods bound to its node | its credentials and its socket are both reachable through a bad hostPath |
| CSI node plugin | DaemonSet on every node, privileged | formats, mounts and unmounts block devices and filesystems on the host | a compromised node plugin is a compromised node |
| CSI controller plugin | Deployment, usually in a system namespace | talks to the storage backend with cloud or array credentials; creates and deletes volumes and snapshots | holds the most valuable storage credentials in the cluster |
| external-provisioner / attacher / resizer / snapshotter | sidecars of the controller plugin | watch PVCs, StorageClasses and VolumeAttachments; create PVs; read provisioning Secrets | cluster-wide RBAC on PersistentVolumes and VolumeAttachments |
| API objects | etcd | PersistentVolume, StorageClass, CSIDriver, CSINode, VolumeAttachment and VolumeSnapshotContent are cluster-scoped; PersistentVolumeClaim and VolumeSnapshot are namespaced | whoever can create cluster-scoped storage objects can reach into every namespace |
Two facts from that table drive most of the exam questions:
- PersistentVolumes are cluster-scoped. A PV is not “in” a namespace; a claim from any namespace can bind to it. That is convenient for admins and dangerous for tenants, as the section on data lifecycle shows.
- CSI node plugins are privileged by design. They have to be, because mounting on the host requires it. The consequence is that your storage vendor’s DaemonSet is inside your trusted computing base whether you like it or not.
hostPath: The Volume Type the Documentation Tells You Not to Use
The Kubernetes volumes documentation opens the hostPath section with an unusually blunt warning: using it “presents many security risks”, and if you can avoid it, you should, by defining a local PersistentVolume instead. The KCSA expects you to know why.
A hostPath volume mounts a path from the node’s filesystem into the container. There is no indirection and no filtering: whatever the kubelet can see at that path, the container sees too. The interesting question is what lives on a node.
| Host path | What a container that mounts it gets | Severity |
|---|---|---|
/var/lib/kubelet/pods/ | the projected volumes of every Pod on the node: their Secrets, ConfigMaps and bound service account tokens | critical: lateral movement into every namespace scheduled on the node |
/var/lib/kubelet/pki/ and /etc/kubernetes/kubelet.conf | the kubelet’s client certificate and kubeconfig | critical: impersonate the node to the API server |
/etc/kubernetes/pki/ (control-plane nodes) | the cluster CA key, API server and etcd certificates | critical: mint any client certificate, read etcd |
/run/containerd/containerd.sock or /var/run/crio/crio.sock | the container runtime API | critical: start a privileged container on the host, read any container’s filesystem |
/etc/, /root/, /home/ | node configuration, SSH keys, admin kubeconfigs | high |
/var/log/ | system and container logs | medium: credential leakage through logs |
/dev/ | block devices, including the root disk | critical with read-write access |
The canonical escape needs none of these specifically. A Pod that mounts / from the host and runs as root can chroot into it and simply be the node. That is the chain the KCSA threat model guide describes under privilege escalation, and it is why the Baseline Pod Security Standard forbids hostPath outright rather than restricting it.
Read-only is not optional
The documentation adds a subtler point that trips up candidates. If you use admission control to restrict hostPath to specific directories, the restriction “is effective only if you also require that every mount of that hostPath volume is read only”. A Pod with a read-write mount of any host path may be able to subvert the host mount and reach beyond the directory it was given. Two consequences:
- An allowlist of host paths without a read-only requirement is not a security control.
- The
readOnlyflag lives on the volumeMount, not the volume. A policy that checksspec.volumes[*].hostPath.pathbut notspec.containers[*].volumeMounts[*].readOnlyis incomplete.
The type field
hostPath.type tells the kubelet what to check before mounting. The default, an empty string, performs no check at all for backward compatibility, which is why real manifests should always set it.
type | Behaviour |
|---|---|
"" (default) | no checks before mounting |
DirectoryOrCreate | creates an empty directory with mode 0755, owned by the kubelet’s user and group, if nothing exists |
Directory | a directory must already exist |
FileOrCreate | creates an empty file with mode 0644 if nothing exists; does not create the parent directory, so the Pod fails to start if the parent is missing |
File | a file must already exist |
Socket | a UNIX socket must exist (this is how you knowingly mount a runtime socket) |
CharDevice, BlockDevice | a character or block device must exist (Linux nodes only) |
From a security standpoint the *OrCreate variants matter because they let a Pod create files and directories on the host as the kubelet’s user, which on almost every node is root.
The legitimate uses
The documentation names two: a log shipper that reads /var/log through a read-only mount, and making a host configuration file available read-only to a static Pod, because static Pods cannot read ConfigMaps. Add CNI and CSI node plugins, node exporters and runtime-security agents, and you have the complete list of workloads that should ever carry a hostPath. Every one of them belongs in a namespace that is explicitly exempted from or set to the privileged Pod Security level, and nowhere else.
hostPath hidden inside a PersistentVolume
Here is the nuance that separates a pass from a strong pass. Pod Security Admission checks the Pod spec. A Pod whose only volume is a persistentVolumeClaim passes the Baseline hostPath check even if the PV behind that claim is a hostPath PV. The admission controller never follows the claim to the volume. Two things follow:
- A tenant who can create PersistentVolumes can give any namespace host filesystem access regardless of its Pod Security level. PV creation must be restricted to administrators. PVs are cluster-scoped, so the RBAC rule is a ClusterRole, and the verbs to guard are
create,updateandpatchonpersistentvolumes. - StorageClasses that point at a provisioner capable of creating host-backed volumes deserve the same scrutiny.
A local PersistentVolume is the documentation’s recommended alternative to hostPath not because the data is any less on the host, but because the PV object is created by an administrator, carries nodeAffinity the scheduler honours, and keeps the workload author out of the decision entirely.
subPath, subPathExpr and the Symlink CVEs
volumeMounts[*].subPath mounts a subdirectory of a volume instead of its root, so one volume can serve several containers. subPathExpr builds that path from Downward API environment variables, with the documentation’s own example mounting the host directory /var/log/pods/$(POD_NAME) through a hostPath volume. Both features have a security history worth knowing.
- CVE-2017-1002101 (fixed in 1.7.14, 1.8.9 and 1.9.4): a container could create a symlink inside a volume, and a
subPathmount following that symlink gave the container arbitrary access to the host filesystem, even from a volume type as innocuous asemptyDir. - CVE-2021-25741 (fixed in 1.19.15, 1.20.11, 1.21.5 and 1.22.2): a symlink-exchange race in the hardened
subPathhandling reopened the same class of escape.
The kubelet now resolves subPath targets safely, but the lesson is the one the exam likes: the volume plugin runs on the host as root, so bugs in it are host escapes, and patching the kubelet is a storage security control. It is also a reminder that subPath mounts do not receive updates when the underlying Secret or ConfigMap changes, which matters for rotation later in this article.
Mount Propagation and Why CSI Node Plugins Are Privileged
volumeMounts[*].mountPropagation controls whether mounts made after the container starts are shared between host and container.
| Mode | Equivalent | What it means |
|---|---|---|
None (default) | rprivate | the container sees no later host mounts under the volume, and its own mounts are invisible to the host |
HostToContainer | rslave | the container sees mounts the host (or a Bidirectional container) makes under the volume |
Bidirectional | rshared | mounts the container creates propagate back to the host and to every other container using the volume |
The documentation is explicit that Bidirectional “can damage the host operating system” and is therefore allowed only in privileged containers. That single rule explains the shape of every CSI deployment: the node plugin must create mounts under /var/lib/kubelet/pods/... that the kubelet and the Pod’s containers can see, so it mounts /var/lib/kubelet from the host with mountPropagation: Bidirectional, which forces privileged: true. Add the hostPath mounts for the plugin registration directory and the device tree, and the CSI node DaemonSet ends up holding exactly the privileges this article has spent two sections warning about.
The security consequences you should be able to state:
- Vendor trust is cluster trust. A vulnerability in a CSI node plugin, or a tampered image of one, is node compromise on every node in the cluster. Image signing and verification, which the KCSA platform security guide covers, apply to storage drivers before almost anything else.
- Namespace isolation. CSI components belong in a dedicated namespace with the
privilegedPod Security level, strict RBAC on who can deploy there, and no application workloads alongside them. - Mount propagation is a Baseline-safe field only because privileged is not.
Bidirectionalon an unprivileged container is rejected by the kubelet, not by admission; a policy engine should still reject it early so the failure is visible.
What a CSI Driver Is Allowed to Know: The CSIDriver Object
Each CSI driver registers a cluster-scoped CSIDriver object that tells the kubelet how to treat it. Several of its fields are security decisions.
| Field | Default | What it does | Security relevance |
|---|---|---|---|
attachRequired | true | whether the kubelet waits for a VolumeAttachment before mounting | drivers that skip attach cut the controller out of the loop; fine for local drivers, suspicious for shared storage |
podInfoOnMount | false | passes csi.storage.k8s.io/pod.name, pod.namespace, pod.uid, serviceAccount.name and ephemeral to the driver on every mount | lets the driver make per-Pod authorization decisions; also means the driver sees which workloads use it |
tokenRequests | unset | the kubelet requests bound service account tokens for the Pod, with the listed audience and optional expirationSeconds, and hands them to the driver | the driver can authenticate as the Pod to an external system; this is how secret-store drivers obtain cloud credentials for the right identity |
requiresRepublish | unset | the kubelet calls NodePublishVolume periodically | needed to refresh short-lived credentials; the driver must update files atomically |
volumeLifecycleModes | Persistent | whether the driver may be used for PVs, for inline ephemeral volumes, or both | only drivers that list Ephemeral can appear directly in a Pod spec |
fsGroupPolicy | ReadWriteOnceWithFSType | whether the kubelet applies fsGroup ownership changes | None means a volume keeps whatever permissions the backend gave it |
seLinuxMount | false | mount each volume with its own SELinux context | setting it true on a driver whose volumes share an export breaks isolation rather than improving it |
The tokenRequests field deserves a second look because it ties storage to the identity topics in our CKS service account token guide. When a driver lists an audience, every mount hands it a token that proves the Pod’s identity, scoped to that audience and expiring on the schedule the driver asked for. That is a far better design than a driver holding one cluster-wide credential, but it also means the driver is a token-handling component, and its logs, metrics and crash dumps become places where tokens can leak.
Inline CSI ephemeral volumes
A Pod can carry a CSI volume directly, without a PVC:
volumes:
- name: certs
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: payments-api
Two gates control whether that is allowed. The driver’s CSIDriver must list Ephemeral in volumeLifecycleModes, and the Pod Security Standards treat csi as an allowed volume type at every level, including Restricted. The second point surprises people. The Restricted profile trusts the cluster administrator to have installed only drivers that are safe for workloads to use inline, because the volumeAttributes map is passed to the driver verbatim and could, with a badly designed driver, name arbitrary backend objects. If you run a driver you would not want an untrusted tenant to call, you need an admission policy that allowlists inline drivers; Pod Security Admission will not do it for you. The ValidatingAdmissionPolicy later in this article shows one way.
Secrets referenced by StorageClasses
CSI drivers that need credentials to provision, attach or mount can read them from Secrets named in StorageClass parameters such as csi.storage.k8s.io/provisioner-secret-name and csi.storage.k8s.io/node-publish-secret-name. Those parameters can use templates like ${pvc.namespace} so that per-tenant credentials are read from the tenant’s namespace. The security points: the provisioner needs get on Secrets in whatever namespaces the templates can resolve to, StorageClass creation must be admin-only because a StorageClass can point at any Secret the provisioner can read, and a per-namespace node-publish-secret is how a shared driver avoids one global credential.
Persistent Data Has a Lifecycle, and Each Stage Leaks Differently
Reclaim policies
A PersistentVolume’s persistentVolumeReclaimPolicy decides what happens when its claim is deleted.
| Policy | Behaviour | Security angle |
|---|---|---|
Delete | the PV object and the backing storage are deleted; default for dynamically provisioned volumes | least data left behind, but a deleted claim is unrecoverable, so backups are the compensating control |
Retain | the PV moves to Released, keeps its claimRef, and the backing data is untouched until an administrator acts | data survives the workload; it must be wiped or re-bound deliberately |
Recycle | deprecated: a basic rm -rf on the volume, supported only by the NFS and hostPath plugins | not a secure erase and not available for CSI volumes; treat it as a wrong answer |
A Released PV cannot be bound by a new claim until someone clears its claimRef. The moment an administrator does that without wiping the data, the PV becomes Available to a claim from any namespace, because PVs are cluster-scoped. A tenant who creates a PVC with a matching storageClassName, access mode and size, or who names the PV directly with spec.volumeName, inherits the previous tenant’s data. The exam phrases this as “sensitive data from a deleted application was found in a new namespace”; the answers are a Delete reclaim policy for multi-tenant classes, a wipe-before-reuse procedure for Retain, and RBAC that keeps PV editing with administrators.
Deletion order and the protection finalizers
Kubernetes adds kubernetes.io/pvc-protection to every PVC and kubernetes.io/pv-protection to every PV. A PVC that is still mounted by a Pod stays in Terminating until the Pod goes away; a PV stays until its claim is gone. These finalizers exist to prevent data loss from racing deletions, and they are why a “stuck Terminating PVC” is almost always a Pod still using it, as our finalizers guide explains.
Until recently there was a gap: if the PV was deleted before the PVC, the Delete reclaim policy was not honoured and the backing storage leaked. HonorPVReclaimPolicy, GA in Kubernetes 1.33, closes it with an extra finalizer (external-provisioner.volume.kubernetes.io/finalizer for CSI volumes, kubernetes.io/pv-controller for in-tree ones) so that Delete is applied regardless of deletion order. From a security viewpoint, “leaked” storage is storage nobody is tracking, which is exactly where stale copies of sensitive data accumulate.
Access modes and single-writer guarantees
ReadWriteOnce is a node-level guarantee: two Pods on the same node can both write. ReadWriteOncePod, GA since Kubernetes 1.29, is the Pod-level guarantee, enforced by the scheduler and the kubelet. When a scenario needs one workload to have exclusive access to a volume, for integrity rather than performance, ReadWriteOncePod is the field being tested.
Snapshots and clones carry the data with them
VolumeSnapshot is namespaced but its VolumeSnapshotContent is cluster-scoped and references real backend data. RBAC on volumesnapshotcontents and on who can restore a snapshot into a new PVC is part of data protection, and an exported snapshot is a copy of everything that was on the volume, including whatever the application wrote in plaintext.
Encryption at rest belongs to the backend
Kubernetes does not encrypt volume contents. Encryption at rest for PVs is a property of the storage backend, usually requested through StorageClass parameters (a cloud disk class with encrypted: "true" and a key reference, for example). Do not confuse it with the EncryptionConfiguration that protects Secrets inside etcd, which our CKS secrets encryption guide covers; the exam offers both as answer options and expects you to pick the one that matches the data in question.
Secrets as Volumes: tmpfs, Modes and the Secrets Store CSI Driver
Native Secret volumes
When a Pod mounts a Secret as a volume, the kubelet writes it to a tmpfs (RAM-backed) filesystem, so it never touches the node’s disk, and mounts it read-only. Three details carry exam weight:
- File modes. The default
defaultModeis0644. Anyone who can read the Pod’s filesystem as another user can read the Secret; set0400or a per-itemmodefor credentials. - Updates. The kubelet refreshes mounted Secrets on its sync period, but a container that mounts a Secret through
subPathnever receives updates. Rotation plans that rely on file watching break silently onsubPath. - Environment variables are worse. They are visible in
/proc/<pid>/environ, inherited by child processes, and routinely dumped into crash reports. Mount, do not inject, and note that the data path from/var/lib/kubelet/pods/back to these files is the reasonhostPathwas the first topic in this article.
The kubelet can only fetch Secrets for Pods bound to its node, thanks to the Node authorizer described in our authorization chain guide. That scoping is what makes a node compromise a node-sized breach rather than a cluster-sized one, and it is exactly the boundary a hostPath to the kubelet’s credentials erases.
emptyDir with medium: Memory is also tmpfs, counts against the container’s memory limit, and is the right place for scratch files you do not want on disk; set sizeLimit so it cannot be used to exhaust node memory, a theme the KCSA denial-of-service guide develops.
Projected volumes and bound tokens
A projected volume combines Secrets, ConfigMaps, Downward API fields and a serviceAccountToken source into one directory. The token source is what makes the modern service account token short-lived and audience-bound (expirationSeconds, audience), and it is the pattern the kubelet uses for the default token mount. The CKS guide linked above owns the details; for the KCSA, know that a projected token is the secure replacement for the legacy long-lived token Secret.
The Secrets Store CSI Driver
The Secrets Store CSI Driver is a Kubernetes SIG project that mounts secrets from an external store (AWS, Azure, GCP, Vault, Akeyless, Conjur, OpenBao providers) into a Pod as a CSI inline ephemeral volume. A SecretProviderClass CRD describes which objects to fetch; the Pod references it through volumeAttributes as shown earlier. Why it belongs in a storage-security article:
- The secret material never has to exist as a Kubernetes Secret in etcd at all. Mounted content lives in the Pod’s volume on the node and disappears with the Pod.
- The driver authenticates the Pod to the external store using the CSIDriver
tokenRequestsandpodInfoOnMountmechanisms above: pod identity in, scoped credential out. - The optional sync as Kubernetes Secret feature (
secretObjectsin theSecretProviderClass) creates a real Secret from the mounted content so it can be consumed as an environment variable or by an Ingress. The documentation notes that the Secret is only created once a Pod mounts the volume and is deleted when the last consuming Pod goes away. The trade-off is that you are back to storing the value in etcd, so etcd encryption at rest becomes relevant again. - Rotation is an alpha feature, off by default, enabled with
--enable-secret-rotation=true. Since v1.6.0 it uses the CSIRequiresRepublishmechanism: the kubelet periodically callsNodePublishVolumeand the driver re-fetches from the provider once the--rotation-poll-interval(default2m) has elapsed. Applications must watch the files; environment variables derived from a synced Secret do not update without a Pod restart. - The driver itself runs as a privileged DaemonSet, like every CSI node plugin, which is one more reason to keep it in a dedicated system namespace.
Mapping Storage Controls to the Pod Security Standards
The Pod Security Standards are where the exam expects you to land most storage questions. The storage-relevant controls are few and precise.
| Control | Baseline | Restricted | Where it is checked |
|---|---|---|---|
hostPath volumes | forbidden (spec.volumes[*].hostPath must be nil) | forbidden | Pod spec only; a hostPath PV behind a PVC is not detected |
| Volume types | any type other than hostPath | only configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, secret | every item in spec.volumes[*] must set exactly one of the allowed fields |
privileged (and therefore Bidirectional propagation) | forbidden | forbidden | container securityContext |
fsGroup, supplementalGroups | unrestricted | unrestricted | covered in our securityContext internals guide |
Three observations make good exam answers:
- The Restricted list contains eight types. As of the v1.37 documentation it does not include
imagevolumes, so an OCI artifact mounted as animagevolume (stable since Kubernetes 1.36, always read-only) is rejected in a Restricted namespace. Watch the release notes for that to change. - Restricted allows
csiandephemeral(generic ephemeral volumes, which are PVC-backed), which is why the inline-driver allowlist discussed earlier has to come from a separate admission policy. - Restricted still allows
persistentVolumeClaim, so the PV-levelhostPathbypass applies at every level. Pod Security Standards constrain Pods; RBAC on PersistentVolumes and StorageClasses constrains the storage behind them. You need both.
Closing the gaps with a ValidatingAdmissionPolicy
Pod Security Admission is deliberately simple. For the two gaps above, a ValidatingAdmissionPolicy written in CEL is the built-in option (the platform security guide covers where it sits among the admission controllers). This example allows hostPath only under /var/log and only when every mount of it is read-only, and allowlists the inline CSI drivers a namespace may use:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: storage-guardrails
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
variables:
- name: containers
expression: >-
object.spec.containers
+ (has(object.spec.initContainers) ? object.spec.initContainers : [])
validations:
- expression: >-
!has(object.spec.volumes) || object.spec.volumes.all(v,
!has(v.hostPath) || (
v.hostPath.path.startsWith("/var/log") &&
variables.containers.all(c, !has(c.volumeMounts) ||
c.volumeMounts.all(m, m.name != v.name || (has(m.readOnly) && m.readOnly)))))
message: "hostPath volumes must be under /var/log and every mount must be readOnly"
- expression: >-
!has(object.spec.volumes) || object.spec.volumes.all(v,
!has(v.csi) || v.csi.driver in ["secrets-store.csi.k8s.io"])
message: "only approved inline CSI drivers may be used"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: storage-guardrails
spec:
policyName: storage-guardrails
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "storage-system"]
The binding excludes the system namespaces where CSI drivers and log shippers legitimately need what the policy forbids. That exclusion list is your trusted computing base; keep it short and review who can deploy into those namespaces.
A Storage Security Checklist You Can Defend
| Risk | Control | Exam keyword |
|---|---|---|
| container escape through host filesystem | Baseline or Restricted PSS; no hostPath outside exempted system namespaces | hostPath, Baseline |
allowed hostPath becomes an escape anyway | require readOnly: true on every mount; prefer local PVs | read-only |
hostPath smuggled through a PV | admin-only RBAC on persistentvolumes and storageclasses | cluster-scoped |
| storage driver compromise | image signing, dedicated privileged namespace, patching | CSI node plugin, Bidirectional |
| untrusted tenants calling arbitrary inline drivers | admission allowlist of csi.driver values | CSI ephemeral volume |
| data left behind after deletion | Delete reclaim for multi-tenant classes, wipe before reusing Retain PVs, HonorPVReclaimPolicy | Released, claimRef |
| two writers corrupting a volume | ReadWriteOncePod | access mode |
| credentials on disk | Secret volumes (tmpfs), defaultMode: 0400, no env vars | tmpfs |
| credentials in etcd at all | Secrets Store CSI Driver without sync | SecretProviderClass |
| secrets in etcd | EncryptionConfiguration with a KMS provider | encryption at rest |
| memory or disk exhaustion through volumes | sizeLimit on emptyDir, ephemeral-storage limits | node-pressure eviction |
How the KCSA Tests Storage
The exam is multiple choice and does not ask you to write a PV. It asks you to recognise which control answers a scenario. Patterns to expect:
| Scenario wording | What is being tested | Best answer |
|---|---|---|
”A developer’s Pod mounts / from the node and the node was compromised” | hostPath as an escape primitive | enforce Baseline PSS; the volume type should never have been admitted |
”A log agent needs /var/log but the security team objects” | legitimate hostPath | read-only mount, dedicated namespace at the privileged level |
| ”Pod Security Admission is set to Restricted but a Pod still reached the host filesystem” | PV-level bypass | the PV was a hostPath PV; restrict PV creation |
”Why does the storage vendor’s DaemonSet require privileged: true?“ | mount propagation | Bidirectional propagation is allowed only in privileged containers |
| ”A new team found another team’s files on a freshly bound volume” | reclaim policy and cluster-scoped PVs | Retain PV reused without wiping; use Delete or a wipe procedure |
| ”Storage was still billed after the application was removed” | deletion-order leak | HonorPVReclaimPolicy (GA 1.33) |
| “Secrets must never be written to the node’s disk” | Secret volume medium | tmpfs-backed Secret volume, not env vars |
| ”Rotated certificate not picked up by the container” | subPath update behaviour | mount the whole Secret without subPath |
| ”Secrets should not be stored in etcd” | external secret stores | Secrets Store CSI Driver without the sync feature |
| ”Which Restricted volume type allows an OCI artifact?“ | the allowlist | none; image is not in the Restricted list |
Common Mistakes
- Treating
localPVs as inherently safe. They remove the workload author from the decision; the data is still on the node and the PV still needs admin-only RBAC. - Allowlisting host paths without requiring read-only mounts. The documentation says this is not an effective restriction. Policies must check
volumeMounts[*].readOnly. - Assuming Pod Security Admission inspects PersistentVolumes. It inspects Pods.
persistentVolumeClaimis allowed everywhere, including Restricted. - Believing Restricted forbids CSI. It allows
csi; inline driver allowlists are your job. - Choosing
Recycleas a secure-wipe answer. It is deprecated, limited to NFS andhostPath, and performs a plain delete. - Confusing volume encryption with etcd encryption.
EncryptionConfigurationprotects Secrets in etcd; PV encryption is a backend property requested through a StorageClass. - Rotating Secrets that are mounted via
subPath. They never update. - Forgetting that a CSI driver is a token handler.
tokenRequestshands it bound service account tokens; its logs and debug endpoints are in scope for secret hygiene.
Frequently Asked Questions
Why is hostPath considered a security risk in Kubernetes?
A hostPath volume gives a container direct access to the node’s filesystem. From there it can read the kubelet’s credentials, the container runtime socket and every other Pod’s Secrets under /var/lib/kubelet/pods/, which turns a compromised container into a compromised node. The Baseline Pod Security Standard forbids hostPath entirely, and the Kubernetes documentation recommends a local PersistentVolume instead.
Is a read-only hostPath mount safe?
It is far safer, and it is the only configuration under which an admission-time allowlist of host paths is meaningful. The documentation states that restricting hostPath to specific directories works only if every mount of that volume is read-only; a read-write mount of any host path may allow the container to subvert the mount. Even read-only, the path should be narrow and the workload should live in a dedicated system namespace.
Why do CSI node plugins run as privileged containers?
They must create mounts on the host that the kubelet and other containers can see, which requires mountPropagation: Bidirectional, and Kubernetes allows Bidirectional only in privileged containers because it can damage the host operating system. The consequence is that every CSI node DaemonSet is part of the cluster’s trusted computing base.
Can a PersistentVolume leak data between namespaces?
Yes. PersistentVolumes are cluster-scoped. A Retain PV whose claimRef is cleared without wiping the data becomes Available to a claim from any namespace, and a PVC can even target it by name with spec.volumeName. Use the Delete reclaim policy for multi-tenant StorageClasses, wipe Retain volumes before reuse, and keep PV editing with administrators.
What does HonorPVReclaimPolicy change?
Before it, deleting a PV before its PVC could skip the Delete reclaim policy and leak the backing storage. HonorPVReclaimPolicy, GA in Kubernetes 1.33, adds a finalizer so the reclaim policy is honoured regardless of deletion order, which stops untracked copies of data from accumulating in the storage backend.
Which volume types does the Restricted Pod Security Standard allow?
Exactly eight: configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected and secret. hostPath is forbidden from Baseline upward, and as of the v1.37 documentation image volumes are not on the Restricted list. Because csi is allowed, restricting which drivers may be used inline requires a separate admission policy.
Are Kubernetes Secrets written to the node’s disk when mounted?
No. Secret volumes are backed by tmpfs, a RAM-backed filesystem, and are mounted read-only. Set a restrictive defaultMode such as 0400, avoid subPath mounts if you rely on rotation, and prefer volumes over environment variables, which leak through /proc and crash dumps.
What is the Secrets Store CSI Driver and how is it different from a Kubernetes Secret?
It mounts secrets from an external store such as a cloud secret manager or Vault directly into a Pod as a CSI inline volume, so the value never has to exist in etcd. A SecretProviderClass describes what to fetch, and the driver uses bound service account tokens to authenticate the Pod to the store. The optional sync feature creates a Kubernetes Secret from the mounted content, which reintroduces etcd storage and its encryption requirements.
Conclusion
Storage is listed as one component in the KCSA’s Cluster Component Security domain, but it behaves like a cross-section of the whole exam. hostPath is the threat model’s favourite escape; read-only mounts, the local PV alternative and the Baseline standard are its controls. CSI node plugins need Bidirectional mount propagation and therefore privilege, so storage drivers are trusted computing base and must be treated as such. PersistentVolumes are cluster-scoped, so reclaim policies and admin-only PV RBAC are tenant-isolation controls, and HonorPVReclaimPolicy stops data from leaking through deletion-order bugs. Secret volumes live on tmpfs, subPath breaks rotation, and the Secrets Store CSI Driver lets you keep secrets out of etcd entirely. Finally, the Pod Security Standards constrain only what is in the Pod spec, which is why PV and StorageClass RBAC, and a small ValidatingAdmissionPolicy, finish the job they start.
If you want to check that these distinctions are automatic under exam conditions, Sailor.sh’s KCSA Certification-Ready Mock Exam Bundle puts storage scenarios alongside the rest of the component security domain across five timed mock exams with explanations, so a wrong answer on reclaim policies or hostPath becomes a fixed mental model rather than a surprise on test day. Pair it with the KCSA exam guide, the KCSA study plan and the free KCSA practice questions to round out your preparation.
Continue with the KCSA cluster component security guide for the other ten components, the Pod Security Standards guide for the admission side of every rule above, and the isolation and multi-tenancy guide for where storage boundaries fit among the others.