Kyverno ISO 27001 Compliance Policies
Implement ISO 27001 and BSI IT-Grundschutz security controls in Kubernetes using Kyverno policies: access control, cryptography, operations security, and audit
π‘ Quick Answer: Map ISO 27001 Annex A controls and BSI IT-Grundschutz requirements to Kyverno policies that enforce encryption, access control, network segmentation, logging, and vulnerability management at the Kubernetes admission level.
The Problem
Organizations need to demonstrate compliance with:
- ISO 27001:2022 Annex A controls (93 controls across 4 themes)
- BSI IT-Grundschutz (German federal security standard)
- Evidence of technical enforcement (not just documentation)
- Continuous compliance (not point-in-time audits)
The Solution
ISO 27001 Control Mapping
ISO 27001 Control Kyverno Policy
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
A.5.15 Access Control β Restrict ServiceAccount tokens
A.5.23 Cloud Service Security β Enforce resource limits
A.8.9 Configuration Mgmt β Require labels/annotations
A.8.10 Information Deletion β Enforce PV reclaim policies
A.8.20 Network Security β Require NetworkPolicies
A.8.24 Cryptography β Enforce TLS on Ingress
A.8.25 Development Security β Image signing verification
A.8.28 Secure Coding β Block privileged containers
A.8.31 Separation of Envs β Namespace isolation
A.8.34 Audit Logging β Require sidecar loggingA.8.24 β Cryptography (Enforce TLS)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: iso27001-a824-enforce-tls
annotations:
policies.kyverno.io/title: "ISO 27001 A.8.24 - Enforce TLS"
policies.kyverno.io/description: "All Ingress must use TLS"
spec:
validationFailureAction: Enforce
rules:
- name: ingress-must-have-tls
match:
any:
- resources:
kinds:
- Ingress
validate:
cel:
expressions:
- expression: "has(object.spec.tls) && object.spec.tls.size() > 0"
message: "[ISO27001-A.8.24] Ingress must configure TLS"
- expression: |
object.spec.tls.all(t, has(t.secretName) && t.secretName.size() > 0)
message: "[ISO27001-A.8.24] TLS must reference a certificate secret"
- name: service-require-https
match:
any:
- resources:
kinds:
- Service
namespaceSelector:
matchExpressions:
- key: compliance/iso27001
operator: Exists
validate:
message: "[ISO27001-A.8.24] Services in compliance namespaces must use HTTPS"
pattern:
metadata:
annotations:
service.beta.kubernetes.io/port_443_no_tls: "!*"A.8.20 β Network Security (Require NetworkPolicy)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: iso27001-a820-network-segmentation
spec:
validationFailureAction: Enforce
rules:
- name: require-network-policy
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
compliance/iso27001: "true"
preconditions:
all:
- key: "{{ request.object.metadata.labels.app || '' }}"
operator: NotEquals
value: ""
validate:
message: "[ISO27001-A.8.20] Namespace must have a NetworkPolicy before deploying Pods"
deny:
conditions:
all:
- key: "{{ request.namespace }}"
operator: AnyNotIn
value: "{{ request.object.metadata.namespace | networkpolicies(@) }}"A.5.15 β Access Control (ServiceAccount Restrictions)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: iso27001-a515-access-control
spec:
validationFailureAction: Enforce
rules:
- name: restrict-automount-token
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: |
has(object.spec.automountServiceAccountToken) &&
object.spec.automountServiceAccountToken == false
message: "[ISO27001-A.5.15] automountServiceAccountToken must be false"
- name: no-default-service-account
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: |
!has(object.spec.serviceAccountName) ||
object.spec.serviceAccountName != 'default'
message: "[ISO27001-A.5.15] Must use a dedicated ServiceAccount, not 'default'"A.8.28 β Secure Coding (Pod Security)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: iso27001-a828-pod-security
spec:
validationFailureAction: Enforce
rules:
- name: restrict-containers
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: |
object.spec.containers.all(c,
has(c.securityContext) &&
has(c.securityContext.runAsNonRoot) &&
c.securityContext.runAsNonRoot == true
)
message: "[ISO27001-A.8.28] Containers must run as non-root"
- expression: |
object.spec.containers.all(c,
has(c.securityContext) &&
has(c.securityContext.readOnlyRootFilesystem) &&
c.securityContext.readOnlyRootFilesystem == true
)
message: "[ISO27001-A.8.28] Root filesystem must be read-only"
- expression: |
!has(object.spec.hostPID) || object.spec.hostPID == false
message: "[ISO27001-A.8.28] hostPID is not allowed"BSI IT-Grundschutz Module: Container (SYS.1.6)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: bsi-sys16-container-hardening
annotations:
policies.kyverno.io/title: "BSI SYS.1.6 Container Hardening"
spec:
validationFailureAction: Enforce
rules:
- name: require-resource-limits
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: |
object.spec.containers.all(c,
has(c.resources) &&
has(c.resources.limits) &&
has(c.resources.limits.memory) &&
has(c.resources.limits.cpu)
)
message: "[BSI-SYS.1.6] All containers must have CPU and memory limits"
- name: require-liveness-probe
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
selector:
matchLabels:
batch.kubernetes.io/job-name: "?*"
validate:
cel:
expressions:
- expression: |
object.spec.containers.all(c,
has(c.livenessProbe)
)
message: "[BSI-SYS.1.6] Long-running containers must have liveness probes"Compliance Reporting
# View policy violations
kubectl get policyreport -A -o wide
# Export compliance report
kubectl get policyreport -A -o json | jq '
.items[] |
{
namespace: .metadata.namespace,
pass: .summary.pass,
fail: .summary.fail,
warn: .summary.warn
}
'
# Generate ISO 27001 evidence report
kubectl get cpol -o json | jq '.items[] |
select(.metadata.annotations["policies.kyverno.io/title"] | startswith("ISO")) |
{
control: .metadata.annotations["policies.kyverno.io/title"],
action: .spec.validationFailureAction,
rules: [.spec.rules[].name]
}
'Common Issues
Policies block system workloads
- Cause: Kyverno enforces on all Pods including kube-system
- Fix: Add
excludefor system namespaces or use namespaceSelector
Compliance drift after policy update
- Cause: Existing non-compliant resources not re-evaluated
- Fix: Enable background scanning; use
kubectl get policyreportto find violations
Too many policy violations overwhelm team
- Cause: Enforcing everything at once
- Fix: Start with Audit; fix violations namespace by namespace; then Enforce
Best Practices
- Map controls to policies β document which ISO control each policy implements
- Use annotations for audit trail (
policies.kyverno.io/title) - Namespace-scoped rollout β label namespaces
compliance/iso27001: true - Background scanning β catch pre-existing violations
- Policy Reports as evidence β export for auditors
- Exception process β PolicyException CRD for approved deviations
Key Takeaways
- Kyverno can enforce 20+ ISO 27001 Annex A controls at admission time
- CEL expressions provide type-safe compliance validation
- BSI IT-Grundschutz SYS.1.6 maps directly to Pod security policies
- PolicyReports provide continuous compliance evidence for auditors
- Namespace labels (
compliance/iso27001: true) enable gradual rollout - Exception CRD allows documented, approved deviations
- Audit β Fix β Enforce pattern prevents production disruption

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
