Kubernetes RBAC & Security
Quick answer
Lock down your cluster — ServiceAccounts, Roles, RoleBindings, pod security contexts, Network Policies as a security control, and the principle of least privilege applied to Kubernetes workloads.
- Authentication vs Authorisation
- ServiceAccounts — Identity for Pods
- RBAC — Roles and Bindings
- Pod Security — Hardening Containers
- Least Privilege Patterns
advanced · 60 min
Before you begin
- Kubernetes core concepts — Pods, Deployments, Services, Namespaces
- Kubernetes networking — Network Policies
- Basic understanding of Linux users and file permissions
Kubernetes RBAC & Security
A default Kubernetes cluster is permissive — Pods can talk to each other freely, service accounts have broad permissions, and containers often run as root. This tutorial covers the controls that tighten that down: RBAC for API access, pod security contexts for container hardening, and the defence-in-depth patterns used in production clusters.
Authentication vs Authorisation
Kubernetes separates who you are from what you can do:
- Authentication — verifying identity. Kubernetes delegates this: client certificates (kubeconfig), OIDC tokens (Okta, Google, Dex), bearer tokens. Kubernetes has no built-in user management.
- Authorisation — what authenticated identities can do. This is RBAC.
Two types of identities:
- Human users — engineers using
kubectl, CI systems. Managed outside Kubernetes. - ServiceAccounts — identities for Pods. Managed by Kubernetes.
ServiceAccounts — Identity for Pods
Every Pod runs as a ServiceAccount. If you don't specify one, it uses the default ServiceAccount in its namespace. The default SA has minimal permissions by default, but you can grant it more — which is why you should always create a dedicated SA for each workload.
A JWT token for the ServiceAccount is automatically mounted into every Pod at /var/run/secrets/kubernetes.io/serviceaccount/token. The Pod uses this to authenticate to the Kubernetes API.
# Create a ServiceAccount for your app
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-server
namespace: production# Reference it in the Deployment
spec:
serviceAccountName: api-server
containers:
- name: api
image: myapp:1.0.0# See which SA a pod is using
kubectl get pod <name> -o jsonpath='{.spec.serviceAccountName}'
# Disable token mounting if the pod doesn't need API access
spec:
automountServiceAccountToken: falseIf a pod doesn't call the Kubernetes API, set automountServiceAccountToken: false. This removes the token from the container's filesystem — no token means no API access even if the container is compromised.
RBAC — Roles and Bindings
RBAC answers: "Can subject X perform verb Y on resource Z?"
Four objects:
| Object | Scope | What it does |
|---|---|---|
Role | Namespace | Defines allowed operations on namespace-scoped resources |
ClusterRole | Cluster-wide | Defines allowed operations on cluster-scoped resources (nodes, PVs) or shared namespace permissions |
RoleBinding | Namespace | Grants a Role or ClusterRole to subjects within a namespace |
ClusterRoleBinding | Cluster-wide | Grants a ClusterRole to subjects across the whole cluster |
Role — namespace-scoped permissions
1apiVersion: rbac.authorization.k8s.io/v1
2kind: Role
3metadata:
4 name: pod-reader
5 namespace: production
6rules:
7 - apiGroups: [""] # "" = core API group (pods, services, configmaps, secrets)
8 resources: ["pods", "pods/log"]
9 verbs: ["get", "list", "watch"]
10 - apiGroups: ["apps"] # Deployments, ReplicaSets, StatefulSets
11 resources: ["deployments"]
12 verbs: ["get", "list", "watch"]ClusterRole — cluster-wide or shared permissions
1apiVersion: rbac.authorization.k8s.io/v1
2kind: ClusterRole
3metadata:
4 name: node-reader
5rules:
6 - apiGroups: [""]
7 resources: ["nodes", "nodes/metrics"]
8 verbs: ["get", "list", "watch"]
9 - apiGroups: ["metrics.k8s.io"]
10 resources: ["nodes", "pods"]
11 verbs: ["get", "list"]Common verbs
| Verb | HTTP equivalent | Effect |
|---|---|---|
get | GET single | Read one resource |
list | GET collection | Read all of a resource type |
watch | GET with watch | Stream changes |
create | POST | Create a resource |
update | PUT | Replace a resource |
patch | PATCH | Partially update |
delete | DELETE | Delete a resource |
deletecollection | DELETE collection | Delete many |
RoleBinding — grant a Role in a namespace
1apiVersion: rbac.authorization.k8s.io/v1
2kind: RoleBinding
3metadata:
4 name: api-server-binding
5 namespace: production
6subjects:
7 - kind: ServiceAccount
8 name: api-server
9 namespace: production
10roleRef:
11 kind: Role
12 name: pod-reader
13 apiGroup: rbac.authorization.k8s.ioMultiple subjects are supported:
1subjects:
2 - kind: ServiceAccount
3 name: monitoring-agent
4 namespace: monitoring
5 - kind: User
6 name: [email protected] # Human user (identity from OIDC)
7 apiGroup: rbac.authorization.k8s.io
8 - kind: Group
9 name: platform-team # Group from OIDC
10 apiGroup: rbac.authorization.k8s.ioUsing a ClusterRole within one namespace
A ClusterRole can be bound in a namespace using a RoleBinding (not ClusterRoleBinding). This is useful for defining reusable role templates:
1# Bind the cluster-wide "view" ClusterRole to a SA, but only in staging
2apiVersion: rbac.authorization.k8s.io/v1
3kind: RoleBinding
4metadata:
5 name: ci-bot-view
6 namespace: staging
7subjects:
8 - kind: ServiceAccount
9 name: ci-bot
10 namespace: staging
11roleRef:
12 kind: ClusterRole # ClusterRole, not Role
13 name: view # Built-in read-only ClusterRole
14 apiGroup: rbac.authorization.k8s.ioBuilt-in ClusterRoles
Kubernetes ships with several ClusterRoles:
| Name | Permissions |
|---|---|
cluster-admin | Everything — use sparingly |
admin | Full namespace control except ResourceQuota |
edit | Read+write most namespace resources |
view | Read-only most namespace resources |
Checking permissions
1# Can I do X?
2kubectl auth can-i create deployments
3kubectl auth can-i delete pods -n production
4
5# Can a specific ServiceAccount do X?
6kubectl auth can-i list secrets \
7 --as=system:serviceaccount:production:api-server \
8 -n productionPod Security — Hardening Containers
securityContext
Security settings live at two levels: the Pod (applies to all containers) and the individual container.
1spec:
2 securityContext:
3 runAsNonRoot: true # Reject if container tries to run as root
4 runAsUser: 1001 # UID for all containers
5 runAsGroup: 1001 # GID for all containers
6 fsGroup: 2000 # GID for mounted volumes (files owned by 2000)
7 seccompProfile:
8 type: RuntimeDefault # Apply the container runtime's default seccomp profile
9 containers:
10 - name: api
11 image: myapp:1.0.0
12 securityContext:
13 allowPrivilegeEscalation: false # Prevent sudo / setuid binaries
14 readOnlyRootFilesystem: true # Container filesystem is immutable
15 capabilities:
16 drop: ["ALL"] # Drop all Linux capabilities
17 add: ["NET_BIND_SERVICE"] # Only add back what's needed (port < 1024)readOnlyRootFilesystem: true is highly effective — it means any exploit that tries to write malware to disk or modify binaries will fail. If your app needs to write files, mount an explicit writable volume:
1volumeMounts:
2 - name: tmp
3 mountPath: /tmp
4 - name: cache
5 mountPath: /app/cache
6volumes:
7 - name: tmp
8 emptyDir: {}
9 - name: cache
10 emptyDir: {}Pod Security Admission (K8s 1.25+)
Pod Security Admission enforces security standards at the namespace level. Three built-in profiles:
| Level | What it enforces |
|---|---|
privileged | No restrictions |
baseline | Prevents known privilege escalations (no hostPath, no privileged containers) |
restricted | All baseline + runAsNonRoot, no capabilities, seccomp required |
Apply via namespace labels:
1# Enforce restricted for production — reject non-compliant pods
2kubectl label namespace production pod-security.kubernetes.io/enforce=restricted
3
4# Warn for staging — allow but warn
5kubectl label namespace staging pod-security.kubernetes.io/warn=restricted
6
7# Audit — allow but log
8kubectl label namespace dev pod-security.kubernetes.io/audit=baselineThree modes: enforce (reject), warn (allow + warn), audit (allow + log). You can set all three independently per namespace.
Least Privilege Patterns
CI/CD service account
A CI system that deploys to a namespace needs only what it actually uses:
1apiVersion: rbac.authorization.k8s.io/v1
2kind: Role
3metadata:
4 name: deployer
5 namespace: production
6rules:
7 - apiGroups: ["apps"]
8 resources: ["deployments"]
9 verbs: ["get", "list", "patch", "update"]
10 - apiGroups: [""]
11 resources: ["services", "configmaps"]
12 verbs: ["get", "list", "create", "update", "patch"]
13 - apiGroups: [""]
14 resources: ["secrets"]
15 verbs: ["get", "list"] # Read only — CI shouldn't create secretsMonitoring agent
A metrics collector needs read access across namespaces but nothing else:
1apiVersion: rbac.authorization.k8s.io/v1
2kind: ClusterRole
3metadata:
4 name: metrics-reader
5rules:
6 - apiGroups: [""]
7 resources: ["pods", "nodes", "services", "endpoints", "namespaces"]
8 verbs: ["get", "list", "watch"]
9 - apiGroups: ["metrics.k8s.io"]
10 resources: ["pods", "nodes"]
11 verbs: ["get", "list"]Application that reads its own ConfigMap
Most application pods don't need any Kubernetes API access at all. If yours does — say, to read a ConfigMap for dynamic config — scope it tightly:
kind: Role
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # Only this specific ConfigMap
verbs: ["get", "watch"]resourceNames restricts access to specific named resources, not the entire type.
Auditing RBAC
1# See all RoleBindings in a namespace
2kubectl get rolebindings -n production -o wide
3
4# See all ClusterRoleBindings
5kubectl get clusterrolebindings -o wide
6
7# Find everything bound to a ServiceAccount
8kubectl get rolebindings,clusterrolebindings -A \
9 -o jsonpath='{range .items[?(@.subjects[*].name=="api-server")]}{.metadata.name}{"\t"}{.metadata.namespace}{"\n"}{end}'
10
11# Check for overly broad permissions — cluster-admin bindings
12kubectl get clusterrolebindings -o json | \
13 jq '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name'Network Policies as a Security Control
RBAC controls API access. Network Policies control network access. Defence-in-depth requires both.
A compromised pod with broad network access can probe other services, reach the metadata API, or exfiltrate data. Locking down egress limits the blast radius.
1# Default deny all, then allow explicitly
2apiVersion: networking.k8s.io/v1
3kind: NetworkPolicy
4metadata:
5 name: api-policy
6 namespace: production
7spec:
8 podSelector:
9 matchLabels:
10 app: api
11 policyTypes:
12 - Ingress
13 - Egress
14 ingress:
15 - from:
16 - namespaceSelector:
17 matchLabels:
18 kubernetes.io/metadata.name: ingress-nginx # Only from ingress controller
19 ports:
20 - port: 3000
21 egress:
22 - to:
23 - podSelector:
24 matchLabels:
25 app: db # Allow to database
26 ports:
27 - port: 5432
28 - ports: # Allow DNS
29 - port: 53
30 protocol: UDP
31 - port: 53
32 protocol: TCPImage Security
RBAC and pod security mean nothing if your container image contains vulnerabilities.
# Scan an image with trivy (free, fast)
brew install trivy
trivy image myapp:1.0.0
# Scan for critical and high only
trivy image --severity CRITICAL,HIGH myapp:1.0.0Key practices:
- Use specific image tags — never
latestin production - Use non-root base images (distroless,
-alpine, or addUSERin Dockerfile) - Scan images in CI before they reach the cluster
- Set
imagePullPolicy: Alwaysif using mutable tags
spec:
containers:
- name: api
image: myapp:1.2.3 # Specific immutable tag
imagePullPolicy: IfNotPresentFrequently Asked Questions
What is the difference between authentication and authorisation here?
Authentication establishes who you are — a certificate, a token, an OIDC identity — and Kubernetes largely delegates it. Authorisation decides what that identity may do, which is RBAC's job. A valid identity with no bindings authenticates successfully and is then denied everything, which reads as a confusing error.
How do I check what a ServiceAccount can actually do?
Use kubectl auth can-i with impersonation, which asks the API server the same question a real request would. That accounts for every binding that applies, including ones inherited from a ClusterRole you had forgotten. Reading Role definitions is guesswork by comparison.
Why is my default ServiceAccount token mounted when I do not use it?
Because automounting is on by default. A pod that never calls the API still carries a credential an attacker could use. Disable automounting on the ServiceAccount or the pod spec unless the workload genuinely talks to the API — this is one of the cheapest hardening steps available.
What is the most common RBAC mistake?
Binding a ClusterRole with a ClusterRoleBinding when a RoleBinding was intended. The same ClusterRole bound with a RoleBinding grants its permissions only in one namespace; bound cluster-wide it grants them everywhere. That one-word difference is how read access to a namespace becomes read access to every Secret in the cluster.
What's Next
You've completed the Kubernetes Foundations learning path:
- Kubernetes Core Concepts — Pods, Deployments, Services, the reconciliation loop
- Kubernetes Networking & Ingress — pod networking, DNS, Network Policies, Ingress
- Kubernetes Storage, ConfigMaps & Secrets — configuration management, PVCs, StatefulSets
- Kubernetes RBAC & Security (this tutorial) — ServiceAccounts, Roles, pod hardening
Next: CI/CD and GitOps. Deploying to Kubernetes manually with kubectl apply is only the beginning. The Platform Engineering Roadmap covers Helm for packaging, GitHub Actions for CI, and ArgoCD for continuous delivery.
Official References
- RBAC authorization — Role, ClusterRole and binding semantics
- Configure service accounts — token projection and the default service account
We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.
Struggling with this in production?
We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.