Securing Persistent Volumes
Required knowledge for the CKS certification.
PersistentVolumes (PVs) can expose sensitive data if improperly configured. Common risks include orphaned volumes retaining data, cross-namespace access through shared volumes, and lack of encryption. Attackers who gain the ability to create pods or PersistentVolumeClaims may access data belonging to other workloads.
This guide covers best practices for securing PersistentVolumes in Kubernetes.
1. Use Delete Reclaim Policy
Issue: The Retain reclaim policy keeps volume data after PVC deletion, allowing future claims to access old data.
Fix: Use Delete reclaim policy to ensure data is removed when PVCs are deleted.
Configure Storage Class with Delete Policy
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: secure-storage
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
parameters:
type: gp3
encrypted: "true"
Update Existing PVs
For existing PVs with Retain policy that need to be cleaned up:
# List PVs with Retain policy
kubectl get pv -o json | jq -r '.items[] |
select(.spec.persistentVolumeReclaimPolicy == "Retain") |
.metadata.name'
# Update to Delete policy (be careful - verify data is backed up)
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
Scrub Released Volumes
If you must use Retain, implement a volume scrubbing process:
# Identify released volumes
kubectl get pv | grep Released
# For each released volume, create a temporary pod to wipe data
kubectl run scrubber --rm -it --image=busybox \
--overrides='{"spec":{"volumes":[{"name":"data","persistentVolumeClaim":{"claimName":"orphaned-pvc"}}],"containers":[{"name":"scrubber","image":"busybox","command":["sh","-c","rm -rf /data/*"],"volumeMounts":[{"name":"data","mountPath":"/data"}]}]}}'
2. Restrict PVC Creation with RBAC
Issue: Users who can create PVCs may bind to PVs containing data from other namespaces.
Fix: Restrict PVC creation to authorized users and namespaces.
Create Restrictive PVC Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pvc-user
namespace: production
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
# Note: create/delete omitted - only admins can manage PVCs
Namespace-Level PVC Quotas
Limit PVC creation in namespaces:
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage-quota
namespace: development
spec:
hard:
persistentvolumeclaims: "5"
requests.storage: "50Gi"
3. Use Namespace-Scoped Storage Classes
Issue: Cluster-wide StorageClasses allow any namespace to provision volumes.
Fix: Use admission control to restrict which namespaces can use specific StorageClasses.
Kyverno Policy to Restrict Storage Classes
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-storage-classes
spec:
validationFailureAction: Enforce
rules:
- name: restrict-premium-storage
match:
any:
- resources:
kinds:
- PersistentVolumeClaim
validate:
message: "Premium storage class is only allowed in production namespace"
deny:
conditions:
all:
- key: "{{ request.object.spec.storageClassName }}"
operator: Equals
value: "premium-ssd"
- key: "{{ request.namespace }}"
operator: NotIn
value: ["production", "database"]
4. Enforce Volume Encryption
Issue: Unencrypted volumes expose data if the underlying storage is compromised.
Fix: Require encryption for all PersistentVolumes.
AWS EBS Encryption
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: encrypted-gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
kmsKeyId: "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
GCP PD Encryption
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: encrypted-pd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
disk-encryption-kms-key: "projects/my-project/locations/us-central1/keyRings/my-ring/cryptoKeys/my-key"
Admission Policy to Require Encryption
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-encrypted-storage
spec:
validationFailureAction: Enforce
rules:
- name: require-encrypted-storage-class
match:
any:
- resources:
kinds:
- PersistentVolumeClaim
validate:
message: "Only encrypted storage classes are allowed"
pattern:
spec:
storageClassName: "*encrypted*"
5. Restrict Access Modes
Issue: ReadWriteMany (RWX) volumes allow multiple pods to access the same data, potentially across namespaces.
Fix: Limit use of RWX volumes and audit their usage.
Admission Policy to Block RWX in Production
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-rwx-volumes
spec:
validationFailureAction: Enforce
rules:
- name: block-rwx-production
match:
any:
- resources:
kinds:
- PersistentVolumeClaim
namespaces:
- production
validate:
message: "ReadWriteMany access mode is not allowed in production"
pattern:
spec:
accessModes:
- "!ReadWriteMany"
Audit Existing RWX Volumes
kubectl get pv -o json | jq -r '.items[] |
select(.spec.accessModes[] | contains("ReadWriteMany")) |
"\(.metadata.name) - \(.spec.claimRef.namespace)/\(.spec.claimRef.name)"'
6. Implement Volume Snapshots Securely
Issue: Volume snapshots may contain sensitive data and be accessible to unauthorized users.
Fix: Restrict snapshot creation and access through RBAC.
Restrict Snapshot Permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: snapshot-reader
namespace: production
rules:
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots"]
verbs: ["get", "list"]
# create/delete restricted to admins
Secure Snapshot Class
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: secure-snapshots
driver: ebs.csi.aws.com
deletionPolicy: Delete
parameters:
encrypted: "true"
7. Monitor and Audit Volume Access
Issue: Unauthorized volume access may go undetected.
Fix: Enable audit logging for PV/PVC operations.
API Server Audit Policy
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["persistentvolumes", "persistentvolumeclaims"]
verbs: ["create", "delete", "patch", "update"]
Falco Rules for Volume Access
- rule: Sensitive Volume Mount
desc: Detect mounts of volumes containing sensitive data
condition: >
container.volume_mounts contains "/secrets" or
container.volume_mounts contains "/credentials"
output: >
Sensitive volume mounted (pod=%k8s.pod.name
ns=%k8s.ns.name mount=%container.volume_mounts)
priority: WARNING
Security Checklist
- Storage classes use
Deletereclaim policy - Volume encryption enabled for all storage classes
- PVC creation restricted through RBAC
- Resource quotas limit storage per namespace
- RWX access mode restricted in production
- Volume snapshot permissions controlled
- Audit logging enabled for PV/PVC operations
- Regular review of released/available PVs
- Process to scrub retained volumes before reuse
References
This article is based on information from the following official sources:
- Persistent Volumes - Kubernetes Documentation
- Storage Classes - Kubernetes Documentation
- Volume Snapshots - Kubernetes Documentation
- Resource Quotas - Kubernetes Documentation