This guide demonstrates how to configure Kyverno to enforce proper security contexts for containers, ensuring they run with appropriate security settings and restrictions.
Security context enforcement involves controlling how containers run by setting security-related parameters. Proper security context configuration prevents:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-nonroot
annotations:
policies.kyverno.io/title: Require Run As Non-Root User
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Containers must run as a non-root user. This policy ensures runAsNonRoot is set to true.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: run-as-non-root
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Running as root is not allowed. Either the field spec.securityContext.runAsNonRoot
must be set to true, or the field spec.containers[*].securityContext.runAsNonRoot
must be set to true.
anyPattern:
- spec:
securityContext:
runAsNonRoot: "true"
- spec:
containers:
- securityContext:
runAsNonRoot: "true"# Apply the policy
kubectl apply -f require-run-as-nonroot.yaml
# Try to create a container explicitly running as root (should fail)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-root
spec:
containers:
- name: nginx
image: nginx
securityContext:
runAsUser: 0
runAsNonRoot: false
EOF
# Try to create a container with non-root user (should work)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-nonroot
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: nginx
image: nginx
EOF
# Clean up
kubectl delete pod test-root test-nonroot --ignore-not-foundPrevent containers from escalating privileges:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privilege-escalation
annotations:
policies.kyverno.io/title: Disallow Privilege Escalation
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Privilege escalation, such as via set-user-ID or set-group-ID file mode, should not be allowed.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: privilege-escalation
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Privilege escalation is disallowed. The fields
spec.containers[*].securityContext.allowPrivilegeEscalation,
spec.initContainers[*].securityContext.allowPrivilegeEscalation,
and spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation
must be set to false.
pattern:
spec:
=(ephemeralContainers):
- securityContext:
allowPrivilegeEscalation: "false"
=(initContainers):
- securityContext:
allowPrivilegeEscalation: "false"
containers:
- securityContext:
allowPrivilegeEscalation: "false"Ensure containers run with specific user IDs:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-user-id-range
annotations:
policies.kyverno.io/title: Require User ID Range
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Containers must run with a specific user ID range to prevent privilege escalation.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: user-id-range
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Containers must run with user ID between 1000 and 65535.
deny:
conditions:
any:
# Check pod-level security context
- key: "{{ request.object.spec.securityContext.runAsUser || 0 }}"
operator: LessThan
value: 1000
- key: "{{ request.object.spec.securityContext.runAsUser || 0 }}"
operator: GreaterThan
value: 65535
# Check container-level security contexts
- key: "{{ request.object.spec.containers[?securityContext.runAsUser && (securityContext.runAsUser < `1000` || securityContext.runAsUser > `65535`)] | length(@) }}"
operator: GreaterThan
value: 0Ensure containers run with non-root group IDs:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root-groups
annotations:
policies.kyverno.io/title: Require Non-Root Groups
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Containers should be required to run with a non-root group ID or supplemental groups.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: non-root-groups
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Containers must run with non-root group ID. Either spec.securityContext.runAsGroup
or spec.containers[*].securityContext.runAsGroup must be set and not be 0.
deny:
conditions:
any:
# Check if pod-level runAsGroup is 0
- key: "{{ request.object.spec.securityContext.runAsGroup || 0 }}"
operator: Equals
value: 0
# Check if any container has runAsGroup set to 0
- key: "{{ request.object.spec.containers[?securityContext.runAsGroup == `0`] | length(@) }}"
operator: GreaterThan
value: 0Enforce secure seccomp profiles:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-seccomp-strict
annotations:
policies.kyverno.io/title: Restrict Seccomp (Strict)
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Seccomp profile must be explicitly set to one of the allowed values.
Both the Unconfined profile and the absence of a profile are prohibited.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: seccomp-strict
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Use of custom Seccomp profiles is disallowed. The field
spec.securityContext.seccompProfile.type must be set to RuntimeDefault or Localhost.
anyPattern:
- spec:
securityContext:
seccompProfile:
type: RuntimeDefault
- spec:
securityContext:
seccompProfile:
type: Localhost
- spec:
containers:
- securityContext:
seccompProfile:
type: RuntimeDefault
- spec:
containers:
- securityContext:
seccompProfile:
type: LocalhostEnsure containers drop all capabilities:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-drop-all-capabilities
annotations:
policies.kyverno.io/title: Require Drop ALL Capabilities
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Containers must drop all capabilities and only add back those that are specifically needed.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-drop-all
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Containers must drop ALL capabilities.
foreach:
- list: request.object.spec.[ephemeralContainers, initContainers, containers][]
deny:
conditions:
all:
- key: ALL
operator: AnyNotIn
value: "{{ element.securityContext.capabilities.drop || `[]` }}"Control AppArmor profile usage:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-apparmor-profiles
annotations:
policies.kyverno.io/title: Restrict AppArmor Profiles
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
On supported hosts, the runtime/default AppArmor profile is applied by default.
The baseline policy should prevent overriding or disabling the default AppArmor profile.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: apparmor-profiles
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
AppArmor profile must be set to runtime/default or a custom profile.
Unconfined profiles are not allowed.
pattern:
metadata:
=(annotations):
=(container.apparmor.security.beta.kubernetes.io/*): "!unconfined"Different security requirements for different environments:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: environment-security-contexts
spec:
validationFailureAction: Enforce
background: true
rules:
# Production: Strict security contexts
- name: production-strict-security
match:
any:
- resources:
kinds:
- Pod
namespaces:
- production
- prod-*
validate:
message: "Production environments require strict security contexts"
pattern:
spec:
securityContext:
runAsNonRoot: "true"
runAsUser: "1000-65535"
runAsGroup: "1000-65535"
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: "false"
readOnlyRootFilesystem: "true"
runAsNonRoot: "true"
capabilities:
drop:
- ALL
# Development: Basic security requirements
- name: development-basic-security
match:
any:
- resources:
kinds:
- Pod
namespaces:
- development
- dev-*
- staging
validate:
message: "Development environments require basic security contexts"
pattern:
spec:
containers:
- securityContext:
allowPrivilegeEscalation: "false"
runAsNonRoot: "true"Different security contexts for different application types:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: application-security-contexts
spec:
validationFailureAction: Enforce
background: true
rules:
# Database applications: Specific user/group IDs
- name: database-security-context
match:
any:
- resources:
kinds:
- Pod
selector:
matchLabels:
app.type: database
validate:
message: "Database applications must use specific security contexts"
pattern:
spec:
securityContext:
runAsUser: "999"
runAsGroup: "999"
fsGroup: "999"
containers:
- securityContext:
runAsNonRoot: "true"
readOnlyRootFilesystem: "true"
# Web applications: Standard security context
- name: web-app-security-context
match:
any:
- resources:
kinds:
- Pod
selector:
matchLabels:
app.type: web
validate:
message: "Web applications must use standard security contexts"
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: "true"
allowPrivilegeEscalation: "false"
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEImplement progressive security context requirements:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: graduated-security-contexts
spec:
validationFailureAction: Enforce
background: true
rules:
# Level 1: Basic security (all namespaces)
- name: basic-security-level
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
validate:
message: "All containers must have basic security contexts"
pattern:
spec:
containers:
- securityContext:
allowPrivilegeEscalation: "false"
# Level 2: Enhanced security (sensitive namespaces)
- name: enhanced-security-level
match:
any:
- resources:
kinds:
- Pod
namespaces:
- finance-*
- hr-*
- security-*
validate:
message: "Sensitive namespaces require enhanced security contexts"
pattern:
spec:
securityContext:
runAsNonRoot: "true"
containers:
- securityContext:
readOnlyRootFilesystem: "true"
capabilities:
drop:
- ALL
# Level 3: Maximum security (critical namespaces)
- name: maximum-security-level
match:
any:
- resources:
kinds:
- Pod
namespaces:
- critical-*
- payment-*
validate:
message: "Critical namespaces require maximum security contexts"
pattern:
spec:
securityContext:
runAsNonRoot: "true"
runAsUser: "1000-1999"
runAsGroup: "1000-1999"
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: "false"
readOnlyRootFilesystem: "true"
runAsNonRoot: "true"
capabilities:
drop:
- ALLImplementing label-based namespace security policy injection:
apiVersion: kyverno.io/v1
kind: Policy
metadata:
annotations:
policies.kyverno.io/category: Pod Security Standards
policies.kyverno.io/description: 'Strictly follow the Security Context configuration in the image to automatically add, allowPrivilegeEscalation:
false, capabilities drop ALL, runAsNonRoot: true, seccompProfile: RuntimeDefault'
policies.kyverno.io/minversion: 1.13.0
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/title: Add precise Security Context configuration
creationTimestamp: "2025-06-04T03:26:54Z"
generation: 1
labels:
velero.io/backup-name: app-backup-20250604111354
velero.io/restore-name: app-recovery
name: add-exact-security-context
namespace: test-1
spec:
admission: true
background: true
emitWarning: false
rules:
- match:
any:
- resources:
kinds:
- Pod
context:
- name: namespaceInfo
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'restricted'"
preconditions:
all:
- key: "{{ namespaceInfo }}"
operator: NotEquals
value: "privileged"
mutate:
foreach:
- list: request.object.spec.containers
patchStrategicMerge:
spec:
containers:
- name: '{{ element.name }}'
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
name: add-container-security-context
skipBackgroundRequests: true
- match:
any:
- resources:
kinds:
- Pod
context:
- name: namespaceInfo
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'restricted'"
preconditions:
all:
- key: "{{ namespaceInfo }}"
operator: NotEquals
value: "privileged"
- key: '{{ request.object.spec.initContainers || `[]` | length(@) }}'
operator: GreaterThan
value: 0
mutate:
foreach:
- list: request.object.spec.initContainers
patchStrategicMerge:
spec:
initContainers:
- name: '{{ element.name }}'
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
name: add-init-container-security-context
skipBackgroundRequests: true
- match:
any:
- resources:
kinds:
- Pod
context:
- name: namespaceInfo
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'restricted'"
preconditions:
all:
- key: "{{ namespaceInfo }}"
operator: NotEquals
value: "privileged"
- key: '{{ request.object.spec.ephemeralContainers || `[]` | length(@) }}'
operator: GreaterThan
value: 0
mutate:
foreach:
- list: request.object.spec.ephemeralContainers
patchStrategicMerge:
spec:
ephemeralContainers:
- name: '{{ element.name }}'
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
name: add-ephemeral-container-security-context
skipBackgroundRequests: true
validationFailureAction: AuditIf you do not expect certain namespaces to be injected with security context detection, please add the label for namespace pod-security.kubernetes.io/enforce: privileged
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-root-user
spec:
containers:
- name: test
image: nginx
securityContext:
runAsUser: 0
EOFcat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-privilege-escalation
spec:
containers:
- name: test
image: nginx
securityContext:
allowPrivilegeEscalation: true
EOFcat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-missing-drop-all
spec:
containers:
- name: test
image: nginx
securityContext:
capabilities:
add:
- NET_ADMIN
EOFcat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-secure-context
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: test
image: nginx
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
EOF