Cloud Tech
SecurityAdvanced

Kubernetes Security

How RBAC's additive model, Pod Security Standards, and NetworkPolicy fit together, and why each surprises people used to simpler permissions.

Reviewed Jul 28, 2026Victor Nwoke6 min read

Written and maintained by Victor Nwoke. Technical behavior is reviewed against the primary references listed on this page.

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.

I start with Kubernetes Fundamentals if Deployment ownership, Pod readiness, Service selectors, or EndpointSlices are unfamiliar. Security troubleshooting depends on proving the normal workload and network path first; otherwise an empty Service backend can be mistaken for a NetworkPolicy denial, or an admission rejection for an application crash.

Quick Reference

MechanismDefault without any policyWhat applying one does
RBACDeny everythingGrants are additive; there's no explicit deny
Pod Security StandardsNo pod-level restrictionBaseline blocks known escalations; Restricted adds real hardening
NetworkPolicyPods are non-isolated, all traffic allowedSelected pods become isolated; only explicitly allowed traffic gets through
CommandDescriptionCopy
kubectl auth can-i create deployments -n devAsk the API authorization layer whether the current identity can create Deployments in dev.
kubectl auth can-i --list -n devList the current identity's allowed API actions in one namespace.
kubectl get role,rolebinding -n devList namespace-scoped RBAC objects.
kubectl get clusterrole,clusterrolebindingList cluster-scoped RBAC objects and bindings.
kubectl get ns --show-labelsInspect Pod Security admission labels on namespaces.
kubectl get networkpolicy -AList NetworkPolicies across all namespaces.

Syntax

yaml
# 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.io

Examples

yaml
# 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. I test in staging before rolling NetworkPolicy into a previously policy-free namespace.

Verify authorization before changing RBAC

bash
kubectl auth can-i get pods -n production
kubectl auth can-i get pods --subresource=log -n production

# Administrators can test a ServiceAccount, but impersonation itself
# requires permission.
kubectl auth can-i list secrets \
  --as=system:serviceaccount:dev:api \
  -n production

Evaluate Pod Security admission without enforcing it

bash
kubectl label --dry-run=server --overwrite namespace production \
  pod-security.kubernetes.io/enforce=restricted

The server-side dry run evaluates the proposed policy and returns violations without changing the namespace. I use audit and warn modes to discover incompatible workloads before enforcement, then pin the policy version your cluster has tested rather than silently inheriting new rules.

Troubleshooting Security Controls

Step 1: Separate authentication, authorization, and admission

An unauthenticated request fails before RBAC. An authenticated but unauthorized API request usually returns 403 Forbidden; verify it with kubectl auth can-i. A Pod Security admission rejection happens after authorization and explains which Pod fields violate policy. Those are three different failure layers.

Step 2: Trace every RBAC grant

I inspect the subject, Role/ClusterRole rules, and RoleBinding/ClusterRoleBinding scope. Because RBAC grants are additive, finding one correct narrow Role does not prove the identity lacks a broader grant elsewhere. Pay particular attention to wildcard permissions, secret access, workload creation, impersonation, and bindings to cluster-admin.

Step 3: Test NetworkPolicy from the real source namespace

Confirm the destination Service has Ready EndpointSlices first, then run a temporary client Pod in the same namespace and with the same labels as the real caller. I check both ingress policy on the destination and egress policy on the source, including DNS access. Also confirm the cluster network provider actually implements NetworkPolicy.

bash
kubectl get endpointslices \
  -l kubernetes.io/service-name=<service> \
  -n <destination-namespace>
kubectl get networkpolicy -n <source-namespace>
kubectl get networkpolicy -n <destination-namespace>
kubectl run -it --rm policy-test \
  -n <source-namespace> \
  --restart=Never \
  --image=busybox:1.36 -- sh

A timeout is not proof of NetworkPolicy

A selector mismatch, failed readiness probe, wrong targetPort, DNS failure, and NetworkPolicy denial can all look like "the Service timed out." Follow the Service troubleshooting order in Kubernetes Fundamentals before changing policy.

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.
  • Checking only one RoleBinding and missing a broader ClusterRoleBinding that grants the same subject more access.
  • Testing policy from an arbitrary debug Pod whose namespace and labels do not match the real caller, then drawing the wrong conclusion from the result.

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.
  • I use kubectl auth can-i in deployment checks and runbooks to verify effective permissions rather than inferring them from one binding.
  • I avoid wildcard permissions and routine cluster-admin use; new API resources can make an old wildcard grant more powerful over time.
  • Apply the Restricted Pod Security Standard to namespaces running security-critical or lower-trust workloads, and Baseline as the sane default everywhere else.
  • Introduce Pod Security admission through audit/warn evaluation and server-side dry runs before switching a namespace to enforce.
  • 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.

Interview questions

How does Kubernetes RBAC decide whether a request is allowed, and can you write an explicit deny rule?
RBAC is default-deny and purely additive: a request is denied unless some Role/RoleBinding or ClusterRole/ClusterRoleBinding explicitly grants it, and there is no such thing as an explicit deny rule. Every applicable binding's permissions are unioned together, so restricting access means removing a grant (or removing the subject from a binding), not adding a deny statement on top of an existing grant, an approach that works cleanly for grant-based access but has no mechanism for "allow everything except X" within RBAC itself.
What is the difference between the Baseline and Restricted Pod Security Standards levels, and why are they cumulative?
Baseline blocks the most well-known container privilege-escalation paths, privileged containers, host namespaces, hostPath volumes, dangerous Linux capabilities, while still allowing a fairly permissive pod spec otherwise. Restricted inherits every Baseline rule and adds real hardening on top: it requires running as non-root, forbids privilege escalation outright, requires a restricted seccomp profile, and requires dropping all Linux capabilities except NET_BIND_SERVICE. A read-only root filesystem is not part of either standard, it's a separate hardening measure some organizations layer on as their own policy, on top of, not as part of, Restricted. They're cumulative by design, Restricted is Baseline plus more, so a workload that passes Restricted automatically satisfies Baseline too, and a cluster can apply different levels per namespace based on how much a given workload can be trusted.
If no NetworkPolicy exists in a namespace, what traffic is allowed between pods, and what changes the moment one NetworkPolicy is applied?
With no NetworkPolicy at all, pods are non-isolated: every pod can send and receive traffic from any other pod, with no restriction in either direction. The moment any NetworkPolicy selects a pod for a given direction (ingress or egress), that pod becomes isolated for that direction specifically, and only the traffic explicitly allowed by an applicable policy's rules gets through from then on; unrelated pods elsewhere in the cluster that no policy selects remain fully open. This is why introducing NetworkPolicy incrementally, rather than all at once, tends to break things: the first policy applied to a namespace can silently cut off traffic nobody had previously needed to declare.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement