Kubernetes Pod Security Policies: Best Practices for Cluster Protection

11 min read 2309 words

Table of Contents

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:

  1. Shared kernel: All containers on a node share the host’s kernel
  2. Default privileges: Containers may have more privileges than needed
  3. Resource contention: Uncontrolled resource usage can lead to DoS conditions
  4. Escape risks: Container breakouts can compromise the host and other containers
  5. 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:

  1. Created as resources in the cluster
  2. 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

ControlPrivilegedBaselineRestricted
Host NamespacesAllowedDisallowedDisallowed
Privileged ContainersAllowedDisallowedDisallowed
CapabilitiesAllowedSome restrictedMinimal set
Volume TypesAllNon-host pathRestricted set
HostPath VolumesAllowedDisallowedDisallowed
Run as Non-rootNot requiredNot requiredRequired
Run as UserAnyAnyMust be specified
SeccompAnyAnyRequired
AppArmorAnyAnyRequired

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:

  1. Audit Current PSPs

    • Identify all PSPs in use
    • Map PSPs to Pod Security Standards levels
    • Identify custom controls not covered by standards
  2. Enable Parallel Operation

    • Enable Pod Security Admission in warn/audit mode
    • Keep PSPs active during transition
    • Monitor for policy violations
  3. Update Namespace Configuration

    • Apply appropriate security levels to namespaces
    • Start with non-production namespaces
    • Gradually increase enforcement levels
  4. 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:

  1. Container Image Security

    • Use minimal base images
    • Scan images for vulnerabilities
    • Implement image signing and verification
  2. Runtime Security

    • Deploy runtime security monitoring
    • Use Seccomp and AppArmor profiles
    • Implement audit logging
  3. Network Security

    • Implement network policies
    • Use service meshes for encryption
    • Restrict egress traffic
  4. 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:

  1. Containerize strategically

    • Separate privileged components into dedicated containers
    • Use init containers for privileged operations
  2. Refactor when possible

    • Modify applications to run as non-root
    • Use volume mounts instead of privileged access
  3. 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:

  1. 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
    
  2. 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:

  1. Use separate security profiles

    • Apply different policies to system namespaces
    • Create dedicated PSPs for system components
  2. Implement strict RBAC

    • Limit who can deploy to system namespaces
    • Use service accounts with minimal permissions
  3. 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:

  1. Tiered environments

    • Less restrictive policies in development
    • Gradually increasing restrictions toward production
  2. Self-service security

    • Provide secure templates and examples
    • Implement automated security checks with clear feedback
  3. 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:

  1. Create custom profiles based on application needs
  2. Test profiles in permissive mode
  3. Deploy profiles to nodes
  4. 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:

  1. 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
    
  2. Software Bill of Materials (SBOM)

    • Generate SBOMs for all images
    • Scan SBOMs for vulnerabilities
  3. 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:

  1. Assess Your Environment

    • Inventory your workloads and their security requirements
    • Identify legacy applications with special requirements
    • Understand your compliance obligations
  2. Define Your Security Levels

    • Map workloads to appropriate security levels
    • Document exceptions and their justifications
    • Create clear guidelines for developers
  3. Implement Gradually

    • Start with audit/warn modes
    • Focus on high-risk workloads first
    • Provide feedback and remediation guidance
  4. 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.

Andrew
Andrew

Andrew is a visionary software engineer and DevOps expert with a proven track record of delivering cutting-edge solutions that drive innovation at Ataiva.com. As a leader on numerous high-profile projects, Andrew brings his exceptional technical expertise and collaborative leadership skills to the table, fostering a culture of agility and excellence within the team. With a passion for architecting scalable systems, automating workflows, and empowering teams, Andrew is a sought-after authority in the field of software development and DevOps.

Tags

Recent Posts