Orchestration

Secure Your Kubernetes Pods: PSS Implementation Guide

July 2, 2026 Kubezilla Team 17 min read

Introduction

In the ever-evolving landscape of cloud-native security, safeguarding your Kubernetes clusters goes beyond just hardening the control plane or securing network access. A critical, often overlooked, layer of defense lies within the very workloads running on your cluster: the Pods themselves. Unrestricted Pod configurations can introduce severe vulnerabilities, allowing attackers to escalate privileges, access sensitive data, or compromise the underlying host. This is precisely where Kubernetes Pod Security Standards (PSS) come into play.

The Pod Security Standards provide a set of predefined security policies ranging from highly permissive to highly restrictive, helping you enforce best practices for how Pods are allowed to run. These standards replace the deprecated Pod Security Policies (PSPs) and integrate directly into the Kubernetes API server, offering a more streamlined and robust enforcement mechanism. By understanding and implementing PSS, you can significantly reduce the attack surface of your applications, ensuring that even if an attacker gains access to a Pod, their potential for lateral movement and damage is severely limited.

This comprehensive guide will walk you through the intricacies of Kubernetes Pod Security Standards. We’ll explore the different PSS profiles, demonstrate how to configure them using Pod Security Admission, and provide practical examples to secure your workloads effectively. Whether you’re a security engineer looking to harden your cluster or a developer aiming to deploy secure applications, this guide will equip you with the knowledge to implement PSS and build a more resilient Kubernetes environment.

TL;DR: Kubernetes Pod Security Standards (PSS) Implementation

Kubernetes Pod Security Standards (PSS) define three security profiles (Privileged, Baseline, Restricted) to enforce best practices for Pod configurations, replacing deprecated Pod Security Policies (PSPs). PSS are enforced via the Pod Security Admission controller, which is built into Kubernetes and configured using namespace labels. This guide covers how to enable PSS, apply different profiles to namespaces, and manage exceptions.

Key Commands:

  • Enable PSS (Kubeadm/Managed K8s): Usually enabled by default in recent K8s versions (1.23+). For older, ensure PodSecurity admission controller is active.
  • Label a Namespace for PSS Enforcement:
    kubectl label namespace <namespace-name> pod-security.kubernetes.io/enforce=<profile> \
        pod-security.kubernetes.io/warn=<profile> \
        pod-security.kubernetes.io/audit=<profile>
    
  • Example: Enforce ‘Restricted’ profile on ‘production’ namespace:
    kubectl label namespace production pod-security.kubernetes.io/enforce=restricted \
        pod-security.kubernetes.io/warn=restricted \
        pod-security.kubernetes.io/audit=restricted
    
  • Check Namespace Labels:
    kubectl get namespace <namespace-name> -o yaml | grep pod-security.kubernetes.io
    
  • Test a Restricted Pod:
    kubectl run nginx --image=nginx -n restricted-test
    

Prerequisites

Before diving into the implementation of Pod Security Standards, ensure you have the following:

  • A Kubernetes Cluster: Version 1.23 or newer is recommended, as Pod Security Admission is GA in 1.25. While PSS can be used with older versions (1.22+), the admission controller might need explicit enabling. A local cluster (Minikube, Kind, Docker Desktop) or a managed cloud Kubernetes service (EKS, GKE, AKS) will work.
  • kubectl Command-Line Tool: Configured to connect to your Kubernetes cluster. Refer to the official Kubernetes documentation for installation instructions.
  • Basic Understanding of Kubernetes Concepts: Familiarity with Pods, Namespaces, Deployments, and ReplicaSets is essential.
  • Administrative Access: You’ll need permissions to create and modify namespaces and deploy applications to test the policies.

Step-by-Step Guide: Implementing Pod Security Standards

The Pod Security Standards define three policy levels:

  1. Privileged: Unrestricted policy, allowing known escalations. This policy is typically used for cluster-critical or system-level workloads that require elevated privileges.
  2. Baseline: Minimally restrictive policy that prevents known privilege escalations. This is a good starting point for most applications, balancing security with operational flexibility.
  3. Restricted: Heavily restricted policy, following current Pod hardening best practices. This is the most secure profile and should be applied to sensitive applications or those with strict compliance requirements.

These policies are enforced through the Pod Security Admission controller, which is a built-in admission controller in Kubernetes. Enforcement is configured by applying specific labels to namespaces.

Step 1: Verify Pod Security Admission Controller Status

For Kubernetes versions 1.23 and later, the Pod Security Admission controller is typically enabled by default. For versions 1.25 and later, it’s GA and fully integrated. If you are running an older cluster (1.22), you might need to ensure it’s explicitly enabled.

To check if the Pod Security Admission controller is enabled, you can inspect your API server’s configuration. However, a simpler way is to try applying a PSS label to a namespace and see if it takes effect. If you encounter issues, consult your Kubernetes distribution’s documentation or the Kubernetes Admission Controllers documentation.

For most modern clusters, you can assume it’s enabled. Let’s create a test namespace first.

kubectl create namespace pss-test

Verify:

kubectl get namespace pss-test

Expected Output:

NAME       STATUS   AGE
pss-test   Active   Xs

Step 2: Understanding PSS Enforcement Modes

Pod Security Admission uses namespace labels to define the desired security profile and enforcement behavior. There are three modes for each profile:

  • enforce: Pods that violate the policy will be rejected. This is the strictest mode.
  • warn: Pods that violate the policy will be admitted, but a warning will be displayed to the user. This is useful for testing and auditing before full enforcement.
  • audit: Pods that violate the policy will be admitted, but an audit event will be recorded in the API server’s audit logs. This is useful for monitoring and compliance.

You can apply different levels for each mode. For example, you could warn on restricted but enforce on baseline. The labels follow the pattern: pod-security.kubernetes.io/<MODE>=<LEVEL>.

Step 3: Applying the ‘Baseline’ Profile to a Namespace

The ‘Baseline’ profile is a great starting point for most applications. It prevents known privilege escalations but is less restrictive than ‘Restricted’. Let’s label our pss-test namespace to enforce the ‘Baseline’ profile. We’ll also add ‘warn’ and ‘audit’ for good measure, all at the ‘baseline’ level.

kubectl label namespace pss-test pod-security.kubernetes.io/enforce=baseline \
    pod-security.kubernetes.io/warn=baseline \
    pod-security.kubernetes.io/audit=baseline

Verify: Check the labels on the namespace.

kubectl get namespace pss-test -o yaml | grep pod-security.kubernetes.io

Expected Output:

  pod-security.kubernetes.io/audit: baseline
  pod-security.kubernetes.io/audit-version: latest
  pod-security.kubernetes.io/enforce: baseline
  pod-security.kubernetes.io/enforce-version: latest
  pod-security.kubernetes.io/warn: baseline
  pod-security.kubernetes.io/warn-version: latest

Now, let’s try to deploy a Pod that violates the ‘Baseline’ profile. A common violation of the baseline policy is running a container as root user with a non-zero UID, or requesting hostPath volumes. We’ll try to run a privileged container, which is explicitly forbidden by ‘Baseline’.

# privileged-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: privileged-app
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true # This violates the Baseline policy
kubectl apply -f privileged-pod.yaml -n pss-test

Expected Output (Rejection):

Error from server (Forbidden): error when creating "privileged-pod.yaml": pods "privileged-app" is forbidden: violates PodSecurity "baseline:latest": privileged (containers "nginx" must not set securityContext.privileged=true)

The Pod was rejected, confirming that the ‘Baseline’ policy is being enforced. This is a crucial step in preventing common attack vectors. For more on securing your network, consider exploring our guide on Kubernetes Network Policies: Complete Security Hardening Guide.

Step 4: Applying the ‘Restricted’ Profile to a Namespace

The ‘Restricted’ profile is the most secure and is ideal for highly sensitive workloads. It requires Pods to run as non-root users, disallows hostPath volumes, and enforces many other security best practices. Let’s create a new namespace for this and apply the ‘Restricted’ profile.

kubectl create namespace restricted-apps
kubectl label namespace restricted-apps pod-security.kubernetes.io/enforce=restricted \
    pod-security.kubernetes.io/warn=restricted \
    pod-security.kubernetes.io/audit=restricted

Verify:

kubectl get namespace restricted-apps -o yaml | grep pod-security.kubernetes.io

Expected Output:

  pod-security.kubernetes.io/audit: restricted
  pod-security.kubernetes.io/audit-version: latest
  pod-security.kubernetes.io/enforce: restricted
  pod-security.kubernetes.io/enforce-version: latest
  pod-security.kubernetes.io/warn: restricted
  pod-security.kubernetes.io/warn-version: latest

Now, let’s try to deploy a simple Nginx Pod without any specific security context. This Pod would normally run as root (UID 0) inside the container, which violates the ‘Restricted’ policy.

# simple-nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: simple-nginx
spec:
  containers:
  - name: nginx
    image: nginx
kubectl apply -f simple-nginx-pod.yaml -n restricted-apps

Expected Output (Rejection):

Error from server (Forbidden): error when creating "simple-nginx-pod.yaml": pods "simple-nginx" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (containers "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (containers "nginx" must drop all capabilities), runAsNonRoot != true (containers "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (containers "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

As you can see, the ‘Restricted’ policy is much stricter. It requires specific security contexts to be set. Let’s fix the Nginx Pod to comply with the ‘Restricted’ profile.

# restricted-nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: restricted-nginx
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000 # Example non-root user
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: nginx
    image: nginx
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
          - ALL
      readOnlyRootFilesystem: true
      # Image should ideally provide a non-root user, e.g., using USER directive in Dockerfile
      # For nginx, it typically runs as nginx user (UID 101 or similar)
      # We'll override it here for demonstration, but a better practice is to build images with non-root by default.
      runAsUser: 1000
kubectl apply -f restricted-nginx-pod.yaml -n restricted-apps

Expected Output (Success):

pod/restricted-nginx created

Verify:

kubectl get pod restricted-nginx -n restricted-apps

Expected Output:

NAME               READY   STATUS    RESTARTS   AGE
restricted-nginx   1/1     Running   0          Xs

This demonstrates how to successfully deploy a Pod under the ‘Restricted’ profile by adhering to its strict security requirements. When deploying sensitive workloads, you might also consider advanced security measures like those discussed in Securing Container Supply Chains with Sigstore and Kyverno.

Step 5: Using Different Enforcement Levels (Warn/Audit)

It’s often impractical to immediately enforce ‘Restricted’ on an existing namespace with many applications. A common strategy is to start with ‘warn’ or ‘audit’ modes to identify violations without blocking deployments.

Let’s modify our pss-test namespace to warn on ‘restricted’ but still enforce ‘baseline’.

kubectl label namespace pss-test pod-security.kubernetes.io/enforce=baseline \
    pod-security.kubernetes.io/warn=restricted \
    pod-security.kubernetes.io/audit=restricted --overwrite

Verify:

kubectl get namespace pss-test -o yaml | grep pod-security.kubernetes.io

Expected Output:

  pod-security.kubernetes.io/audit: restricted
  pod-security.kubernetes.io/audit-version: latest
  pod-security.kubernetes.io/enforce: baseline
  pod-security.kubernetes.io/enforce-version: latest
  pod-security.kubernetes.io/warn: restricted
  pod-security.kubernetes.io/warn-version: latest

Now, let’s try to deploy our previously rejected simple-nginx-pod.yaml (which violates ‘Restricted’ but not ‘Baseline’) into the pss-test namespace.

kubectl apply -f simple-nginx-pod.yaml -n pss-test

Expected Output (Success with Warning):

Warning: pods "simple-nginx" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (containers "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (containers "nginx" must drop all capabilities), runAsNonRoot != true (containers "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (containers "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/simple-nginx created

The Pod was created, but a warning was issued, indicating that it violates the ‘Restricted’ policy. This allows you to identify non-compliant Pods without disrupting existing deployments. You can then work on bringing these Pods into compliance before switching the enforce mode to ‘restricted’.

Step 6: Exemptions for Specific Workloads

Sometimes, specific applications (e.g., certain monitoring agents, custom storage drivers, or even GPU-enabled LLM workloads) genuinely require higher privileges and cannot comply with ‘Baseline’ or ‘Restricted’ profiles. PSS allows for exemptions at the Pod, RuntimeClass, or User/ServiceAccount level.

However, direct Pod or RuntimeClass exemptions are typically configured at the API server level (e.g., in a PodSecurityConfiguration file), which requires cluster administrative access and often a restart of the API server. For most users, exempting a specific ServiceAccount is the most practical approach.

Let’s say you have an application in the restricted-apps namespace that absolutely needs to run with hostPath volumes. Instead of weakening the entire namespace’s policy, you can exempt its ServiceAccount.

First, create a ServiceAccount and a Pod that uses it, violating the ‘Restricted’ policy with a hostPath.

kubectl create serviceaccount privileged-sa -n restricted-apps
# hostpath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-app
spec:
  serviceAccountName: privileged-sa
  containers:
  - name: my-container
    image: busybox
    command: ["sh", "-c", "echo Hello from hostPath; sleep 3600"]
    volumeMounts:
    - name: host-path-vol
      mountPath: /var/log/host
  volumes:
  - name: host-path-vol
    hostPath: # This violates the Restricted policy
      path: /var/log
      type: DirectoryOrCreate
kubectl apply -f hostpath-pod.yaml -n restricted-apps

Expected Output (Rejection):

Error from server (Forbidden): error when creating "hostpath-pod.yaml": pods "hostpath-app" is forbidden: violates PodSecurity "restricted:latest": hostPath volumes (volumes "host-path-vol" must not use hostPath)

Now, to exempt the privileged-sa ServiceAccount from the ‘Restricted’ policy, you would typically configure this in the PodSecurityConfiguration file for the API server. This file is specified via the --admission-control-config-file flag on the kube-apiserver.

# pod-security-config.yaml (example for API server configuration)
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
  enforce: restricted
  enforceVersion: latest
  audit: restricted
  auditVersion: latest
  warn: restricted
  warnVersion: latest
exemptions:
  namespaces: []
  runtimeClasses: []
  serviceAccounts:
    - names: ["privileged-sa"]
      namespaces: ["restricted-apps"]

Note: Applying this configuration requires access to the Kubernetes control plane and restarting the kube-apiserver. This is usually beyond the scope of a typical user and is an administrator’s task. For managed Kubernetes services, consult your cloud provider’s documentation on how to configure Pod Security Admission exemptions.

Once the API server is updated with this configuration, the hostpath-app Pod using privileged-sa in the restricted-apps namespace would be allowed, even though it violates the ‘Restricted’ policy. It’s crucial to use exemptions sparingly and only when absolutely necessary, documenting the reasons thoroughly. For broader security context, also consider how robust network configurations, like those provided by Cilium WireGuard Encryption, complement PSS.

Production Considerations

Implementing Pod Security Standards in a production environment requires careful planning and execution to avoid disrupting critical workloads.

  • Phased Rollout: Never apply enforce: restricted directly to a production namespace without prior testing. Start with warn: restricted and audit: restricted to identify violations. Monitor your logs and application behavior for warnings.
  • Application Compatibility: Review your existing applications. Many legacy or poorly configured applications might violate even the ‘Baseline’ profile. Identify these, prioritize remediation, or plan for specific exemptions (with extreme caution).
  • Image Hardening: Encourage or mandate the use of hardened container images that run as non-root users, drop unnecessary capabilities, and have minimal attack surface. Tools like Distroless images are excellent for this.
  • CI/CD Integration: Integrate PSS checks into your CI/CD pipeline. Use tools like Kyverno or OPA/Gatekeeper to validate Pod manifests against PSS profiles before deployment, providing developers with early feedback. This is also covered in our Sigstore and Kyverno Security guide.
  • Monitoring and Alerting: Set up monitoring and alerting for Pod Security Admission audit events. This helps detect attempts to deploy non-compliant Pods, even if they are eventually blocked. eBPF Observability with Hubble can provide deep insights into runtime behavior that complements PSS.
  • Exemption Management: If exemptions are necessary, keep them minimal, time-bound, and well-documented. Regularly review and challenge exemptions to see if the underlying application can be refactored to comply.
  • Cluster-wide Defaults: Consider setting a cluster-wide default PSS policy in the PodSecurityConfiguration file. For example, defaulting to ‘Baseline’ or ‘Restricted’ for all new namespaces ensures a minimum security posture across the cluster.
  • Version Skew: Be aware of Kubernetes version changes. PSS is GA in 1.25, and behavior might slightly differ in older versions. Always refer to the official Kubernetes PSS documentation for your specific version.
  • Service Mesh Integration: While PSS secures the Pod, a service mesh like Istio Ambient Mesh can provide additional layers of security like mTLS, fine-grained authorization, and traffic encryption for Pod-to-Pod communication.

Troubleshooting

Here are common issues you might encounter when implementing Pod Security Standards and their solutions:

  1. Pod Rejected with “violates PodSecurity” Error

    Issue: You tried to create a Pod, and it was rejected with an error message similar to “violates PodSecurity ‘restricted:latest’: runAsNonRoot != true”.

    Solution: The error message is usually very descriptive. It tells you exactly which part of the Pod’s security context violates the applied PSS profile. Review the Pod’s YAML and adjust its securityContext to comply. For example, if it says “runAsNonRoot != true”, add runAsNonRoot: true and a runAsUser to your Pod or container’s security context.

    # Example fix for runAsNonRoot
    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000 # Must be a non-root user
      containers:
      - name: my-container
        image: my-image
        # ... other settings
    
  2. Namespace Labels Not Taking Effect

    Issue: You’ve applied PSS labels to a namespace, but Pods that should be rejected are still being admitted (or vice versa).

    Solution:

    1. Check Labels: Double-check that the labels are correctly applied to the namespace using kubectl get namespace <name> -o yaml.
    2. Admission Controller Enabled: Ensure the Pod Security Admission controller is enabled on your API server. For K8s 1.23+, it should be by default. For older versions, check your kube-apiserver manifest for the --enable-admission-plugins=...PodSecurity... flag.
    3. Kubernetes Version: PSS behavior might vary slightly across Kubernetes versions. Ensure you’re referencing documentation for your specific version.
  3. “Warning: pods … is forbidden” but Pod is Created

    Issue: You see a warning about PSS violations, but the Pod is still created successfully.

    Solution: This is expected behavior when the namespace is labeled with warn=<profile> but not enforce=<profile>. The Pod Security Admission controller is warning you about non-compliance without blocking the deployment. This is useful for auditing and preparing for full enforcement. To block the Pod, change the label to enforce=<profile>.

    kubectl label namespace <namespace-name> pod-security.kubernetes.io/enforce=restricted --overwrite
    
  4. Existing Pods Not Affected by New Policy

    Issue: You’ve applied a strict PSS policy to a namespace, but existing running Pods in that namespace are not being terminated or rejected.

    Solution: Pod Security Admission only applies to new Pod creation or updates. It does not affect already running Pods. To enforce the new policy on existing deployments, you need to recreate them (e.g., by updating an annotation, scaling down and up, or rolling out a new version). Managed solutions like Karpenter Cost Optimization can help manage node lifecycles, but Pod restarts are still needed for policy re-evaluation.

  5. Exemptions Not Working

    Issue: You’ve tried to configure an exemption for a ServiceAccount or RuntimeClass, but Pods using it are still being rejected.

    Solution: Exemptions are configured at the API server level via the PodSecurityConfiguration file.

    1. API Server Configuration: Ensure your kube-apiserver is configured with the --admission-control-config-file flag pointing to your PodSecurityConfiguration.
    2. API Server Restart: Changes to the PodSecurityConfiguration often require a restart of the kube-apiserver to take effect.
    3. Correct Exemption Syntax: Double-check the YAML syntax for your exemptions (serviceAccounts, namespaces, runtimeClasses) in the configuration file.
    4. Managed Kubernetes: For EKS, GKE, AKS, etc., exemption configuration might be handled differently (e.g., via cluster-level settings or specific add-ons). Consult your cloud provider’s documentation.

FAQ Section

  1. What are the Kubernetes Pod Security Standards (PSS) and why were they introduced?

    The Kubernetes Pod Security Standards (PSS) are built-in, evolving security policies for Pods, replacing the deprecated Pod Security Policies (PSPs). They were introduced to provide a more user-friendly and maintainable way to enforce Pod security best practices directly within the Kubernetes API server. PSS defines three profiles (Privileged, Baseline, Restricted) to help users secure their workloads against common privilege escalation attacks and other vulnerabilities.

  2. What’s the difference between ‘Baseline’ and ‘Restricted’ profiles?

    The ‘Baseline’ profile is a moderately restrictive policy that aims to prevent known privilege escalations. It disallows actions like running privileged containers, using host namespaces, or escalating privileges. The ‘Restricted’ profile is the most secure, heavily constrained policy, following current Pod hardening best practices. It further requires Pods to run as non-root, drop all capabilities, use a read-only root filesystem, and enforce specific seccomp profiles, among other things. ‘Baseline’ is a good starting point, while ‘Restricted’ is for highly sensitive workloads.

  3. How do I apply PSS to a specific namespace?

    You apply PSS to a namespace by labeling it with pod-security.kubernetes.io/enforce=<profile>, pod-security.kubernetes.io/warn=<profile>, and/or pod-security.kubernetes.io/audit=<profile>. For example, to enforce the ‘Restricted’ profile:

    kubectl label namespace my-app-ns pod-security.kubernetes.io/enforce=restricted
    

  4. Can I exempt certain Pods or ServiceAccounts from PSS?

    Yes, you can configure exemptions for specific namespaces, RuntimeClasses, or ServiceAccounts. These exemptions are defined in the PodSecurityConfiguration resource, which is passed to the kube-apiserver via the --admission-control-config-file flag. This is typically an administrative task and may require a restart of the API server. Exemptions should be used sparingly and with a clear rationale.

  5. What happens to existing Pods when I apply a new PSS policy to their namespace?

    Existing, running Pods are not affected by a new PSS policy applied to their namespace. The Pod Security Admission controller only evaluates Pods at creation or update time. To enforce the new policy on existing workloads, you need to trigger a recreation or update of those Pods (e.g., by performing a rolling update on their Deployment).

Cleanup Commands

To clean up the resources created during this guide, execute the following commands:

kubectl delete namespace pss-test
kubectl delete namespace restricted-apps
kubectl delete -f privileged-pod.yaml -n pss-test # If it was created with warn/audit
kubectl delete -f simple-nginx-pod.yaml -n pss-test # If it was created with warn/audit
kubectl delete -f restricted-nginx-pod.yaml -n restricted-apps
kubectl delete -f hostpath-pod.yaml -n restricted-apps # If it was created via exemption

Verify:

kubectl get namespace pss-test restricted-apps

Expected Output:

Error from server (NotFound): namespaces "pss-test" not found
Error from server (NotFound): namespaces "restricted-apps" not found

Next Steps / Further Reading

Congratulations! You’ve successfully navigated the implementation of Kubernetes Pod Security Standards. To further enhance your cluster’s security posture and deepen your understanding, consider these topics:

Leave a comment