Kubernetes RBAC: Roles, ClusterRoles, Service Accounts, and Least-Privilege Patterns
Implement Kubernetes RBAC correctly: design least-privilege Roles and ClusterRoles, bind service accounts to workloads, audit permissions with kubectl-who-can, prevent privilege escalation, and integrate with AWS IAM via IRSA.
Kubernetes RBAC controls who (subjects: users, groups, service accounts) can do what (verbs: get, list, create, delete) to which resources (pods, secrets, deployments). The default posture in most clusters is too permissive: workloads run with service accounts that have cluster-wide access they don't need.
The principle of least privilege: every workload gets exactly the permissions it requires, nothing more. This limits blast radius when a pod is compromised.
RBAC Concepts
Subject: Who is making the request?
- User (human, authenticated via certificates or OIDC)
- Group (collection of users)
- ServiceAccount (identity for a pod or workload)
Role / ClusterRole: What can they do?
- Role: namespace-scoped permissions
- ClusterRole: cluster-wide permissions (or reusable namespace template)
RoleBinding / ClusterRoleBinding: Connect subjects to roles
- RoleBinding: grants a Role (or ClusterRole) within a namespace
- ClusterRoleBinding: grants a ClusterRole cluster-wide
Namespace-Scoped Roles
# kubernetes/rbac/api-server-role.yaml
# Minimal permissions for the API server pod
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-server
namespace: production
rules:
# Read ConfigMaps (for feature flags, config)
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
# Read Secrets (for database credentials at startup)
# Better: use external-secrets operator to avoid this
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames: ["db-credentials", "jwt-secret"] # Only specific secrets!
# No pod access needed — the API server doesn't need to manage pods
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-server
namespace: production
annotations:
# IRSA: link this service account to an IAM role (AWS-specific)
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/api-server-production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-server
namespace: production
subjects:
- kind: ServiceAccount
name: api-server
namespace: production
roleRef:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
name: api-server
# kubernetes/deployments/api-server.yaml
spec:
template:
spec:
# Explicitly reference the service account
serviceAccountName: api-server
# Security hardening — works with RBAC
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: api
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
ClusterRole for Cross-Namespace Access
# kubernetes/rbac/metrics-reader-clusterrole.yaml
# Read-only access to pods across all namespaces — for a monitoring agent
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: metrics-reader
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/metrics", "nodes/proxy"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods", "endpoints", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics", "/metrics/cadvisor"]
verbs: ["get"]
---
# Bind to monitoring namespace service account
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: metrics-reader-prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
roleRef:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
name: metrics-reader
IRSA: IAM Roles for Service Accounts (AWS EKS)
IRSA lets pods assume AWS IAM roles without long-lived credentials:
# terraform/modules/ecs-rbac/irsa.tf
# Create IAM role for an EKS service account
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [var.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "${replace(var.oidc_provider_url, "https://", "")}:sub"
values = ["system:serviceaccount:${var.namespace}:${var.service_account_name}"]
}
condition {
test = "StringEquals"
variable = "${replace(var.oidc_provider_url, "https://", "")}:aud"
values = ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "this" {
name = "${var.service_account_name}-${var.namespace}"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
tags = var.tags
}
# Attach only the policies the workload needs
resource "aws_iam_role_policy" "s3_access" {
name = "s3-access"
role = aws_iam_role.this.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = ["${var.s3_bucket_arn}/*"]
},
{
Effect = "Allow"
Action = ["s3:ListBucket"]
Resource = [var.s3_bucket_arn]
Condition = {
StringLike = {
"s3:prefix" = ["uploads/${var.namespace}/*"]
}
}
}
]
})
}
output "role_arn" {
value = aws_iam_role.this.arn
}
// src/lib/aws.ts — Pods using IRSA don't need explicit credentials
// The AWS SDK automatically uses the projected service account token
import { S3Client } from "@aws-sdk/client-s3";
// No accessKeyId / secretAccessKey — IRSA provides credentials via
// the projected volume at /var/run/secrets/eks.amazonaws.com/serviceaccount/token
export const s3 = new S3Client({
region: process.env.AWS_REGION ?? "us-east-1",
// Credentials automatically sourced from IRSA token
});
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Developer Namespace Roles
# kubernetes/rbac/developer-role.yaml
# What developers can do in their team's namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: team-payments
rules:
# Read everything
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "configmaps",
"replicasets", "jobs", "cronjobs", "events"]
verbs: ["get", "list", "watch"]
# Stream logs
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# Execute into pods (for debugging)
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
# Restart deployments (by patching replicas or rolling restart)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["patch"]
# Cannot: delete pods, modify secrets, create/delete deployments
---
# Separate read-only role for CI/CD observers
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-observer
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
CI/CD Service Account
# kubernetes/rbac/cicd-role.yaml
# GitHub Actions / ArgoCD deployer — minimal write access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cicd-deployer
namespace: production
rules:
# Update deployments (image rollout)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "patch", "update"]
# Wait for rollout
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
# Apply ConfigMaps
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "create", "update", "patch"]
# Check pod health during rollout
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
# No Secrets access — use external-secrets operator instead
Auditing Permissions
# kubectl-who-can: find who can do what
# Install: kubectl krew install who-can
# Who can delete pods in production?
kubectl who-can delete pods -n production
# Who can read secrets cluster-wide?
kubectl who-can get secrets --all-namespaces
# What can the api-server service account do?
kubectl auth can-i --list \
--as=system:serviceaccount:production:api-server \
-n production
# Check a specific permission
kubectl auth can-i get secrets \
--as=system:serviceaccount:production:api-server \
-n production
# Output: yes / no
# Audit all ClusterRoleBindings for overly-broad permissions
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects'
# Any subject bound to cluster-admin is a security concern
Common RBAC Mistakes
| Mistake | Risk | Fix |
|---|---|---|
Using default service account | All pods in NS share permissions | Create dedicated SA per workload |
cluster-admin for CI/CD | Full cluster access if pipeline compromised | Use namespace-scoped deployer role |
secrets without resourceNames | Pod can read ALL secrets in namespace | Restrict to specific secret names |
automountServiceAccountToken: true (default) | Token exposed to pod unnecessarily | Set false when SA token not needed |
| ClusterRoleBinding instead of RoleBinding | Cluster-wide vs namespace-scoped | Use RoleBinding + ClusterRole for ns access |
Wildcard verbs ["*"] | Too broad | List exact verbs needed |
See Also
- Cloud Native Security — broader Kubernetes security
- Zero-Trust Security Architecture — identity-based access
- Kubernetes Networking — network policies alongside RBAC
- Kubernetes Cost Optimization — resource governance
Working With Viprasol
RBAC misconfiguration is one of the most common Kubernetes security issues. Our platform security engineers design least-privilege RBAC policies, implement IRSA for AWS resource access, audit existing clusters for over-permissive bindings, and establish namespace governance that scales as teams grow.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.