Active Nerds
Module #42 · intermediate Article

Kubernetes RBAC: Implementing Roles, ClusterRoles, and Bindings for Granular Security

RBAC (Role-Based Access Control) grants permissions to subjects (users, groups, ServiceAccounts) through Roles (namespace-scoped) or ClusterRoles (cluster-wi...

6 min read+15 XPPublished 2026-05-12

Quick Answer: RBAC (Role-Based Access Control) grants permissions to subjects (users, groups, ServiceAccounts) through Roles (namespace-scoped) or ClusterRoles (cluster-wide) bound via RoleBindings or ClusterRoleBindings.

Detailed Answer:

RBAC building blocks:

WHO (Subject)          WHAT (Role/ClusterRole)     WHERE (Binding)
─────────────          ───────────────────────     ───────────────
User                   Role                        RoleBinding
Group           ──────► (namespace-scoped)   ─────► (namespace-scoped)
ServiceAccount         ClusterRole                 ClusterRoleBinding
                       (cluster-wide)              (cluster-wide)

Practical Implementation — Dev Team RBAC:

# Scenario: 3 teams — frontend, backend, ops
# Each team gets appropriate access to their namespace

# ── FRONTEND TEAM ────────────────────────────────────────────────
# Developers can view everything, deploy to dev namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: frontend-dev
rules:
# View all common resources
- apiGroups: ["", "apps", "autoscaling", "batch"]
  resources:
  - pods
  - pods/log
  - pods/exec                       # Allow kubectl exec
  - deployments
  - replicasets
  - services
  - configmaps
  - horizontalpodautoscalers
  - jobs
  - cronjobs
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Read-only for secrets (can reference, can't read values)
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list"]            # Can see secret names, not values
# Can port-forward for debugging
- apiGroups: [""]
  resources: ["pods/portforward"]
  verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: frontend-developers
  namespace: frontend-dev
subjects:
- kind: Group
  name: frontend-team               # Maps to OIDC group or AWS IAM role
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

---
# ── OPS TEAM ─────────────────────────────────────────────────────
# Ops gets read-only access cluster-wide + full access to ops namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-viewer
rules:
- apiGroups: ["", "apps", "autoscaling", "batch", "networking.k8s.io"]
  resources:
  - pods
  - nodes
  - namespaces
  - deployments
  - services
  - ingresses
  - persistentvolumes
  - persistentvolumeclaims
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ops-cluster-viewer
subjects:
- kind: Group
  name: ops-team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-viewer
  apiGroup: rbac.authorization.k8s.io

---
# ── CI/CD SERVICE ACCOUNT ─────────────────────────────────────────
# Minimal permissions for GitLab CI runner
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab-ci-runner
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/gitlab-ci-role
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-deployer
  namespace: production
rules:
# Can update deployments (for image updates)
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets"]
  verbs: ["get", "list", "patch", "update"]
# Can manage configmaps (for config updates)
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "create", "update", "patch"]
# Can check rollout status
- apiGroups: ["apps"]
  resources: ["replicasets"]
  verbs: ["get", "list", "watch"]
# Can read pods and logs (for deployment verification)
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: gitlab-ci-deployer
  namespace: production
subjects:
- kind: ServiceAccount
  name: gitlab-ci-runner
  namespace: production
roleRef:
  kind: Role
  name: ci-deployer
  apiGroup: rbac.authorization.k8s.io

RBAC on EKS — AWS IAM Integration:

# EKS maps IAM users/roles to K8s RBAC via aws-auth ConfigMap
# OR via EKS Access Entries (newer, preferred approach)

# Method 1: aws-auth ConfigMap (legacy but widely used)
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    # Map IAM role to K8s group
    - rolearn: arn:aws:iam::123456789012:role/frontend-developers
      username: frontend-developer
      groups:
        - frontend-team              # This group name matches RoleBinding subjects

    - rolearn: arn:aws:iam::123456789012:role/ops-engineers
      username: ops-engineer
      groups:
        - ops-team

    # Node group role — required for worker nodes
    - rolearn: arn:aws:iam::123456789012:role/EKSNodeGroupRole
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

  mapUsers: |
    # Map specific IAM user (use roles instead — easier to manage)
    - userarn: arn:aws:iam::123456789012:user/admin-user
      username: admin-user
      groups:
        - system:masters             # cluster-admin equivalent
# Method 2: EKS Access Entries (preferred for new clusters)
aws eks create-access-entry \
  --cluster-name production-cluster \
  --principal-arn arn:aws:iam::123456789012:role/frontend-developers \
  --kubernetes-groups frontend-team

# Verify what permissions a user/SA has
kubectl auth can-i list pods \
  --as=system:serviceaccount:production:gitlab-ci-runner \
  -n production
# yes

kubectl auth can-i delete secrets \
  --as=system:serviceaccount:production:gitlab-ci-runner \
  -n production
# no

# View all permissions for a ServiceAccount (comprehensive audit)
kubectl get rolebindings,clusterrolebindings -A \
  -o json | jq -r '
    .items[] |
    select(.subjects[]?.name == "gitlab-ci-runner") |
    [.metadata.name, .roleRef.name] | @tsv'

RBAC Best Practices:

| Practice | Why | |---|---| | Least privilege always | Start with read-only, add verbs only as needed | | Use Groups not Users | Groups scale; individual user bindings are hard to audit | | Namespace-scope first | Use Role before ClusterRole unless cluster-wide truly needed | | Never give secrets list to devs | list on secrets reveals all secret names — use get with specific name | | Audit regularly | kubectl get clusterrolebindings — revoke unused bindings | | Never use system:masters | Cannot be audited or restricted once granted; use cluster-admin role instead |

Common Mistake:

Granting cluster-admin to CI/CD pipelines "for simplicity." A compromised pipeline token with cluster-admin can delete every resource in the cluster. Always scope CI/CD access to the minimum required verbs in specific namespaces.

Key Takeaway: RBAC follows the principle of least privilege — start with the minimum permissions needed, use Groups for maintainability, and regularly audit bindings to remove what's no longer needed.


Finished reading?

Mark as complete to claim your +15 XP and track progress.