RBAC and Service Accounts in Kubernetes
Implement fine-grained access control with Kubernetes RBAC, service accounts, and least-privilege security patterns.
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
| Resource | Scope | Purpose |
|---|---|---|
| Role | Namespace | Define permissions within a namespace |
| ClusterRole | Cluster | Define cluster-wide permissions |
| RoleBinding | Namespace | Grant Role to users/service accounts |
| ClusterRoleBinding | Cluster | Grant 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
| Principle | Implementation |
|---|---|
| Namespace isolation | Use Roles over ClusterRoles when possible |
| Minimal verbs | Only grant required actions (get, list vs *) |
| Specific resources | Target specific resource types, not wildcards |
| Service account per app | Unique SA for each application |
| No token auto-mount | Disable unless API access needed |
| Regular audit | Review 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