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").
ClusterRoleapplies cluster-wide,Roleis namespaced. - RoleBinding/ClusterRoleBinding: Grants the permissions defined in a Role/ClusterRole to a subject (user, group, or service account).
ClusterRoleBindingapplies cluster-wide,RoleBindingis namespaced.
Let’s say we want to give a specific ServiceAccount the ability to list pods only in the default namespace.
-
Create a ServiceAccount:
kubectl create serviceaccount pod-lister-sa -n default -
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 -
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.ioApply 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.
- Apply a NetworkPolicy to the
backendpods:
Apply it: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)kubectl apply -f backend-network-policy.yaml
With this policy in place:
- The
frontendpod can still reach thebackend-serviceon port 80. - If you had another pod (e.g., with
app: database) in the same namespace, it would not be able to reach thebackendpods on port 80. - If you tried to exec into the
backendpod andcurl localhost:8080, it would work. - If you tried to exec into the
frontendpod andcurl backend-service:80, it would work. - If you tried to exec into a different pod (e.g.,
app: worker) andcurl 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.