Kubernetes gives us a lot of flexibility when defining Pods. That flexibility is powerful, but it can also become dangerous when Pods are allowed to run with more access than they really need.
A Pod can be configured to run as privileged, use the host network namespace, mount host paths, add Linux capabilities, use host ports, disable default security profiles, or run as the root user. Some infrastructure components genuinely need elevated permissions, but most application workloads do not.
Pod Security Standards help solve this problem by giving us predefined security levels for Pods. Instead of inventing a pod security model from scratch, Kubernetes defines three standard profiles:
privilegedbaselinerestricted
The goal is not to make every Pod restricted on day one. The practical goal is to apply the least permissive profile that still allows the workload to run correctly.
For many application namespaces, a good starting point is to enforce baseline, while using warn and audit with restricted. This blocks clearly risky configurations, but still gives developers feedback about what should be improved before moving to stricter enforcement.
What Are Pod Security Standards?
Pod Security Standards, often shortened to PSS, are Kubernetes-defined security profiles for Pods. They describe which Pod and container-level settings are allowed or disallowed.
They are not custom resources that you create with kubectl apply. They are predefined policy levels built into the Kubernetes security model.
| Profile | Restriction level | Typical use case |
|---|---|---|
privileged | Least restrictive | Trusted system workloads that need host-level access. |
baseline | Moderate restriction | General-purpose application workloads that should avoid known privilege escalations. |
restricted | Most restrictive | Hardened workloads, sensitive applications, regulated environments, or namespaces used by lower-trust users. |
The profiles move from very permissive to very restrictive. privileged allows almost everything. baseline blocks common privilege escalation paths while staying compatible with many normal containers. restricted follows stronger pod-hardening practices and may require changes to container images and manifests.
Why Pod Security Standards Matter
Without pod-level guardrails, a developer, CI/CD pipeline, Helm chart, or operator can accidentally deploy a Pod with too much power.
Examples of risky configurations include:
- running a container as
privileged - mounting the host filesystem with
hostPath - using the host network namespace with
hostNetwork: true - using the host process or IPC namespaces
- adding powerful Linux capabilities
- allowing privilege escalation
- running as the root user
- disabling or weakening seccomp or AppArmor profiles
- using host ports unnecessarily
These settings do not automatically mean a cluster is compromised. The problem is blast radius. If an application is exploited, excessive Pod privileges can give an attacker more options to escape, escalate, inspect the node, access other workloads, or interfere with the cluster.
Pod Security Standards reduce this risk by making unsafe configurations visible or by blocking them during admission.
Pod Security Standards vs Pod Security Admission
There are two terms that are easy to mix up:
- Pod Security Standards are the policy profiles:
privileged,baseline, andrestricted. - Pod Security Admission is the built-in Kubernetes admission controller that evaluates Pods against those profiles.
When a request is sent to the Kubernetes API server, the Pod Security Admission controller can check whether the Pod matches the profile configured for its namespace.
Depending on the configured mode, Kubernetes can:
- reject the Pod
- allow the Pod but show a warning
- allow the Pod but add audit information
This is why Pod Security Standards are useful operationally. You can introduce them gradually instead of immediately blocking every non-compliant workload.
What Happened to PodSecurityPolicy?
Before Pod Security Admission, Kubernetes had a feature called PodSecurityPolicy, usually shortened to PSP.
PodSecurityPolicy was powerful, but it was also difficult to use correctly. It required policy objects, RBAC bindings, and careful ordering. In many clusters it became confusing to understand which policy applied to which user or workload.
PodSecurityPolicy was deprecated in Kubernetes v1.21 and removed in Kubernetes v1.25. Pod Security Admission became the built-in replacement path for standard pod-level security enforcement.
That does not mean Pod Security Admission is a one-to-one replacement for everything PSP could do. It is intentionally simpler. It gives Kubernetes users a standard baseline, but it does not replace every custom policy use case.
For more advanced policy requirements, tools such as Kyverno, OPA Gatekeeper, Kubewarden, or Kubernetes Validating Admission Policy can be used alongside Pod Security Admission.
How Pod Security Admission Is Configured
Pod Security Admission is configured at the namespace level using labels.
The general label format is:
pod-security.kubernetes.io/<MODE>: <LEVEL>
The available modes are:
enforce
warn
audit
The available levels are:
privileged
baseline
restricted
For example, this namespace enforces the baseline profile:
apiVersion: v1
kind: Namespace
metadata:
name: application
labels:
pod-security.kubernetes.io/enforce: baseline
This means that Pods violating the baseline profile will be rejected in this namespace.
Admission Modes: Enforce, Warn, and Audit
Pod Security Admission supports three different modes. Each mode answers a different operational question.
| Mode | What Kubernetes does | Useful for |
|---|---|---|
enforce | Rejects Pods that violate the configured profile. | Hard security boundaries. |
warn | Allows the Pod but returns a user-facing warning. | Developer feedback and migration. |
audit | Allows the Pod but records an audit annotation. | Security monitoring and compliance visibility. |
These modes can be combined. This is one of the most useful parts of Pod Security Admission.
A common pattern is:
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
This means:
- Pods that violate
baselineare blocked. - Pods that violate
restrictedstill run, but developers see warnings. - Violations against
restrictedare also recorded in audit logs.
This is a good compromise for teams that want to improve security without breaking all workloads at once.
Version Pinning
Pod Security Standards can change between Kubernetes minor versions. For production clusters, it is often safer to pin the policy version explicitly.
Example:
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.36
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.36
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.36
The version labels make the policy behavior more predictable during cluster upgrades.
For a local lab or learning cluster, using the default behavior or latest is usually acceptable. For production, pinning versions helps prevent unexpected policy changes after a Kubernetes upgrade.
The Three Pod Security Profiles
Now let’s go deeper into the three profiles.
Profile 1: Privileged
The privileged profile is intentionally open. It does not impose restrictions.
This profile is mainly for trusted system-level or infrastructure-level workloads. Examples may include:
- some CNI components
- some CSI storage components
- low-level node agents
- security or monitoring agents that need host access
- cluster administration workloads
A privileged workload may need access to host namespaces, host devices, kernel-level interfaces, or node paths. That can be legitimate, but it must be treated as high risk.
Do not use privileged as the default profile for application namespaces.
A namespace that allows privileged workloads should have strong RBAC boundaries. Only trusted users, service accounts, and deployment automation should be able to create workloads there.
Example privileged namespace:
apiVersion: v1
kind: Namespace
metadata:
name: privileged
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/warn: baseline
pod-security.kubernetes.io/audit: baseline
This namespace allows privileged workloads, but still warns and audits when a Pod violates baseline. That gives visibility without blocking system-level workloads.
Profile 2: Baseline
The baseline profile is designed for broad compatibility with common application workloads while blocking known privilege escalation paths.
This is often the best starting point for regular application namespaces.
Baseline is useful because it blocks clearly dangerous settings, but it usually does not require every container image to be rebuilt immediately.
Baseline disallows or restricts several categories of risky behavior.
| Control area | What baseline tries to prevent |
|---|---|
| Host namespaces | Pods should not share the host network, PID, or IPC namespace. |
| Privileged containers | Containers should not run with privileged: true. |
| HostPath volumes | Pods should not mount arbitrary paths from the node filesystem. |
| Extra capabilities | Containers should not add capabilities outside the allowed baseline set. |
| Host ports | Host ports should be avoided or tightly controlled. |
| AppArmor | Workloads should not disable or override safe defaults in unsafe ways. |
| SELinux | Custom SELinux user and role settings are restricted. |
/proc mount type | Containers should use the default masked /proc behavior. |
| Seccomp | Containers should not explicitly use Unconfined. |
| Unsafe sysctls | Only safe sysctls are allowed. |
| Windows HostProcess | HostProcess containers are not allowed under baseline. |
| Probe and lifecycle host fields | Setting custom host fields in probes and lifecycle hooks is restricted in newer Kubernetes versions. |
Baseline does not require every workload to run as non-root. It also does not require dropping all capabilities. Those stricter requirements belong to the restricted profile.
That makes baseline a realistic first enforcement level for many existing applications.
Profile 3: Restricted
The restricted profile is the strongest built-in profile.
It includes everything from baseline, and adds stricter controls that align with current pod-hardening practices.
Restricted is a good target for:
- sensitive application namespaces
- regulated workloads
- production environments with strong security requirements
- namespaces used by external teams or lower-trust users
- workloads that should follow least privilege as strictly as possible
Restricted commonly requires the following patterns.
| Requirement | Meaning |
|---|---|
allowPrivilegeEscalation: false | The process should not gain more privileges through mechanisms such as setuid or setgid binaries. |
runAsNonRoot: true | Containers must run as a non-root user. |
runAsUser must not be 0 | Containers must not explicitly run as root. |
seccompProfile.type: RuntimeDefault or Localhost | Seccomp must be explicitly configured. |
capabilities.drop: ["ALL"] | Linux capabilities must be dropped. |
Only NET_BIND_SERVICE can be added back | The only capability that may be re-added is NET_BIND_SERVICE. |
| Restricted volume types only | Volumes must be from the allowed set, such as configMap, secret, emptyDir, projected, persistentVolumeClaim, ephemeral, csi, or downwardAPI. |
Restricted gives better security, but it can expose problems in existing images.
For example, a container image may assume that it runs as root and writes to directories owned by root. If you set runAsNonRoot: true, the Pod may pass admission but fail at runtime because the container process cannot write to the expected path.
This is not a Kubernetes bug. It means the image was not designed to run as a non-root user.
Why a Restricted Pod Can Still Fail at Runtime
Pod Security Admission validates the Pod specification. It does not fully test whether the application inside the container can actually run.
A Pod can pass the restricted policy but still fail because:
- the image tries to run as root
- the image needs to bind to port
80instead of a higher port - the process writes to a root-owned directory
- the application expects writable paths that are read-only
- the entrypoint script changes file permissions at startup
- the container expects capabilities that were dropped
This is why moving to restricted is both a Kubernetes task and an application packaging task.
In practice, you often need to adjust Dockerfiles, file ownership, application ports, and writable directories.
Workload Resources and Pod Templates
Most Pods are not created directly. They are created by workload controllers such as:
- Deployments
- StatefulSets
- DaemonSets
- Jobs
- CronJobs
- ReplicaSets
These workload resources contain a Pod template.
Pod Security Admission has an important nuance here:
warnandauditchecks are applied to workload resources so violations can be caught early.enforceis applied to the resulting Pod objects, not directly to the workload object itself.
This means you may see warnings when creating a Deployment, but the hard rejection happens when the controller tries to create the actual Pod.
That nuance is useful when troubleshooting. If a Deployment exists but no Pods are created, check the ReplicaSet events and namespace policy labels.
Useful commands:
kubectl describe deployment <deployment-name> -n <namespace>
kubectl describe rs -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
Lab Overview
Let’s create a small lab that demonstrates the most important cases.
We will create three namespaces:
privilegedbaselinerestricted
Then we will test different Pod manifests against them.
Suggested folder structure:
pod-security-standards/
├── namespaces/
│ ├── privileged.yaml
│ ├── baseline.yaml
│ └── restricted.yaml
├── pods/
│ ├── privileged-pod.yaml
│ ├── baseline-pod.yaml
│ ├── restricted-nginx-root.yaml
│ └── restricted-nginx-unprivileged.yaml
└── deployments/
└── restricted-deployment.yaml
Create the Namespaces
Create namespaces/privileged.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: privileged
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/warn: baseline
pod-security.kubernetes.io/audit: baseline
Create namespaces/baseline.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: baseline
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
Create namespaces/restricted.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: restricted
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
Apply the namespaces:
kubectl apply -f namespaces/
Check the labels:
kubectl get namespaces --show-labels
Example 1: A Privileged Pod
Create pods/privileged-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx-privileged
spec:
containers:
- name: nginx
image: nginx:1.27.0
ports:
- containerPort: 80
securityContext:
privileged: true
Apply it to the privileged namespace:
kubectl apply -f pods/privileged-pod.yaml -n privileged
The Pod should be created, but you should see a warning because the namespace is configured to warn against baseline violations.
Expected behavior:
Warning: would violate PodSecurity "baseline:latest": privileged ...
pod/nginx-privileged created
Now try to apply the same Pod to the baseline namespace:
kubectl apply -f pods/privileged-pod.yaml -n baseline
Expected behavior:
Error from server (Forbidden): error when creating ... violates PodSecurity "baseline:latest" ...
This demonstrates the difference between warn and enforce.
In the privileged namespace, the Pod is allowed but warned. In the baseline namespace, the Pod is blocked.
Example 2: A Simple Baseline-Compatible Pod
Create pods/baseline-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx-baseline
spec:
containers:
- name: nginx
image: nginx:1.27.0
ports:
- containerPort: 80
Apply it to the baseline namespace:
kubectl apply -f pods/baseline-pod.yaml -n baseline
The Pod should be accepted by the baseline enforcement policy.
However, because the namespace warns against restricted, Kubernetes may show warnings about missing restricted-level settings, such as:
allowPrivilegeEscalationis not set tofalse- capabilities are not dropped
runAsNonRootis not setseccompProfileis not set
This is exactly why warn: restricted is useful. Developers can deploy the workload, but they also get immediate feedback about how to harden it.
Example 3: A Restricted Pod Using the Default NGINX Image
Now create a restricted-style Pod using the regular nginx image.
Create pods/restricted-nginx-root.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx-restricted-root-image
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.27.0
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Apply it to the baseline namespace:
kubectl apply -f pods/restricted-nginx-root.yaml -n baseline
This Pod may pass Pod Security Admission because the manifest contains the expected restricted fields.
But it can fail during container creation because the regular NGINX image commonly expects to run as root.
Check the Pod:
kubectl get pods -n baseline
Describe it:
kubectl describe pod nginx-restricted-root-image -n baseline
You may see an error similar to:
container has runAsNonRoot and image will run as root
This is a very important lesson. Pod Security Admission validates policy. It does not magically make an image compatible with non-root execution.
Example 4: A Restricted Pod Using an Unprivileged Image
Now use an image designed to run without root privileges.
Create pods/restricted-nginx-unprivileged.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx-restricted
spec:
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:1.27.1
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Apply it to the restricted namespace:
kubectl apply -f pods/restricted-nginx-unprivileged.yaml -n restricted
Check the Pod:
kubectl get pods -n restricted
This Pod should be much more compatible with the restricted profile because:
- it runs as a non-root user
- it drops all Linux capabilities
- it disables privilege escalation
- it explicitly uses the runtime default seccomp profile
- it listens on port
8080instead of privileged port80
This is the kind of adjustment that is often needed when moving applications from baseline to restricted.
Example 5: Deployment Behavior
Create deployments/restricted-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-restricted
spec:
replicas: 2
selector:
matchLabels:
app: nginx-restricted
template:
metadata:
labels:
app: nginx-restricted
spec:
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:1.27.1
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Apply it:
kubectl apply -f deployments/restricted-deployment.yaml -n restricted
Check the Deployment and Pods:
kubectl get deployment -n restricted
kubectl get pods -n restricted
If the Deployment is created but Pods do not appear, check the ReplicaSet and events:
kubectl describe rs -n restricted
kubectl get events -n restricted --sort-by=.lastTimestamp
This helps distinguish between workload object creation and actual Pod admission.
Testing Namespace Label Changes Safely
When applying Pod Security labels to existing namespaces, do not immediately enforce strict policies without testing.
Use server-side dry run first:
kubectl label --dry-run=server --overwrite ns baseline \
pod-security.kubernetes.io/enforce=restricted
Kubernetes will evaluate existing Pods and return warnings without actually changing the namespace.
For all namespaces, you can start with warnings and audit annotations:
kubectl label --overwrite ns --all \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/audit=baseline
Then list namespaces that do not yet have an explicit enforce policy:
kubectl get namespaces --selector='!pod-security.kubernetes.io/enforce'
This gives you an inventory of namespaces that still need a decision.
Cluster-Wide Defaults
Namespace labels are the most common way to configure Pod Security Admission, but the admission controller can also be configured with cluster-wide defaults.
A kube-apiserver admission configuration can define default levels and exemptions.
Example:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces:
- kube-system
This file must be provided to the API server using the appropriate admission-control configuration flag.
Managed Kubernetes providers may not expose this configuration directly. In that case, namespace labels are usually the practical method.
Exemptions
Pod Security Admission supports static exemptions.
Exemptions can be based on:
- usernames
- runtime class names
- namespaces
Be careful with exemptions. They skip all Pod Security Admission behavior, including enforce, warn, and audit.
Avoid broad exemptions for controller service accounts. Most Pods are created indirectly by controllers. If you exempt a controller service account too broadly, you may accidentally exempt many workloads created by normal users.
A safer pattern is to isolate privileged workloads into dedicated namespaces and protect those namespaces with RBAC.
Protect Privileged Namespaces with RBAC
Pod Security Standards control which Pod specs are allowed in a namespace. They do not decide who can create workloads in that namespace.
RBAC is still required.
For example, you may have a namespace called privileged where privileged workloads are allowed, but only platform administrators should deploy there.
Example Role for managing Pods in the privileged namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: privileged
name: privileged-workload-manager
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/exec"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Example RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: privileged
name: platform-admins-privileged-workloads
subjects:
- kind: Group
name: platform-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: privileged-workload-manager
apiGroup: rbac.authorization.k8s.io
This is the important combination:
- Pod Security Admission defines what is allowed in the namespace.
- RBAC defines who can create or change workloads in the namespace.
You usually need both.
Practical Namespace Strategy
A practical production strategy could look like this.
| Namespace type | Recommended policy |
|---|---|
| System namespaces | Usually privileged, with tight RBAC and audit visibility. |
| General application namespaces | enforce: baseline, warn: restricted, audit: restricted. |
| New application namespaces | Start with restricted when the platform supports it. |
| Sensitive workloads | enforce: restricted. |
| Third-party Helm charts | Start with warn and audit, then enforce after validation. |
| Legacy workloads | Use baseline first, fix warnings, then move toward restricted. |
For many teams, the migration path is:
- Add
warnandauditlabels first. - Collect violations.
- Fix the most common manifest and image issues.
- Enforce
baseline. - Keep warning and auditing against
restricted. - Move selected namespaces to
restrictedonce workloads are compatible.
Common Violations and Fixes
| Problem | Typical message or symptom | Fix |
|---|---|---|
| Privileged container | Violates baseline because privileged=true. | Remove privileged: true, or move the workload to a tightly controlled privileged namespace. |
| HostPath volume | Violates baseline because hostPath is set. | Use persistentVolumeClaim, configMap, secret, emptyDir, or another safer volume type. |
| Host network | Violates baseline because hostNetwork=true. | Use a Service, Ingress, or other Kubernetes networking pattern. |
| Added capability | Capability is not allowed by the profile. | Remove the capability or justify a different namespace/profile. |
Missing allowPrivilegeEscalation: false | Warning against restricted. | Add allowPrivilegeEscalation: false to each container security context. |
| Missing capability drop | Warning against restricted. | Add capabilities.drop: ["ALL"]. |
| Missing seccomp profile | Warning or rejection under restricted. | Set seccompProfile.type: RuntimeDefault at Pod or container level. |
| Root image | Runtime error with runAsNonRoot. | Use a non-root image or update the Dockerfile to use a non-root USER. |
| Port 80 as non-root | Application fails to bind to port 80. | Listen on a high port such as 8080 and expose it through a Service. |
| Writable root-owned paths | Container starts but crashes. | Fix image ownership, use writable volumes, or configure the app to write to allowed paths. |
Security Context Example for Restricted Workloads
A reusable baseline for restricted Linux workloads can look like this:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: example/app:1.0.0
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
This does not guarantee the application will run. The image must also be compatible with non-root execution.
A matching Dockerfile pattern might look like this:
FROM python:3.12-slim
RUN groupadd --gid 10001 app \
&& useradd --uid 10001 --gid 10001 --create-home app
WORKDIR /app
COPY --chown=10001:10001 . /app
USER 10001:10001
EXPOSE 8080
CMD ["python", "-m", "myapp"]
The Pod manifest and the image need to agree with each other.
What Pod Security Standards Do Not Cover
Pod Security Standards are useful, but they are not a complete Kubernetes security solution.
They do not replace:
- RBAC
- NetworkPolicies
- image scanning
- image signing and verification
- runtime threat detection
- secret management
- node hardening
- Kubernetes audit logging
- admission policies for custom organization rules
- resource quotas and limit ranges
- supply-chain security
They also do not mutate workloads. For example, Pod Security Admission will not automatically add allowPrivilegeEscalation: false for you. It only evaluates the request and then allows, warns, audits, or rejects.
If you want automatic mutation or custom organization-specific rules, use an admission policy tool such as Kyverno, OPA Gatekeeper, Kubewarden, or native Kubernetes admission policy features where appropriate.
When to Use Kyverno, Gatekeeper, or Kubewarden
Pod Security Admission is excellent for standard pod security levels. It is not designed for every custom rule.
Use an additional policy engine when you need rules such as:
- only images from approved registries are allowed
- images must use immutable digests instead of mutable tags
- Pods must define CPU and memory requests
- Deployments must have certain labels
- Services of type
LoadBalancerare restricted - host ports are allowed only from a specific list
- containers must not use the
latesttag - security contexts should be automatically injected
- namespace-specific exceptions need more flexibility
Pod Security Standards give you the standard security floor. A policy engine gives you more detailed organization-specific governance.
Recommended Rollout Plan
A safe rollout plan for existing clusters:
Inventory namespaces.
kubectl get namespaces --show-labelsStart with warn and audit.
kubectl label --overwrite ns --all \ pod-security.kubernetes.io/warn=baseline \ pod-security.kubernetes.io/audit=baselineReview warnings and audit logs.
Focus on common problems: privileged containers, host paths, host namespaces, and unsafe capabilities.
Enforce baseline for application namespaces.
kubectl label --overwrite ns app \ pod-security.kubernetes.io/enforce=baseline \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restrictedFix restricted warnings.
Update manifests and rebuild images where needed.
Move compatible namespaces to restricted.
kubectl label --overwrite ns sensitive-app \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restrictedProtect privileged namespaces with RBAC.
Do not allow every developer or pipeline to deploy workloads into namespaces that allow privileged Pods.
Add complementary controls.
Add NetworkPolicies, RBAC reviews, image scanning, runtime monitoring, and custom admission policies.
Cleanup Commands
Delete the lab Pods:
kubectl delete pod --all -n privileged
kubectl delete pod --all -n baseline
kubectl delete pod --all -n restricted
Delete the Deployment:
kubectl delete deployment --all -n restricted
Delete the namespaces:
kubectl delete namespace privileged baseline restricted
Key Takeaways
Pod Security Standards give Kubernetes a common language for pod-level security.
Use privileged only for trusted system workloads that truly require elevated access. Protect those namespaces with RBAC.
Use baseline as a realistic minimum for many general-purpose application namespaces. It blocks common privilege escalation paths without breaking as many existing workloads as restricted.
Use restricted for hardened workloads and as the long-term target for new applications. Expect to update container images, ports, writable paths, and security contexts.
Use warn and audit before strict enforcement. They are extremely useful for migration because they show what would break before you block workloads.
Do not treat Pod Security Standards as the only security mechanism. Combine them with RBAC, NetworkPolicies, image security, audit logging, and additional admission policies where needed.
The best practical default for many application namespaces is:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
Then move workloads toward restricted whenever the application and image are ready.
References
- Kubernetes: Pod Security Standards - kubernetes[.]io/docs/concepts/security/pod-security-standards/
- Kubernetes: Pod Security Admission - kubernetes[.]io/docs/concepts/security/pod-security-admission/
- Kubernetes: Enforce Pod Security Standards with Namespace Labels - kubernetes[.]io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
- Kubernetes: Enforce Pod Security Standards by Configuring the Built-in Admission Controller - kubernetes[.]io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/
- Kubernetes: Pod Security Policies - kubernetes[.]io/docs/concepts/security/pod-security-policy/
- Kubernetes Blog: Pod Security Admission Controller in Stable - kubernetes[.]io/blog/2022/08/25/pod-security-admission-stable/
- Kubernetes Enhancements: KEP-2579 Pod Security Admission Control - github[.]com/kubernetes/enhancements/blob/master/keps/sig-auth/2579-psp-replacement/README.md
- Kyverno: Pod Security Standards - kyverno[.]io/docs/guides/pod-security/