Lesson  in  DevSecOps in Practice: Container & Kubernetes Security

RBAC — Least Privilege

Create a service account with cluster-admin and prove it can delete nodes, read any secret, and escalate privileges. Then create a properly scoped service account that can only read ConfigMaps and Secrets in one namespace — nothing else.

The Mistake: cluster-admin on a Service Account

This is one of the most common Kubernetes misconfigurations in real clusters. A developer needed "admin" access for convenience — and gave a service account full cluster control.

kubectl create namespace rbac-demo

kubectl create serviceaccount over-privileged-sa -n rbac-demo

kubectl create clusterrolebinding over-privileged-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=rbac-demo:over-privileged-sa

Test what this account can do

SA="system:serviceaccount:rbac-demo:over-privileged-sa"

kubectl auth can-i list secrets -n kube-system --as=$SA
kubectl auth can-i delete nodes --as=$SA
kubectl auth can-i create clusterrolebindings --as=$SA
kubectl auth can-i '*' '*' --as=$SA

Expected: yes / yes / yes / yes

A compromised pod using this service account has full cluster control — it can delete nodes, escalate its own privileges, and read every secret.

The Fix: Least Privilege

Scenario: A backend API needs to read ConfigMaps and Secrets in its own namespace. Nothing else.

kubectl create serviceaccount backend-api-sa -n rbac-demo
cat > backend-role.yaml << 'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: backend-api-role
  namespace: rbac-demo
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: backend-api-binding
  namespace: rbac-demo
subjects:
- kind: ServiceAccount
  name: backend-api-sa
  namespace: rbac-demo
roleRef:
  kind: Role
  apiGroup: rbac.authorization.k8s.io
  name: backend-api-role
EOF
kubectl apply -f backend-role.yaml

Verify least-privilege permissions

SA_BACKEND="system:serviceaccount:rbac-demo:backend-api-sa"

echo "=== Allowed ==="
kubectl auth can-i list configmaps -n rbac-demo --as=$SA_BACKEND
kubectl auth can-i get secrets -n rbac-demo --as=$SA_BACKEND

echo "=== Blocked ==="
kubectl auth can-i delete pods -n rbac-demo --as=$SA_BACKEND
kubectl auth can-i list secrets -n kube-system --as=$SA_BACKEND
kubectl auth can-i create clusterrolebindings --as=$SA_BACKEND

Expected: yes / yes / no / no / no

Audit: Find All cluster-admin Accounts

kubectl get clusterrolebindings -o json | \
  python3 -c "
import json, sys
data = json.load(sys.stdin)
for item in data['items']:
    if item.get('roleRef', {}).get('name') == 'cluster-admin':
        for s in item.get('subjects', []):
            print(f\"  {s.get('kind')}/{s.get('namespace','cluster')}/{s.get('name')} via {item['metadata']['name']}\")
"

Run this periodically in production to find over-privileged accounts before attackers do.

Cleanup

kubectl delete namespace rbac-demo
kubectl delete clusterrolebinding over-privileged-binding