Lock down who can do what with Roles and RoleBindings, give pods scoped identities with ServiceAccounts, restrict traffic with NetworkPolicies, and harden containers with a securityContext.
Why: Role-Based Access Control decides who may do what. A Role is a set of allowed verbs (get, list, create…) on resources within a namespace; a RoleBinding grants that Role to a user, group, or ServiceAccount. The model is additive and deny-by-default: nothing is permitted until a binding grants it. (ClusterRole / ClusterRoleBinding are the cluster-wide versions.)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: staging
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"] # read-only on pods
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: staging
subjects:
- kind: ServiceAccount
name: ci-bot
namespace: staging
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioWhy: a pod that talks to the Kubernetes API does so as a ServiceAccount — its identity. Give each workload its own, bound to the minimum Role it needs, instead of the permissive default. can-i lets you test exactly what an identity is allowed to do.
Create a dedicated identity for a workload
kubectl create serviceaccount ci-bot -n stagingTest what it can and cannot do
kubectl auth can-i list pods \
--as=system:serviceaccount:staging:ci-bot -n stagingWhy: by default every pod can talk to every other pod — flat and open. A NetworkPolicy changes that to allow-list. This one says pods labelled app=db accept traffic only from pods labelled app=web. Note: policies are enforced by the CNI plugin (Calico, Cilium…); on a cluster without one, they are silently ignored.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-web
spec:
podSelector:
matchLabels:
app: db
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels:
app: web # only web pods may reach dbWhy: a container should run with the least privilege it can. A securityContext drops it to a non-root user, makes the root filesystem read-only, and forbids privilege escalation — so a compromised process has far less room to do damage. These few lines block the most common container escapes.
spec:
containers:
- name: app
image: myapp:1.0
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]