Kubernetes Security: PSA and Network Policies [2026]
Bottom Line
Use Pod Security Admission to stop unsafe pod specs at the API boundary, then use NetworkPolicy to constrain east-west traffic between workloads. The two controls solve different problems and are strongest when rolled out together, namespace by namespace.
Key Takeaways
- ›Pod Security Admission is built in and stable from Kubernetes 1.25 onward.
- ›Namespace labels control PSA modes: enforce, audit, and warn.
- ›A default-deny ingress policy isolates pods until you add explicit allow rules.
- ›NetworkPolicy does nothing unless your CNI plugin enforces it.
Securing a Kubernetes cluster is easier when you separate admission-time controls from runtime traffic controls. Pod Security Admission evaluates pod specs before they are stored, while NetworkPolicy restricts which pods can talk to each other after scheduling. This tutorial shows a practical rollout: apply Pod Security Admission labels to one namespace, then add a default-deny ingress policy with a single explicit allow path.
- Pod Security Admission is built in and stable from Kubernetes 1.25 onward.
- Namespace labels drive enforce, audit, and warn behavior.
- A default-deny ingress policy blocks lateral access until you open only what is needed.
- Your CNI plugin must implement NetworkPolicy or traffic will remain unrestricted.
Prerequisites
Before you start
- A cluster running a current Kubernetes release with Pod Security Admission enabled. It is included by default in standard upstream control planes.
- kubectl configured against the cluster.
- A CNI plugin that enforces NetworkPolicy. Creating policies without enforcement support has no practical effect.
- Permission to create namespaces, pods, deployments, services, and network policies.
- A non-production namespace for validation. This tutorial uses
secure-demo.
Bottom Line
Pod Security Admission blocks unsafe pod specs before they land, and NetworkPolicy blocks unnecessary east-west traffic after they run. You want both.
Implement Pod Security Admission
1. Label a namespace for a safe first rollout
The fastest mistake with Pod Security Admission is jumping straight to restricted enforcement on a busy namespace. A safer pattern is to enforce the baseline profile while using audit and warn for restricted. The latest label value is supported for version labels, so you do not have to hard-code a minor release number in this tutorial.
apiVersion: v1
kind: Namespace
metadata:
name: secure-demo
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestkubectl apply -f namespace.yaml
kubectl get ns secure-demo --show-labelsThat gives you an immediate control boundary for obviously unsafe pods while surfacing stricter restricted gaps in warnings and audit output.
2. Prove the admission check works
Now create a pod that should be rejected by the baseline policy because it requests a privileged container.
apiVersion: v1
kind: Pod
metadata:
name: privileged-test
namespace: secure-demo
spec:
containers:
- name: shell
image: busybox:1.36
command:
- sh
- -c
- sleep 3600
securityContext:
privileged: truekubectl apply -f privileged-test.yamlYou should see a forbidden response from the API server referencing a PodSecurity violation. That validates the admission control path before you touch production namespaces.
For real rollouts, dry-run first and review warnings. If you need to paste manifests or warning output into tickets, scrub cluster-specific names and addresses with TechBytes' Data Masking Tool.
Lock Down East-West Traffic
3. Deploy a tiny app and two client pods
Next, create one server pod behind a service, one allowed client, and one client that should be blocked. This keeps verification simple and makes the effect of the policy obvious.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: secure-demo
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.k8s.io/e2e-test-images/agnhost:2.53
args:
- netexec
- --http-port=8080
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: secure-demo
spec:
selector:
app: api
ports:
- port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
name: client-allowed
namespace: secure-demo
labels:
access: granted
spec:
containers:
- name: client
image: busybox:1.36
command:
- sh
- -c
- sleep 3600
---
apiVersion: v1
kind: Pod
metadata:
name: client-blocked
namespace: secure-demo
spec:
containers:
- name: client
image: busybox:1.36
command:
- sh
- -c
- sleep 3600kubectl apply -f app-and-clients.yaml
kubectl wait --for=condition=Available deployment/api -n secure-demo --timeout=90s
kubectl get pods -n secure-demo4. Add default-deny ingress and one allow rule
A pod becomes isolated for ingress when at least one policy selects it. The cleanest pattern is a namespace-wide default deny, then a narrow allow policy for the application port and source selector you actually need.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: secure-demo
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-from-granted-clients
namespace: secure-demo
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
access: granted
ports:
- protocol: TCP
port: 8080kubectl apply -f network-policies.yaml
kubectl get networkpolicy -n secure-demoThis policy set does exactly two things:
- It isolates every pod in the namespace for ingress by default.
- It allows only pods labeled
access: grantedto reach pods labeledapp: apion TCP 8080.
Verification and Expected Output
- Confirm the namespace labels are present.
kubectl get ns secure-demo --show-labelsExpected result: the namespace shows the six pod-security.kubernetes.io/* labels you applied.
- Confirm the privileged pod is rejected.
kubectl apply -f privileged-test.yamlExpected result: the API server returns a forbidden error mentioning a PodSecurity violation.
- Test the allowed path.
kubectl exec -n secure-demo client-allowed -- nc -vz -w 3 api 8080Expected result: the TCP connection succeeds.
- Test the blocked path.
kubectl exec -n secure-demo client-blocked -- nc -vz -w 3 api 8080Expected result: the command times out or exits non-zero because the ingress policy blocks the connection.
Troubleshooting Top 3
1. Existing workloads throw warnings after labeling the namespace
This is normal when you add warn or audit labels. Treat the warnings as a migration queue:
- Review which controls fail most often, usually runAsNonRoot, seccompProfile, or privilege escalation settings.
- Apply labels first in a lower environment, then repeat in production.
- Move to stronger enforcement only after the namespace is clean under the stricter profile.
2. NetworkPolicy applied, but traffic still flows
This nearly always points to enforcement, not YAML syntax.
- Verify your CNI plugin supports NetworkPolicy.
- Check that the policy selects the right destination pods with
podSelector. - Check that the source pod labels actually match the
fromrule.
3. You later add default-deny egress and DNS breaks
That is expected unless you allow DNS explicitly.
- Add an egress rule for your cluster DNS pods or DNS service path.
- Re-test name resolution before concluding the app itself is broken.
- When sharing failing manifests or logs outside the team, sanitize them with the Data Masking Tool so internal hostnames and addresses do not leak.
What's Next
This tutorial establishes a strong baseline, but it should not be your stopping point. The next hardening pass is usually straightforward:
- Move high-confidence namespaces from baseline enforcement to restricted enforcement.
- Add default-deny egress with explicit allowances for DNS, telemetry, and required upstream services.
- Pair admission and network controls with RBAC, image signing or policy checks, and secret management.
- Automate validation in CI so unsafe manifests never reach the cluster API in the first place.
The important architectural point is simple: Pod Security Admission decides what may run, and NetworkPolicy decides who may talk. Roll out both controls namespace by namespace, verify behavior with small tests, and the cluster becomes materially harder to misuse or move through laterally.
Frequently Asked Questions
What replaced PodSecurityPolicy in Kubernetes? +
PodSecurityPolicy approach. Instead of maintaining custom policy objects, you label namespaces with enforce, audit, and warn modes tied to the Pod Security Standards.Does NetworkPolicy work on every Kubernetes cluster? +
Can Pod Security Admission block a Deployment? +
Deployment with a non-compliant template will fail when Kubernetes tries to create the underlying pods. In practice, you experience it as the workload not starting because pod creation is rejected.Why can pods still talk to each other after I created one policy? +
Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.