🔒 Security Intermediate ⏱️ 15 min

RBAC and Service Accounts in Kubernetes

Implement fine-grained access control with Kubernetes RBAC, service accounts, and least-privilege security patterns.

By Victor Robin

Introduction

Kubernetes defaults to a permissive stance, but in production, “everyone is an admin” is a recipe for disaster. Role-Based Access Control (RBAC) allows you to define who can do what within your cluster. This guide covers implementing least-privilege access patterns using Service Accounts, Roles, and RoleBindings, ensuring that both users and applications operate within their designated boundaries.

Architecture Overview

Access to resources in Kubernetes is governed by the connection between an Identity (Subject), a set of permissions (Role), and the link between them (Binding).

flowchart TB
    Subject["👤 User / 🤖 ServiceAccount"] --> Binding["🔗 RoleBinding"]
    Binding --> Role["📜 Role / ClusterRole"]
    
    Role --> Allow["✅ Allowed Actions<br/>(get, list, watch)"]
    Allow --> Resource["📦 Resource<br/>(Pod, ConfigMap)"]
    
    Subject -.-> Deny["🚫 Direct Access<br/>(Implicit Deny)"] -.-> Resource

    classDef primary fill:#7c3aed,color:#fff
    classDef secondary fill:#06b6d4,color:#fff
    classDef db fill:#f43f5e,color:#fff
    classDef warning fill:#fbbf24,color:#000

    class Subject warning
    class Role,Binding secondary
    class Resource db

Implementation

RBAC Components

ResourceScopePurpose
RoleNamespaceDefine permissions within a namespace
ClusterRoleClusterDefine cluster-wide permissions
RoleBindingNamespaceGrant Role to users/service accounts
ClusterRoleBindingClusterGrant ClusterRole cluster-wide

Service Accounts

Application Service Account

# apps/archives-api/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: archives-api
  namespace: archives-staging
  labels:
    app.kubernetes.io/name: archives-api
automountServiceAccountToken: false

Pod Using Service Account

# apps/archives-api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: archives-api
  namespace: archives-staging
spec:
  template:
    spec:
      serviceAccountName: archives-api
      automountServiceAccountToken: false
      containers:
        - name: api
          image: archives-api:latest
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false

Namespace Roles

Developer Role

# core-infra/security/roles/developer-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: archives-staging
rules:
  # Read pods and logs
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
  # Read deployments and services
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "watch"]
  # Port-forward for debugging
  - apiGroups: [""]
    resources: ["pods/portforward"]
    verbs: ["create"]
  # Exec for troubleshooting (limited)
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create"]

Operator Role

# core-infra/security/roles/operator-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: operator
  namespace: archives-staging
rules:
  # Full deployment management
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets", "statefulsets"]
    verbs: ["get", "list", "watch", "update", "patch"]
  # Pod management
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch", "delete"]
  - apiGroups: [""]
    resources: ["pods/log", "pods/exec", "pods/portforward"]
    verbs: ["get", "create"]
  # ConfigMap and Secret read
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list", "watch"]
  # Service management
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["get", "list", "watch", "update", "patch"]
  # HPA management
  - apiGroups: ["autoscaling"]
    resources: ["horizontalpodautoscalers"]
    verbs: ["get", "list", "watch", "update", "patch"]

Cluster Roles

Read-Only Cluster Role

# core-infra/security/clusterroles/readonly.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-readonly
rules:
  - apiGroups: [""]
    resources: ["namespaces", "nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses", "networkpolicies"]
    verbs: ["get", "list", "watch"]

Flux Controller Role

# core-infra/security/clusterroles/flux-controller.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: flux-reconciler
rules:
  # Full resource management for GitOps
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  # CRD management
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

Role Bindings

User RoleBinding

# core-infra/security/rolebindings/developer-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-binding
  namespace: archives-staging
subjects:
  - kind: Group
    name: developers
    apiGroup: rbac.authorization.k8s.io
  - kind: User
    name: victor@bluerobin.local
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

Service Account Binding

# Bind ClusterRole to ServiceAccount in specific namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: external-secrets-reader
  namespace: archives-staging
subjects:
  - kind: ServiceAccount
    name: external-secrets
    namespace: external-secrets
roleRef:
  kind: ClusterRole
  name: external-secrets-controller
  apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding

# core-infra/security/clusterrolebindings/readonly-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-readonly-binding
subjects:
  - kind: Group
    name: readonly-users
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-readonly
  apiGroup: rbac.authorization.k8s.io

Aggregated ClusterRoles

Aggregation Labels

# core-infra/security/clusterroles/monitoring-base.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-base
  labels:
    rbac.bluerobin.local/aggregate-to-monitoring: "true"
rules:
  - apiGroups: [""]
    resources: ["pods", "nodes", "services"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets"]
    verbs: ["get", "list", "watch"]

Aggregated Role

# core-infra/security/clusterroles/monitoring-aggregated.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.bluerobin.local/aggregate-to-monitoring: "true"
rules: []  # Automatically aggregated

Application-Specific Roles

External Secrets Operator

# infrastructure/external-secrets/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-secrets-controller
rules:
  # Manage secrets
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  # Read configmaps for configuration
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  # Manage ExternalSecret CRs
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets", "secretstores", "clustersecretstores"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets/status"]
    verbs: ["update", "patch"]
  # Events for status reporting
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]

CNPG Operator

# infrastructure/cnpg/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cnpg-manager
rules:
  # Manage PostgreSQL clusters
  - apiGroups: ["postgresql.cnpg.io"]
    resources: ["*"]
    verbs: ["*"]
  # Pod management
  - apiGroups: [""]
    resources: ["pods", "pods/exec", "pods/log"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  # PVC management
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  # Secrets and ConfigMaps
  - apiGroups: [""]
    resources: ["secrets", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

Auditing RBAC

Check User Permissions

# Check if user can perform action
kubectl auth can-i create deployments -n archives-staging --as=victor@bluerobin.local

# List all permissions for a user
kubectl auth can-i --list --as=victor@bluerobin.local -n archives-staging

# Check service account permissions
kubectl auth can-i --list --as=system:serviceaccount:archives-staging:archives-api -n archives-staging

List Role Bindings

# List all role bindings in namespace
kubectl get rolebindings -n archives-staging -o wide

# List cluster role bindings
kubectl get clusterrolebindings -o wide | grep -v system:

# Describe specific binding
kubectl describe rolebinding developer-binding -n archives-staging

Best Practices

Least Privilege Checklist

PrincipleImplementation
Namespace isolationUse Roles over ClusterRoles when possible
Minimal verbsOnly grant required actions (get, list vs *)
Specific resourcesTarget specific resource types, not wildcards
Service account per appUnique SA for each application
No token auto-mountDisable unless API access needed
Regular auditReview bindings periodically

Conclusion

RBAC is the primary mechanism for securing access to the Kubernetes API. By strictly defining roles and binding them only to the service accounts that need them, you minimize the potential impact of compromised credentials and ensure compliance with security standards.

[Using RBAC Authorization] — Kubernetes