Overview
Kubernetes security rests on three independent mechanisms that each surprise people used to a simpler model: RBAC is default-deny and purely additive, permissions can only be granted, never explicitly denied, so restricting access means removing a binding, not layering a deny rule on top. Pod Security Standards define three cumulative levels, Privileged, Baseline, Restricted, each adding real hardening (non-root, no privilege escalation, dropped capabilities) on top of the last. NetworkPolicy inverts the surprise the other way: pods are wide open by default with no policy at all, and only become isolated the moment any policy selects them, which is why the very first NetworkPolicy applied to a namespace is the one most likely to unexpectedly break something.
Quick Reference
| Mechanism | Default without any policy | What applying one does |
|---|---|---|
| RBAC | Deny everything | Grants are additive; there's no explicit deny |
| Pod Security Standards | No pod-level restriction | Baseline blocks known escalations; Restricted adds real hardening |
| NetworkPolicy | Pods are non-isolated, all traffic allowed | Selected pods become isolated; only explicitly allowed traffic gets through |
Syntax
# RBAC - grants are additive; to restrict access, remove the
# binding, there's no "deny" rule to add on top of it.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: ci-deploy
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioExamples
# NetworkPolicy - the moment this selects role=db pods, they
# become isolated for ingress; only traffic matching this rule
# is allowed in, everything else that used to reach them is cut off.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-api-only
spec:
podSelector:
matchLabels: { role: db }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels: { role: api }The first NetworkPolicy in a namespace is the riskiest one
Pods are non-isolated until some policy selects them. Applying the first NetworkPolicy to a namespace can silently cut off traffic nobody ever had to declare before, because it was simply allowed by default. Test in staging before rolling NetworkPolicy into a previously policy-free namespace.
Visual Diagram
Common Mistakes
- Trying to write an RBAC "deny" rule; RBAC has no such concept, restricting access means removing the grant, not layering a denial on top of it.
- Applying the Restricted Pod Security Standard cluster-wide without testing, breaking workloads that legitimately needed a capability Baseline still allowed.
- Assuming pods are isolated by default and skipping NetworkPolicy entirely, when the actual default is fully open pod-to-pod traffic.
- Applying a single NetworkPolicy to a namespace and being surprised when unrelated traffic breaks, not realizing that policy selected pods well beyond its intended scope.
Performance
- RBAC authorization checks happen on the API request path but are lightweight lookups against already-loaded bindings; the real cost is the audit and design effort of keeping bindings correct, not runtime latency.
- NetworkPolicy enforcement is implemented by the cluster's CNI plugin, not the API server itself, and its performance characteristics (rule-matching overhead per packet) vary by CNI implementation and policy count.
Best Practices
- Scope RBAC bindings as narrowly as possible (a Role in one namespace) and reserve ClusterRole/ClusterRoleBinding for genuinely cluster-wide needs.
- Apply the Restricted Pod Security Standard to namespaces running security-critical or lower-trust workloads, and Baseline as the sane default everywhere else.
- Introduce NetworkPolicy deliberately and incrementally, starting with a namespace's own traffic map, rather than applying a broad policy and reacting to what breaks.
- Default every namespace to at least a basic deny-by-default NetworkPolicy plus explicit allows, rather than leaving pod-to-pod traffic fully open indefinitely.