This guide demonstrates how to configure Kyverno to enforce volume security policies that restrict dangerous volume types and configurations that could compromise container security.
Volume security involves controlling which types of volumes containers can mount and how they can access them. Proper volume security prevents:
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
# 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
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"
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
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
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
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
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
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
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"
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