Part ofKubernetes Foundations·Step 4 of 4
DevOps & Platform

Kubernetes RBAC & Security

Advanced60 min to complete17 min readJune 1, 2026Updated August 19, 2026

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.

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
ServiceAccounts
Pod Security
DevOps

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.

yaml
# Create a ServiceAccount for your app
apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-server
  namespace: production
yaml
# Reference it in the Deployment
spec:
  serviceAccountName: api-server
  containers:
    - name: api
      image: myapp:1.0.0
bash
# 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: false

If 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:

ObjectScopeWhat it does
RoleNamespaceDefines allowed operations on namespace-scoped resources
ClusterRoleCluster-wideDefines allowed operations on cluster-scoped resources (nodes, PVs) or shared namespace permissions
RoleBindingNamespaceGrants a Role or ClusterRole to subjects within a namespace
ClusterRoleBindingCluster-wideGrants a ClusterRole to subjects across the whole cluster

Role — namespace-scoped permissions

yaml
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

yaml
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

VerbHTTP equivalentEffect
getGET singleRead one resource
listGET collectionRead all of a resource type
watchGET with watchStream changes
createPOSTCreate a resource
updatePUTReplace a resource
patchPATCHPartially update
deleteDELETEDelete a resource
deletecollectionDELETE collectionDelete many

RoleBinding — grant a Role in a namespace

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

Multiple subjects are supported:

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

Using 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:

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

Built-in ClusterRoles

Kubernetes ships with several ClusterRoles:

NamePermissions
cluster-adminEverything — use sparingly
adminFull namespace control except ResourceQuota
editRead+write most namespace resources
viewRead-only most namespace resources

Checking permissions

bash
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 production

Pod Security — Hardening Containers

securityContext

Security settings live at two levels: the Pod (applies to all containers) and the individual container.

yaml
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:

yaml
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:

LevelWhat it enforces
privilegedNo restrictions
baselinePrevents known privilege escalations (no hostPath, no privileged containers)
restrictedAll baseline + runAsNonRoot, no capabilities, seccomp required

Apply via namespace labels:

bash
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=baseline

Three 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:

yaml
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 secrets

Monitoring agent

A metrics collector needs read access across namespaces but nothing else:

yaml
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:

yaml
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

bash
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.

yaml
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: TCP

Image Security

RBAC and pod security mean nothing if your container image contains vulnerabilities.

bash
# 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.0

Key practices:

  • Use specific image tags — never latest in production
  • Use non-root base images (distroless, -alpine, or add USER in Dockerfile)
  • Scan images in CI before they reach the cluster
  • Set imagePullPolicy: Always if using mutable tags
yaml
spec:
  containers:
    - name: api
      image: myapp:1.2.3        # Specific immutable tag
      imagePullPolicy: IfNotPresent

Frequently 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:

  1. Kubernetes Core Concepts — Pods, Deployments, Services, the reconciliation loop
  2. Kubernetes Networking & Ingress — pod networking, DNS, Network Policies, Ingress
  3. Kubernetes Storage, ConfigMaps & Secrets — configuration management, PVCs, StatefulSets
  4. 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

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.