As Kubernetes adoption continues to grow across organizations of all sizes, securing containerized workloads has become a critical concern. Pod Security Policies (PSPs) and their successor, Pod Security Admission, represent Kubernetes’ native approach to enforcing security best practices at the pod level. By controlling the security-sensitive aspects of pod specifications, these mechanisms help prevent privilege escalation and limit the potential damage from container-based attacks.
This comprehensive guide explores how to implement effective pod security controls in Kubernetes, covering both the legacy Pod Security Policies and the newer Pod Security Standards and Admission Controller. You’ll learn practical strategies for balancing security with operational requirements, implementing defense in depth, and addressing common security challenges in Kubernetes environments.
Understanding Pod Security in Kubernetes
Before diving into implementation details, it’s essential to understand the security risks that pod security controls address and how they fit into the broader Kubernetes security landscape.
The Container Security Challenge
Containers introduce unique security challenges:
- Shared kernel: All containers on a node share the host’s kernel
- Default privileges: Containers may have more privileges than needed
- Resource contention: Uncontrolled resource usage can lead to DoS conditions
- Escape risks: Container breakouts can compromise the host and other containers
- Weak isolation: Containers provide weaker isolation than VMs
Pod Security Controls Evolution
Kubernetes’ approach to pod-level security has evolved:
Pod Security Policies (PSPs)
- Introduced in Kubernetes 1.3
- Cluster-level resources that control security-sensitive aspects of pod specifications
- Deprecated in Kubernetes 1.21
- Removed in Kubernetes 1.25
Pod Security Standards
- Introduced in Kubernetes 1.22
- Three predefined security levels: Privileged, Baseline, and Restricted
- Documented set of controls rather than a specific implementation
Pod Security Admission
- Built-in admission controller in Kubernetes 1.23+
- Enforces Pod Security Standards
- Replaces PSPs as the recommended approach
Security Contexts and Their Importance
At the core of pod security are security contexts, which define privilege and access control settings for pods and containers:
# Example of security context at pod and container level
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: secure-container
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
Security contexts control:
- User and group IDs
- Privilege escalation
- Linux capabilities
- SELinux context
- AppArmor and Seccomp profiles
- File system permissions
Pod Security Policies (Legacy Approach)
While PSPs are now deprecated, understanding them is valuable for maintaining legacy clusters and grasping the evolution of Kubernetes security.
PSP Core Concepts
A Pod Security Policy is a cluster-level resource that controls security-sensitive aspects of pod specifications:
# Example of a restrictive PSP
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
readOnlyRootFilesystem: true
PSP Authorization
For PSPs to work, they must be:
- Created as resources in the cluster
- Authorized for use by specific users, groups, or service accounts
# RBAC configuration to authorize PSP usage
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: use-restricted-psp
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['restricted']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developers-use-restricted-psp
namespace: development
roleRef:
kind: ClusterRole
name: use-restricted-psp
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
Common PSP Strategies
Organizations typically implemented PSPs using one of these strategies:
1. Tiered Approach
- Multiple PSPs with varying levels of restriction
- Different PSPs for different types of workloads
- RBAC controls which users/service accounts can use which PSPs
2. Default-Deny with Exceptions
- Highly restrictive default PSP
- Specific exceptions for system components and special workloads
- Requires careful RBAC configuration
3. Graduated Implementation
- Start in audit mode to understand impact
- Gradually increase restrictions
- Implement in non-production environments first
Pod Security Standards and Admission
The modern approach to pod security in Kubernetes uses Pod Security Standards enforced through the built-in Pod Security Admission controller.
Pod Security Standards
The Pod Security Standards define three security levels:
1. Privileged
- Unrestricted policy
- Provides the same level of access as the host
- Typically used for infrastructure and system pods
2. Baseline
- Minimally restrictive policy
- Prevents known privilege escalations
- Default for untrusted workloads
3. Restricted
- Heavily restricted policy
- Follows security best practices
- Suitable for high-security environments
Key Controls by Security Level
Control | Privileged | Baseline | Restricted |
---|---|---|---|
Host Namespaces | Allowed | Disallowed | Disallowed |
Privileged Containers | Allowed | Disallowed | Disallowed |
Capabilities | Allowed | Some restricted | Minimal set |
Volume Types | All | Non-host path | Restricted set |
HostPath Volumes | Allowed | Disallowed | Disallowed |
Run as Non-root | Not required | Not required | Required |
Run as User | Any | Any | Must be specified |
Seccomp | Any | Any | Required |
AppArmor | Any | Any | Required |
Implementing Pod Security Admission
The Pod Security Admission controller is enabled by default in Kubernetes 1.23+ and can be configured at the namespace level:
# Namespace with Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: secure-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
The controller supports three modes:
- enforce: Violations will cause the pod to be rejected
- audit: Violations will be recorded in the audit log
- warn: Violations will trigger a user-facing warning
Migrating from PSPs to Pod Security Admission
For organizations transitioning from PSPs, follow these steps:
Audit Current PSPs
- Identify all PSPs in use
- Map PSPs to Pod Security Standards levels
- Identify custom controls not covered by standards
Enable Parallel Operation
- Enable Pod Security Admission in warn/audit mode
- Keep PSPs active during transition
- Monitor for policy violations
Update Namespace Configuration
- Apply appropriate security levels to namespaces
- Start with non-production namespaces
- Gradually increase enforcement levels
Remove PSPs
- Once all namespaces are properly configured
- After confirming no unexpected rejections
- Before upgrading to Kubernetes 1.25+
Example Migration Script:
#!/bin/bash
# Script to help migrate from PSPs to Pod Security Standards
# Get all namespaces
namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}')
# For each namespace, check if pods would violate restricted policy
for ns in $namespaces; do
echo "Analyzing namespace: $ns"
# Get all pods in namespace
pods=$(kubectl get pods -n $ns -o jsonpath='{.items[*].metadata.name}')
for pod in $pods; do
# Check pod against restricted policy
violations=$(kubectl get pod $pod -n $ns -o yaml | kube-pss-validator restricted)
if [ -n "$violations" ]; then
echo "Pod $pod in namespace $ns violates restricted policy:"
echo "$violations"
echo "Recommended policy level: baseline"
else
echo "Pod $pod in namespace $ns complies with restricted policy"
fi
done
echo "-----------------------------------"
done
Best Practices for Pod Security
Regardless of whether you’re using PSPs or Pod Security Admission, these best practices will enhance your Kubernetes security posture:
1. Implement the Principle of Least Privilege
Configure pods with the minimum privileges needed to function:
# Example of least privilege pod configuration
apiVersion: v1
kind: Pod
metadata:
name: least-privilege-pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: app
image: myapp:1.0
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
Key principles:
- Run as non-root user
- Drop unnecessary capabilities
- Use read-only root filesystem where possible
- Set resource limits to prevent DoS
2. Use Namespace Isolation
Leverage namespaces for security boundaries:
# Network policy for namespace isolation
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: secure-workloads
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-specific-traffic
namespace: secure-workloads
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 80
Best practices:
- Use separate namespaces for different security levels
- Apply network policies to restrict inter-namespace communication
- Configure resource quotas per namespace
- Apply appropriate Pod Security Standards to each namespace
3. Implement Defense in Depth
Don’t rely solely on pod security controls:
Container Image Security
- Use minimal base images
- Scan images for vulnerabilities
- Implement image signing and verification
Runtime Security
- Deploy runtime security monitoring
- Use Seccomp and AppArmor profiles
- Implement audit logging
Network Security
- Implement network policies
- Use service meshes for encryption
- Restrict egress traffic
RBAC
- Implement least privilege for users and service accounts
- Regularly audit RBAC permissions
- Use groups for role assignments
4. Automate Security Validation
Implement automated checks in your CI/CD pipeline:
# Example GitHub Actions workflow for Kubernetes security scanning
name: Kubernetes Security Scan
on:
pull_request:
paths:
- 'k8s/**'
jobs:
kubesec-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Scan Kubernetes manifests with kubesec
uses: controlplaneio/kubesec-action@master
with:
input: k8s/deployment.yaml
format: json
exit-code: "1"
- name: Scan with Conftest
uses: instrumenta/conftest-action@master
with:
files: k8s/
policy: policy/kubernetes/
Useful tools:
- KubeSec
- Conftest
- Kube-bench
- Kube-hunter
- Trivy
5. Monitor and Respond to Security Events
Implement comprehensive monitoring:
# Falco rule for detecting privilege escalation
- rule: Privilege Escalation
desc: Detect privilege escalation activities
condition: >
evt.type = execve and
proc.name = sudo and
not user.name in (allowed_sudo_users)
output: >
Privilege escalation detected (user=%user.name command=%proc.cmdline)
priority: WARNING
tags: [process, mitre_privilege_escalation]
Key components:
- Runtime security monitoring
- Kubernetes audit logs
- Pod security events
- Automated response to security incidents
Common Pod Security Challenges and Solutions
Challenge 1: Legacy Applications Requiring Privileges
Many legacy applications require root access or special privileges to function.
Solutions:
Containerize strategically
- Separate privileged components into dedicated containers
- Use init containers for privileged operations
Refactor when possible
- Modify applications to run as non-root
- Use volume mounts instead of privileged access
Isolate privileged workloads
- Run on dedicated nodes
- Apply stricter network policies
- Implement additional monitoring
Challenge 2: Third-Party Applications and Helm Charts
Many third-party applications and Helm charts aren’t designed with security best practices.
Solutions:
Patch Helm charts
- Use custom values to override security settings
- Submit upstream improvements
# Example values.yaml override for security podSecurityContext: runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 containerSecurityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true
Use OPA Gatekeeper or Kyverno
- Implement policy enforcement
- Automatically mutate resources
# Kyverno policy to add security context apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: add-security-context spec: rules: - name: add-pod-security-context match: resources: kinds: - Pod mutate: patchStrategicMerge: spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000
Challenge 3: Kubernetes System Components
System components often require privileged access to function.
Solutions:
Use separate security profiles
- Apply different policies to system namespaces
- Create dedicated PSPs for system components
Implement strict RBAC
- Limit who can deploy to system namespaces
- Use service accounts with minimal permissions
Regular auditing
- Monitor system components for unexpected changes
- Implement drift detection
Challenge 4: Developer Experience vs. Security
Strict security policies can impede developer productivity.
Solutions:
Tiered environments
- Less restrictive policies in development
- Gradually increasing restrictions toward production
Self-service security
- Provide secure templates and examples
- Implement automated security checks with clear feedback
Education and tooling
- Train developers on security best practices
- Provide tools to validate manifests locally
# Example developer tool for local validation #!/bin/bash # validate-k8s.sh echo "Validating Kubernetes manifests..." # Check for basic Kubernetes validity kubectl apply --dry-run=client -f $1 # Check security with kubesec kubesec scan $1 # Check against policy with conftest conftest test $1 --policy ./policy
Advanced Pod Security Techniques
For organizations with mature Kubernetes deployments, consider these advanced techniques:
1. Custom Security Profiles with Seccomp and AppArmor
Seccomp and AppArmor provide fine-grained control over system calls and file access:
# Pod with custom seccomp profile
apiVersion: v1
kind: Pod
metadata:
name: audit-pod
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profiles/audit.json"
spec:
containers:
- name: audit-container
image: registry.example/audit-app:v1
Implementation steps:
- Create custom profiles based on application needs
- Test profiles in permissive mode
- Deploy profiles to nodes
- Apply profiles to pods via annotations
2. Runtime Class for Container Isolation
RuntimeClass allows you to use different container runtimes for different workloads:
# Define a RuntimeClass
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: gvisor
---
# Pod using the RuntimeClass
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
runtimeClassName: gvisor
containers:
- name: secure-container
image: registry.example/secure-app:v1
Options include:
- gVisor for enhanced isolation
- Kata Containers for VM-based isolation
- Firecracker for lightweight VM isolation
3. Policy Enforcement with OPA Gatekeeper
OPA Gatekeeper provides flexible policy enforcement:
# OPA Gatekeeper constraint template
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredsecuritycontext
spec:
crd:
spec:
names:
kind: K8sRequiredSecurityContext
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredsecuritycontext
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container %v must set runAsNonRoot to true", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.allowPrivilegeEscalation == false
msg := sprintf("Container %v must set allowPrivilegeEscalation to false", [container.name])
}
---
# Constraint using the template
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: require-security-context
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system"]
Benefits:
- Flexible policy language (Rego)
- Audit and enforcement modes
- Custom error messages
- Extensive library of policies
4. Supply Chain Security
Secure the entire path from development to runtime:
Signed Images
- Implement image signing with Cosign
- Verify signatures before deployment
# Sign an image with Cosign cosign sign --key cosign.key registry.example/myapp:v1.0 # Verify an image signature cosign verify --key cosign.pub registry.example/myapp:v1.0
Software Bill of Materials (SBOM)
- Generate SBOMs for all images
- Scan SBOMs for vulnerabilities
Admission Control
- Verify image provenance
- Enforce build-time security requirements
Conclusion: Building a Pod Security Strategy
Implementing effective pod security in Kubernetes requires a strategic approach:
Assess Your Environment
- Inventory your workloads and their security requirements
- Identify legacy applications with special requirements
- Understand your compliance obligations
Define Your Security Levels
- Map workloads to appropriate security levels
- Document exceptions and their justifications
- Create clear guidelines for developers
Implement Gradually
- Start with audit/warn modes
- Focus on high-risk workloads first
- Provide feedback and remediation guidance
Monitor and Improve
- Regularly review security posture
- Track exceptions and work to eliminate them
- Stay current with evolving best practices
By following these guidelines and implementing pod security controls appropriate for your environment, you can significantly reduce the risk of container-based attacks while maintaining operational efficiency. Remember that pod security is just one layer in a defense-in-depth strategy—combine it with other security controls for comprehensive protection of your Kubernetes environment.
Whether you’re using the legacy PSPs or the newer Pod Security Standards and Admission, the fundamental principles remain the same: apply the principle of least privilege, implement defense in depth, and continuously monitor and improve your security posture.