Every pod in Kubernetes runs under a ServiceAccount that determines what it can do via the API server. RBAC (Role-Based Access Control) is the policy engine that grants or denies those permissions.
{{ "{{" }} if .Values.rbac.create {{ "}}" }}A ServiceAccount provides an identity for processes running in a pod. When a pod makes a request to the Kubernetes API server, it authenticates using the token mounted from its ServiceAccount.
# templates/serviceaccount.yaml
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "myapp.serviceAccountName" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- if .Values.serviceAccount.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.serviceAccount.imagePullSecrets | nindent 2 }}
{{- end }}
{{- end }}
automountServiceAccountToken: false is the secure default. Only enable it when your app actually calls the Kubernetes API.Kubernetes RBAC has four resource types that work together to grant permissions.
| Resource | Scope | Purpose |
|---|---|---|
| Role | Namespace | Defines a set of permissions within a single namespace |
| ClusterRole | Cluster-wide | Defines permissions across all namespaces or for cluster resources |
| RoleBinding | Namespace | Grants a Role (or ClusterRole) to subjects in a namespace |
| ClusterRoleBinding | Cluster-wide | Grants a ClusterRole to subjects across the entire cluster |
Key insight: A RoleBinding can reference a ClusterRole but still limits the permissions to its own namespace. This is a common pattern for reusing permission sets.
The most common pattern: your app needs to read ConfigMaps and Secrets in its own namespace.
# templates/role.yaml
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
{{- end }}
# templates/rolebinding.yaml
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "myapp.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "myapp.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
rules:
- apiGroups: [""]
resources: ["pods", "services",
"configmaps"]
verbs: ["get", "list", "watch"]
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["*"]
verbs: ["*"]
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-creds"]
verbs: ["get"]
rules:
- apiGroups: ["myapp.io"]
resources: ["widgets"]
verbs: ["get", "list", "watch",
"create", "update", "delete"]
A well-structured values.yaml lets users control ServiceAccount and RBAC creation independently.
# values.yaml
serviceAccount:
create: true
name: "" # auto-generated if empty
automount: false # secure default
annotations: {}
imagePullSecrets: []
rbac:
create: true
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
# templates/_helpers.tpl
{{- define "myapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "myapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
ServiceAccounts can carry imagePullSecrets so every pod using that account automatically authenticates to private container registries.
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp
imagePullSecrets:
- name: ghcr-credentials
- name: ecr-credentials
automountServiceAccountToken: false unless API access is neededverbs to exactly what the app needs — avoid ["*"]resourceNames to limit access to specific objectskubectl auth can-i --list --as=system:serviceaccount:ns:name