Kubernetes RBAC, PSP, and Network Policies aren’t just security features; they’re fundamental to shaping the trust and communication model within your cluster, and understanding their interplay is key to truly hardening your environment.

Let’s see this in action. Imagine a simple application with two pods: frontend and backend. The frontend needs to talk to the backend on port 8080.

First, we’ll create a Service for the backend:

apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

Now, a Deployment for the backend pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend-container
        image: nginx:latest # Using nginx as a placeholder for a simple web server
        ports:
        - containerPort: 8080

And for the frontend pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend-container
        image: busybox # Using busybox to curl the backend
        command: ["/bin/sh", "-c", "while true; do wget -O- http://backend-service:80/ && sleep 30; done"]

By default, without any specific policies, the frontend pod can successfully reach the backend-service on port 80. This is because Kubernetes’ default network behavior is to allow all pods to communicate with all other pods.

Role-Based Access Control (RBAC): Who Can Do What?

RBAC is about managing permissions for users and service accounts. It defines who can perform what actions on which resources.

  • Role/ClusterRole: Defines a set of permissions (e.g., "can list pods," "can create deployments"). ClusterRole applies cluster-wide, Role is namespaced.
  • RoleBinding/ClusterRoleBinding: Grants the permissions defined in a Role/ClusterRole to a subject (user, group, or service account). ClusterRoleBinding applies cluster-wide, RoleBinding is namespaced.

Let’s say we want to give a specific ServiceAccount the ability to list pods only in the default namespace.

  1. Create a ServiceAccount:

    kubectl create serviceaccount pod-lister-sa -n default
    
  2. Create a Role:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: default
      name: pod-reader
    rules:
    - apiGroups: [""] # "" indicates the core API group
      resources: ["pods"]
      verbs: ["get", "list", "watch"]
    

    Apply it:

    kubectl apply -f role.yaml
    
  3. Create a RoleBinding:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: read-pods
      namespace: default
    subjects:
    - kind: ServiceAccount
      name: pod-lister-sa
      namespace: default
    roleRef:
      kind: Role
      name: pod-reader
      apiGroup: rbac.authorization.k8s.io
    

    Apply it:

    kubectl apply -f role-binding.yaml
    

Now, any pod using the pod-lister-sa service account can list pods in the default namespace. If you try to list pods in another namespace without appropriate permissions, it will fail.

Pod Security Policies (PSPs): What Pods Are Allowed To Do

PSPs (now deprecated in favor of Pod Security Admission, but the concepts are similar) enforced security best practices at the pod creation level. They controlled things like:

  • Whether pods could run as root.
  • Whether pods could mount host paths.
  • Whether pods could use privileged containers.
  • Allowed volume types.

Let’s imagine a PSP that prevents privileged containers and root execution:

apiVersion: policy/v1beta1 # Note: PSP API version is deprecated
kind: PodSecurityPolicy
metadata:
  name: restricted-psp
spec:
  privileged: false
  # Prevent root user from running
  runAsUser:
    rule: MustRunAsNonRoot
  # Allow only specific capabilities, disallow all others
  allowedCapabilities:
  - NET_BIND_SERVICE
  # Disallow SELinux context, but allow if the container is already configured
  seLinux:
    rule: RunAsAny
  # Allow read-only root filesystem
  readOnlyRootFilesystem: true
  # Allowed host paths (empty means none allowed)
  volumes:
  - configMap
  - emptyDir
  - projected
  - secret
  - downwardAPI
  - persistentVolumeClaim

To enforce this PSP, you would need a Role or ClusterRole that allows use on this PSP and a RoleBinding or ClusterRoleBinding to link it to a ServiceAccount or User/Group.

Crucially, PSPs don’t apply by default. You must explicitly grant permission for a pod (via its service account) to use a specific PSP.

Network Policies: Who Can Talk To Whom

Network Policies control traffic flow at the IP address or port level. They are like firewalls for your pods.

  • Namespace-scoped: Network Policies are namespaced resources.
  • Default-deny: If no Network Policy selects a pod, all ingress and egress traffic is allowed. If any Network Policy selects a pod, all traffic to that pod is denied by default, and only traffic explicitly allowed by a policy is permitted.

Let’s implement the scenario where only the frontend can talk to the backend on port 80.

  1. Apply a NetworkPolicy to the backend pods:
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: backend-allow-frontend
      namespace: default
    spec:
      podSelector:
        matchLabels:
          app: backend # This policy applies to pods with the label app: backend
      policyTypes:
      - Ingress # We are defining ingress rules for backend pods
      ingress:
      - from:
        - podSelector:
            matchLabels:
              app: frontend # Allow traffic only from pods with the label app: frontend
        ports:
        - protocol: TCP
          port: 80 # Allow traffic on port 80 (where the backend service exposes its port)
    
    Apply it:
    kubectl apply -f backend-network-policy.yaml
    

With this policy in place:

  • The frontend pod can still reach the backend-service on port 80.
  • If you had another pod (e.g., with app: database) in the same namespace, it would not be able to reach the backend pods on port 80.
  • If you tried to exec into the backend pod and curl localhost:8080, it would work.
  • If you tried to exec into the frontend pod and curl backend-service:80, it would work.
  • If you tried to exec into a different pod (e.g., app: worker) and curl backend-service:80, it would fail.

The interplay is critical: RBAC determines who can create/modify these policies and who can run pods with specific ServiceAccounts. PSPs ensure that the pods themselves are created with a secure baseline. Network Policies then dictate the runtime communication between these pods. A common pitfall is forgetting that Network Policies are opt-in for traffic allowance once a pod is selected by any policy, leading to unintended network isolation.

The next step in hardening is often implementing admission controllers beyond PSPs, such as OPA/Gatekeeper or Kyverno, for more complex policy enforcement across your cluster.

Want structured learning?

Take the full Cdk course →