Back to Blog

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.

Viprasol Tech Team
October 10, 2026
13 min read

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

MistakeRiskFix
Using default service accountAll pods in NS share permissionsCreate dedicated SA per workload
cluster-admin for CI/CDFull cluster access if pipeline compromisedUse namespace-scoped deployer role
secrets without resourceNamesPod can read ALL secrets in namespaceRestrict to specific secret names
automountServiceAccountToken: true (default)Token exposed to pod unnecessarilySet false when SA token not needed
ClusterRoleBinding instead of RoleBindingCluster-wide vs namespace-scopedUse RoleBinding + ClusterRole for ns access
Wildcard verbs ["*"]Too broadList exact verbs needed

See Also


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.

Kubernetes security services → | Talk to our engineers →

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

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.