本指南演示如何配置 Kyverno 以强制执行卷安全策略,限制可能危及容器安全的危险卷类型和配置。
卷安全涉及控制容器可以挂载的卷类型及其访问方式。适当的卷安全可以防止:
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
# 应用策略
kubectl apply -f restrict-volume-types.yaml
# 尝试创建带有 hostPath 卷的 Pod(应失败)
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
# 先创建测试 ConfigMap
kubectl create configmap test-config --from-literal=key=value
# 尝试创建允许的卷类型 Pod(应成功)
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
# 清理资源
kubectl delete pod test-hostpath test-configmap --ignore-not-found
kubectl delete configmap test-config --ignore-not-found
防止容器挂载主机文件系统路径:
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"
允许特定的 HostPath 卷且必须为只读:
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:
# 如果路径不在允许列表则拒绝
- 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:
# 如果卷挂载不是只读则拒绝
- key: "{{ element.readOnly || false }}"
operator: Equals
value: false
阻止绕过安全控制的特权卷类型:
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
确保容器使用只读根文件系统:
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
限制卷挂载的权限和路径:
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:
# 阻止挂载到敏感系统路径
- 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
针对不同环境设置不同的卷限制:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: environment-volume-security
spec:
validationFailureAction: Enforce
background: true
rules:
# 生产环境:严格的卷控制
- 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
# 开发环境:更宽松但仍安全
- 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 # 开发环境仍禁止 hostPath
- nfs # 禁止网络文件系统
针对不同应用类型设置不同的卷策略:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: application-volume-policies
spec:
validationFailureAction: Enforce
background: true
rules:
# 数据库应用:允许持久存储
- 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 应用:限制为安全的卷类型
- 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
控制卷大小和资源使用:
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