#Volume Security Policy
This guide demonstrates how to configure Kyverno to enforce volume security policies that restrict dangerous volume types and configurations that could compromise container security.
#TOC
#What is Volume Security?
Volume security involves controlling which types of volumes containers can mount and how they can access them. Proper volume security prevents:
- Host filesystem access: Unauthorized access to host directories
- Privilege escalation: Using volumes to gain elevated permissions
- Data exfiltration: Accessing sensitive host data through volume mounts
- Container escape: Breaking out of container isolation via volume access
- Insecure volume types: Using volume types that bypass security controls
#Quick Start
#1. Restrict Volume Types
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-volume-types
annotations:
policies.kyverno.io/title: Restrict Volume Types
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod,Volume
policies.kyverno.io/description: >-
Only allow safe volume types. This policy restricts volumes to configMap, csi,
downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, and secret.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: restrict-volume-types
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Only the following types of volumes may be used: configMap, csi, downwardAPI,
emptyDir, ephemeral, persistentVolumeClaim, projected, and secret.
foreach:
- list: "request.object.spec.volumes || []"
deny:
conditions:
all:
- key: "{{ element.keys(@) }}"
operator: AnyNotIn
value:
- name
- configMap
- csi
- downwardAPI
- emptyDir
- ephemeral
- persistentVolumeClaim
- projected
- secret#2. Test the Policy
# Apply the policy
kubectl apply -f restrict-volume-types.yaml
# Try to create a pod with hostPath volume (should fail)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: host-vol
mountPath: /host
volumes:
- name: host-vol
hostPath:
path: /
EOF
# Create a test ConfigMap first
kubectl create configmap test-config --from-literal=key=value
# Try to create a pod with allowed volume (should work)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-configmap
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: config-vol
mountPath: /config
volumes:
- name: config-vol
configMap:
name: test-config
EOF
# Clean up
kubectl delete pod test-hostpath test-configmap --ignore-not-found
kubectl delete configmap test-config --ignore-not-found#Core Volume Security Policies
#Policy 1: Disallow HostPath Volumes
Prevent containers from mounting host filesystem paths:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path
annotations:
policies.kyverno.io/title: Disallow Host Path
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod,Volume
policies.kyverno.io/description: >-
HostPath volumes let Pods use host directories and volumes in containers.
Using host resources can be used to access shared data or escalate privileges
and should not be allowed.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
HostPath volumes are forbidden. The field spec.volumes[*].hostPath must be unset.
pattern:
spec:
=(volumes):
- X(hostPath): "null"#Policy 2: Restrict HostPath Volumes (Controlled Access)
Allow specific hostPath volumes with read-only access:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-host-path-readonly
annotations:
policies.kyverno.io/title: Restrict Host Path (Read-Only)
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod,Volume
policies.kyverno.io/description: >-
HostPath volumes which are allowed must be read-only and restricted to specific paths.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: host-path-readonly
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.spec.volumes[?hostPath] | length(@) }}"
operator: GreaterThan
value: 0
validate:
message: >-
HostPath volumes must be read-only and limited to allowed paths.
foreach:
- list: "request.object.spec.volumes[?hostPath]"
deny:
conditions:
any:
# Deny if path is not in allowed list
- key: "{{ element.hostPath.path }}"
operator: AnyNotIn
value:
- "/var/log"
- "/var/lib/docker/containers"
- "/proc"
- "/sys"
foreach:
- list: "request.object.spec.containers[].volumeMounts[?name]"
deny:
conditions:
any:
# Deny if volume mount is not read-only
- key: "{{ element.readOnly || false }}"
operator: Equals
value: false#Policy 3: Disallow Privileged Volume Types
Block volume types that can bypass security controls:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-volumes
annotations:
policies.kyverno.io/title: Disallow Privileged Volume Types
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod,Volume
policies.kyverno.io/description: >-
Certain volume types are considered privileged and should not be allowed.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: disallow-privileged-volumes
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Privileged volume types are not allowed: hostPath, gcePersistentDisk,
awsElasticBlockStore, gitRepo, nfs, iscsi, glusterfs, rbd, flexVolume,
cinder, cephFS, flocker, fc, azureFile, azureDisk, vsphereVolume, quobyte,
portworxVolume, scaleIO, storageos.
foreach:
- list: "request.object.spec.volumes || []"
deny:
conditions:
any:
- key: "{{ element.keys(@) }}"
operator: AnyIn
value:
- hostPath
- gcePersistentDisk
- awsElasticBlockStore
- gitRepo
- nfs
- iscsi
- glusterfs
- rbd
- flexVolume
- cinder
- cephFS
- flocker
- fc
- azureFile
- azureDisk
- vsphereVolume
- quobyte
- portworxVolume
- scaleIO
- storageos#Policy 4: Require Read-Only Root Filesystem
Ensure containers use read-only root filesystems:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-readonly-rootfs
annotations:
policies.kyverno.io/title: Require Read-Only Root Filesystem
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
A read-only root file system helps to enforce an immutable infrastructure strategy;
the container only needs to write on the mounted volume that persists the state.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: readonly-rootfs
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Root filesystem must be read-only. Set readOnlyRootFilesystem to true.
foreach:
- list: request.object.spec.[ephemeralContainers, initContainers, containers][]
deny:
conditions:
any:
- key: "{{ element.securityContext.readOnlyRootFilesystem || false }}"
operator: Equals
value: false#Policy 5: Control Volume Mount Permissions
Restrict volume mount permissions and paths:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: control-volume-mounts
annotations:
policies.kyverno.io/title: Control Volume Mount Permissions
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod,Volume
policies.kyverno.io/description: >-
Control where volumes can be mounted and with what permissions.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: restrict-mount-paths
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Volume mounts to sensitive paths are not allowed.
foreach:
- list: request.object.spec.[ephemeralContainers, initContainers, containers][].volumeMounts[]
deny:
conditions:
any:
# Block mounts to sensitive system paths
- key: "{{ element.mountPath }}"
operator: AnyIn
value:
- "/etc"
- "/root"
- "/var/run/docker.sock"
- "/var/lib/kubelet"
- "/var/lib/docker"
- "/usr/bin"
- "/usr/sbin"
- "/sbin"
- "/bin"
- name: require-readonly-sensitive-mounts
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Mounts to /proc and /sys must be read-only.
foreach:
- list: request.object.spec.[ephemeralContainers, initContainers, containers][].volumeMounts[]
preconditions:
any:
- key: "{{ element.mountPath }}"
operator: AnyIn
value:
- "/proc"
- "/sys"
deny:
conditions:
any:
- key: "{{ element.readOnly || false }}"
operator: Equals
value: false#Advanced Scenarios
#Scenario 1: Environment-Specific Volume Policies
Different volume restrictions for different environments:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: environment-volume-security
spec:
validationFailureAction: Enforce
background: true
rules:
# Production: Strict volume controls
- name: production-volume-restrictions
match:
any:
- resources:
kinds:
- Pod
namespaces:
- production
- prod-*
validate:
message: "Production environments allow only secure volume types"
foreach:
- list: "request.object.spec.volumes || []"
deny:
conditions:
all:
- key: "{{ element.keys(@) }}"
operator: AnyNotIn
value:
- name
- configMap
- secret
- persistentVolumeClaim
- emptyDir
# Development: More permissive but still secure
- name: development-volume-restrictions
match:
any:
- resources:
kinds:
- Pod
namespaces:
- development
- dev-*
- staging
validate:
message: "Development environments allow additional volume types"
foreach:
- list: "request.object.spec.volumes || []"
deny:
conditions:
any:
- key: "{{ element.keys(@) }}"
operator: AnyIn
value:
- hostPath # Still block hostPath in dev
- nfs # Block network filesystems#Scenario 2: Application-Specific Volume Policies
Different volume policies for different application types:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: application-volume-policies
spec:
validationFailureAction: Enforce
background: true
rules:
# Database applications: Allow persistent storage
- name: database-volume-policy
match:
any:
- resources:
kinds:
- Pod
selector:
matchLabels:
app.type: database
validate:
message: "Database applications must use persistent volumes"
pattern:
spec:
volumes:
- persistentVolumeClaim: {}
# Web applications: Restrict to safe volumes
- name: web-app-volume-policy
match:
any:
- resources:
kinds:
- Pod
selector:
matchLabels:
app.type: web
validate:
message: "Web applications can only use safe volume types"
foreach:
- list: "request.object.spec.volumes || []"
deny:
conditions:
all:
- key: "{{ element.keys(@) }}"
operator: AnyNotIn
value:
- name
- configMap
- secret
- emptyDir
- projected#Scenario 3: Volume Size and Resource Limits
Control volume sizes and resource usage:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: volume-resource-limits
spec:
validationFailureAction: Enforce
background: true
rules:
- name: limit-emptydir-size
match:
any:
- resources:
kinds:
- Pod
validate:
message: "EmptyDir volumes must have size limits"
foreach:
- list: "request.object.spec.volumes[?emptyDir]"
deny:
conditions:
any:
- key: "{{ element.emptyDir.sizeLimit || '' }}"
operator: Equals
value: ""
- name: limit-emptydir-memory
match:
any:
- resources:
kinds:
- Pod
validate:
message: "EmptyDir memory volumes are not allowed"
foreach:
- list: "request.object.spec.volumes[?emptyDir]"
deny:
conditions:
any:
- key: "{{ element.emptyDir.medium || '' }}"
operator: Equals
value: "Memory"#Testing and Validation
#Test HostPath Volume (Should Fail)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: host-vol
mountPath: /host
volumes:
- name: host-vol
hostPath:
path: /
EOF