CKA — RBAC & Security Lab

5 minute read

Lab Overview

Time to put everything together. In this lab, you’ll create Roles, RoleBindings, ServiceAccounts, and Security Contexts from scratch — just like on the CKA exam.

Prerequisites

  • A running Kubernetes cluster (minikube, kind, or kubeadm)
  • kubectl configured and connected
  • Cluster admin access (to create Roles and Bindings)

What You’ll Practice

  • Creating namespaces, Roles, and RoleBindings
  • Binding Roles to users and ServiceAccounts
  • Testing permissions with kubectl auth can-i
  • Deploying Pods with ServiceAccounts
  • Applying Security Contexts to Pods
  • Creating ClusterRoles and ClusterRoleBindings

Let’s go.

Task 1: Create a Namespace and Role

Step 1: Create the namespace

kubectl create namespace secure-ns

Expected output:

namespace/secure-ns created

Step 2: Verify the namespace

kubectl get namespace secure-ns

Expected output:

NAME        STATUS   AGE
secure-ns   Active   5s

Step 3: Create a Role that allows reading Pods

kubectl create role pod-reader \
  --verb=get,list,watch \
  --resource=pods \
  -n secure-ns

Expected output:

role.rbac.authorization.k8s.io/pod-reader created

Step 4: Verify the Role

kubectl describe role pod-reader -n secure-ns

Expected output:

Name:         pod-reader
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [get list watch]

You now have a Role that grants read-only access to Pods in the secure-ns namespace.

Task 2: Create a RoleBinding

Step 1: Bind the Role to user “dev-user” (imperative)

kubectl create rolebinding dev-read \
  --role=pod-reader \
  --user=dev-user \
  -n secure-ns

Expected output:

rolebinding.rbac.authorization.k8s.io/dev-read created

Step 2: Verify the RoleBinding

kubectl describe rolebinding dev-read -n secure-ns

Expected output:

Name:         dev-read
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  pod-reader
Subjects:
  Kind  Name      Namespace
  ----  ----      ---------
  User  dev-user

Step 3: Create the same binding declaratively (for practice)

Save this to a file and apply it — or just review the structure:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-read-declarative
  namespace: secure-ns
subjects:
- kind: User
  name: dev-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f rolebinding.yaml

Task 3: Test Permissions

Step 1: Test if dev-user can list Pods

kubectl auth can-i list pods --as dev-user -n secure-ns

Expected output:

yes

Step 2: Test if dev-user can create Pods (should fail)

kubectl auth can-i create pods --as dev-user -n secure-ns

Expected output:

no

Step 3: Test if dev-user can list Pods in default namespace (should fail)

kubectl auth can-i list pods --as dev-user -n default

Expected output:

no

The RoleBinding only grants access in secure-ns — not in other namespaces.

Step 4: List all permissions for dev-user in secure-ns

kubectl auth can-i --list --as dev-user -n secure-ns

Expected output should include:

Resources   Non-Resource URLs   Resource Names   Verbs
pods        []                  []               [get list watch]
...

Task 4: ServiceAccount RBAC

This task walks through the complete SA → Role → RoleBinding → Pod pattern.

Step 1: Create a ServiceAccount

kubectl create serviceaccount app-sa -n secure-ns

Expected output:

serviceaccount/app-sa created

Step 2: Create a Role for the ServiceAccount

kubectl create role configmap-reader \
  --verb=get,list \
  --resource=configmaps \
  -n secure-ns

Expected output:

role.rbac.authorization.k8s.io/configmap-reader created

Step 3: Bind the Role to the ServiceAccount

kubectl create rolebinding app-sa-binding \
  --role=configmap-reader \
  --serviceaccount=secure-ns:app-sa \
  -n secure-ns

Expected output:

rolebinding.rbac.authorization.k8s.io/app-sa-binding created

Step 4: Test the ServiceAccount’s permissions

# Can it list ConfigMaps?
kubectl auth can-i list configmaps --as system:serviceaccount:secure-ns:app-sa -n secure-ns

Expected output:

yes
# Can it list Pods? (should be no)
kubectl auth can-i list pods --as system:serviceaccount:secure-ns:app-sa -n secure-ns

Expected output:

no

Step 5: Create a Pod using the ServiceAccount

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
  namespace: secure-ns
spec:
  serviceAccountName: app-sa
  containers:
  - name: app
    image: bitnami/kubectl
    command: ["sleep", "3600"]
kubectl apply -f app-pod.yaml

Expected output:

pod/app-pod created

Step 6: Verify the Pod is using the correct ServiceAccount

kubectl get pod app-pod -n secure-ns -o jsonpath='{.spec.serviceAccountName}'

Expected output:

app-sa

Step 7: Test from inside the Pod

# Create a ConfigMap to test against
kubectl create configmap test-config --from-literal=key=value -n secure-ns

# Exec into the Pod and try listing ConfigMaps
kubectl exec app-pod -n secure-ns -- kubectl get configmaps -n secure-ns

Expected output (should succeed):

NAME               DATA   AGE
kube-root-ca.crt   1      5m
test-config        1      10s
# Try listing Pods (should fail)
kubectl exec app-pod -n secure-ns -- kubectl get pods -n secure-ns

Expected output:

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:secure-ns:app-sa" cannot list resource "pods"...

This confirms RBAC is working correctly.

Task 5: Security Contexts

Step 1: Deploy a Pod with Security Contexts

apiVersion: v1
kind: Pod
metadata:
  name: secure-context-pod
  namespace: secure-ns
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: app
    image: busybox
    command: ["sleep", "3600"]
    securityContext:
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
    volumeMounts:
    - name: writable-tmp
      mountPath: /tmp
  volumes:
  - name: writable-tmp
    emptyDir: {}
kubectl apply -f secure-context-pod.yaml

Expected output:

pod/secure-context-pod created

Step 2: Verify the user ID

kubectl exec secure-context-pod -n secure-ns -- id

Expected output:

uid=1000 gid=3000 groups=2000

The process is running as UID 1000, not root.

Step 3: Verify read-only root filesystem

kubectl exec secure-context-pod -n secure-ns -- touch /test-file

Expected output:

touch: /test-file: Read-only file system
command terminated with exit code 1

Step 4: Verify writable tmp directory

kubectl exec secure-context-pod -n secure-ns -- touch /tmp/test-file
kubectl exec secure-context-pod -n secure-ns -- ls -la /tmp/test-file

Expected output:

-rw-r--r--    1 1000     2000             0 Feb  9 06:00 /tmp/test-file

The emptyDir volume is writable, and the file is owned by UID 1000 and GID 2000 (fsGroup).

Step 5: Verify no privilege escalation

kubectl get pod secure-context-pod -n secure-ns -o jsonpath='{.spec.containers[0].securityContext.allowPrivilegeEscalation}'

Expected output:

false

Task 6: ClusterRole and ClusterRoleBinding

Step 1: Create a ClusterRole for node access

kubectl create clusterrole node-viewer \
  --verb=get,list,watch \
  --resource=nodes

Expected output:

clusterrole.rbac.authorization.k8s.io/node-viewer created

Step 2: Create a ClusterRoleBinding

kubectl create clusterrolebinding ops-node-viewer \
  --clusterrole=node-viewer \
  --user=ops-user

Expected output:

clusterrolebinding.rbac.authorization.k8s.io/ops-node-viewer created

Step 3: Test the permissions

# Can ops-user list nodes?
kubectl auth can-i list nodes --as ops-user

Expected output:

yes
# Can ops-user delete nodes? (should be no)
kubectl auth can-i delete nodes --as ops-user

Expected output:

no
# Can ops-user list pods? (should be no — nodes only)
kubectl auth can-i list pods --as ops-user

Expected output:

no

Step 4: Verify ops-user can actually list nodes

kubectl get nodes --as ops-user

Expected output:

NAME            STATUS   ROLES           AGE   VERSION
control-plane   Ready    control-plane   30d   v1.28.0

Cleanup

When you’re done, clean up all resources:

# Delete the namespace (this removes all namespaced resources within it)
kubectl delete namespace secure-ns

Expected output:

namespace "secure-ns" deleted
# Delete cluster-scoped resources
kubectl delete clusterrole node-viewer
kubectl delete clusterrolebinding ops-node-viewer

Expected output:

clusterrole.rbac.authorization.k8s.io "node-viewer" deleted
clusterrolebinding.rbac.authorization.k8s.io "ops-node-viewer" deleted

Note: Deleting the namespace automatically removes all Roles, RoleBindings, ServiceAccounts, and Pods within it. Cluster-scoped resources (ClusterRoles, ClusterRoleBindings) must be deleted separately.

Lab Summary

In this lab you practiced:

Task What You Did
Task 1 Created a namespace and a Role with read-only Pod access
Task 2 Created a RoleBinding to grant permissions to a user
Task 3 Used kubectl auth can-i to verify permissions work correctly
Task 4 Full ServiceAccount RBAC workflow: SA → Role → RoleBinding → Pod
Task 5 Applied Security Contexts: runAsUser, readOnlyRootFilesystem, fsGroup
Task 6 Created a ClusterRole and ClusterRoleBinding for node access

These patterns come up frequently on the CKA exam. Practice them until you can do them from memory — speed matters on exam day.

Updated: