RBAC#
In the secrets chapter we said that secret data “can be restricted with RBAC”. That was a promise we haven’t paid off yet. Let’s pay it off.
RBAC - Role Based Access Control - answers one question: who is allowed to do what to which objects. It’s the bouncer at the club door. Everyone with a kubeconfig on your laptop is a cluster-admin, which is why it never came up until now. But the moment you hand a namespace to a team, or give an app a token, the bouncer starts working.
Two halves, and they’re separate on purpose:
- Role: a list of permissions (“can
getandlistpods”) - RoleBinding: attaches that role to a user, group, or ServiceAccount (“and this app gets those permissions”)
özetlersek Role = yetki listesi, RoleBinding = “kim” ile “yetkisi nedir” desek daha dogru olur. Rol tek başına kimseye yetki vermez, RoleBinding’i olmayan bir Role’de bir şey yaptıramazsin.
RBAC is additive and default-deny. There are no
denyrules at all - if no rule allows an action, it’s denied. And when several rules apply, the answers are unioned: one “yes” anywhere wins. That’s why giving someonecluster-admin“just to debug” is so hard to walk back - you can’t subtract, you can only edit the rules.
ServiceAccounts#
Every request to the API server runs as some identity. Humans use certificates; pods use ServiceAccounts. If you don’t name one, the pod gets default in its namespace - which is one more reason not to leave the default namespace full of half-finished experiments.
Create one the imperative way:
kubectl create serviceaccount crawler-saOr in YAML:
apiVersion: v1
kind: ServiceAccount
metadata:
name: crawler-saA pod claims it like this:
spec:
serviceAccountName: crawler-saModern Kubernetes mounts the token as a projected volume at
/var/run/secrets/kubernetes.io/serviceaccount/-token,ca.crt, andnamespacefiles. TheautomountServiceAccountToken: falseoption on a pod spec turns that off entirely, which is the right move for pods that never talk to the API.
Need to authenticate as that ServiceAccount outside the pod - to test, or to hand a CI job a credential? Mint a short-lived token:
kubectl create token crawler-sa -n crawlerOlder docs will tell you to read the token out of a
Secretof typekubernetes.io/serviceaccount-token. That still works, but the projected volume pluskubectl create tokenis the modern path - the token rotates, and you never have to copy a long-lived secret around.
Özetlersek: ServiceAccount = pod’un kimliği. İnsalar sertifika ile, podlar SA token ile konuşur. Her namespace’te bir default SA var ama ona yetki yüklemek, o namespace’teki her pod’un aynı yetkiye sahip olması demek - her uygulamaya kendi SA’sini ver.
Roles and ClusterRoles#
A Role is namespaced - it grants permissions inside one namespace. A ClusterRole grants cluster-wide, and is also what you need for non-namespaced things (nodes, persistent volumes).
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: crawler
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]The three fields that matter:
apiGroups:""means the core group (pods, services, secrets…);"apps"covers deployments and statefulsetsresources: what objects ("pods","deployments","secrets", or"pods/log"for subresources)verbs: what actions -get,list,watch,create,update,patch,delete, and the wildcard*
Verbs are exactly what they say on the tin:
listwithoutgetmeans “you may see the names, but not the contents”.watchis whatkubectl get -wneeds.createwithoutdeleteis a fun one to hand out.
apiGroups and subresources#
The two you’ll type most are "" (core) and apps. But the exam likes to trip people on the rest:
apiGroups | resources |
|---|---|
"" | pods, services, configmaps, secrets, events, namespaces |
apps | deployments, statefulsets, daemonsets, replicasets |
batch | jobs, cronjobs |
networking.k8s.io | ingresses, networkpolicies |
rbac.authorization.k8s.io | roles, rolebindings, clusterroles |
Subresources are written as parent/child and need their own entry in resources:
resources: ["pods", "pods/log", "pods/exec"]pods/log is the “read the output” permission. pods/exec is kubectl exec - that one is a shell inside the container, so hand it out like you’d hand out a root password.
resourceNames#
Rules can be narrowed to specific objects by name:
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get"]
resourceNamesonly restricts requests that name a single object:get,update,patch,delete.listandwatchdon’t take a name, andcreatedoesn’t have one yet - so a rule withresourceNamesandverbs: ["list"]or["create"]silently matches nothing. If you need “read these three configmaps”, useresourceNameswithget, not withlist.
ClusterRole#
A ClusterRole looks identical to a Role, minus metadata.namespace - and it’s the only way to grant permissions on cluster-scoped resources: nodes, persistentvolumes, namespaces, storageclasses, certificatesigningrequests.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list"]Dikkat: “Role ile node yetkisi ver” bir sınav klasiğidir ve doğru cevabı yoktur. Node, PV, Namespace gibi cluster-scoped nesneler hiçbir namespace’e ait değildir;
Roleyalnızca tek namespace içinde çalışır. Bu yüzden bu tür bir görevdeClusterRoleyazman gerekir - Role yazarsan görevi kaybedersin.
A ClusterRole is also handy as a reusable template even for namespaced resources - more on that in the combinations table below.
nonResourceURLs#
RBAC also covers things that aren’t Kubernetes objects at all - HTTP paths on the API server like /healthz, /metrics, /version:
rules:
- nonResourceURLs: ["/healthz", "/metrics"]
verbs: ["get"]Note there is no apiGroups and no resources here - nonResourceURLs is a different rule shape.
Dikkat: nonResourceURLs kuralının çalışması için
ClusterRole+ClusterRoleBindinggerekir.Roleiçine yazarsan obje belkiapplyolur ama hiçbir zaman eşleşmez - bu yollar namespace’e ait değildir. Sınavda “monitoring’e/metricsokuma yetkisi ver” tarzı bir görevdeRoleBindingyazmak klasik kaybedilen puandır.
Where do these rules live in a real cluster? Kubernetes ships a few of them already: system:public-info-viewer grants get on /healthz, /livez, /readyz, /version to everyone including anonymous, system:discovery grants the API discovery paths to authenticated users, and system:monitoring grants /metrics plus health endpoints to the system:monitoring group. Knowing that system:public-info-viewer exists explains why curl /healthz works before you’ve configured any RBAC at all.
Bindings#
A RoleBinding sticks a role to a subject:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: crawler-pod-reader
namespace: crawler
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-reader
subjects:
- kind: ServiceAccount
name: crawler-sa
namespace: crawlerNote the roleRef is not a reference you can edit in place - changing which role a binding points at means deleting and recreating the binding. That’s a Kubernetes quirk, not a bug in your YAML.
A ClusterRoleBinding does the same with cluster-wide reach. Watch out: a ClusterRoleBinding to cluster-admin is how people accidentally give every pod in the cluster full control. Bind narrowly.
Subjects: User, Group, ServiceAccount#
subjects is a list, and it has three kinds. This is where most RBAC YAML goes wrong:
subjects:
- kind: ServiceAccount
name: crawler-sa
namespace: crawler # REQUIRED - the SA lives somewhere specific
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io # REQUIRED for User
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io # REQUIRED for GroupThe rules:
- ServiceAccount — the identity of a pod.
namespaceis not taken from the binding, you must write it out. A binding incrawlerpointing atcrawler-sawithout anamespacefield is a broken binding. - User — a human. Kubernetes has no
Userobject; the name is whatever the authentication layer said it was. With client certs it’s the certificate’sCNfield. - Group — a set of users, coming from a certificate’s
Ofield or a token claim. Some are built in, likesystem:mastersandsystem:serviceaccounts:<ns>.
And the apiGroup swap that trips everyone: ServiceAccount takes apiGroup: "" (or nothing at all), while User and Group take rbac.authorization.k8s.io. Get this wrong and the binding is rejected at apply time - which is at least loud about it.
The string
system:serviceaccount:<namespace>:<name>is the ServiceAccount’s internal username. That’s whykubectl auth can-i --as=system:serviceaccount:crawler:crawler-saworks - you’re impersonating it by its full internal name.
ClusterRoleBinding#
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alice-node-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: node-reader
subjects:
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.ioCluster-wide reach, no metadata.namespace - a ClusterRoleBinding is not in any namespace.
The Four Combinations#
This table is the single highest-value thing to memorize. The permission bundle (Role / ClusterRole) and the binding (RoleBinding / ClusterRoleBinding) can be mixed - and the mix decides how far the permissions reach:
| Bundle | Binding | What the subject can do |
|---|---|---|
Role | RoleBinding | the Role’s rules, inside the binding’s namespace only |
ClusterRole | RoleBinding | the ClusterRole’s rules, but only inside the binding’s namespace |
ClusterRole | ClusterRoleBinding | the ClusterRole’s rules, everywhere (all namespaces + cluster-scoped) |
Role | ClusterRoleBinding | not possible - a Role is namespace-bound and can’t be granted cluster-wide |
Row two is the one that pays for itself. Write the ClusterRole once (“can read deployments”), then give every team a RoleBinding to it in their own namespace. One permission definition, N teams, zero overlap. That’s namespace isolation without copy-pasting roles.
Row four is not a mistake you can make quietly - the API server rejects the object.
Özetlersek: Rol + RoleBinding = “tek namespace’te şu yetkiler”. ClusterRole + RoleBinding = “aynı yetkiler ama sadece bu namespace’te” (yeniden kullanım, sınırlı erişim). ClusterRole + ClusterRoleBinding = “tüm cluster’da”. Role + ClusterRoleBinding = imkansız.
Imperative Generators#
The exam is timed. YAML is clearer, but the generators get the skeleton down in seconds - and every one of them takes --dry-run=client -o yaml, so you can generate and then edit:
# Roles
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n crawler
kubectl create role config-reader --verb=get --resource=configmaps --resource-name=app-config -n crawler
kubectl create clusterrole node-reader --verb=get,list --resource=nodes
# Bindings
kubectl create rolebinding crawler-pod-reader \
--role=pod-reader \
--serviceaccount=crawler:crawler-sa -n crawler
kubectl create rolebinding alice-can-view \
--clusterrole=view --user=alice -n crawler
kubectl create clusterrolebinding alice-node-reader \
--clusterrole=node-reader --user=aliceNote the two subject spellings - --serviceaccount=namespace:name for generators, versus --as=system:serviceaccount:namespace:name for impersonation. Same identity, different syntax. Mixing them up is the fastest way to waste five minutes.
Checking Permissions#
You never have to guess who can do what:
kubectl auth can-i create deployments --namespace crawler
kubectl auth can-i list secrets --namespace crawler --as=system:serviceaccount:crawler:crawler-sa
kubectl auth can-i '*' '*' --as=system:serviceaccount:crawler:crawler-saYou want no on that last one. kubectl auth can-i --list dumps the full effective rule set for the current user; add --as=... to see the whole rulebook for someone else, and --namespace to scope it:
kubectl auth can-i --list --as=system:serviceaccount:crawler:crawler-sa -n crawlerAlways pass
--namespaceexplicitly. Without it,can-ichecks against whatever namespace your kubeconfig context happens to be pointing at - and you’ll “prove” the wrong thing.
There is also --as-group when you want to impersonate a group rather than a user:
kubectl auth can-i list pods -n crawler --as=alice --as-group=dev-teamÖzetlersek: RBAC’i test etmek tahmin işi değildir. kubectl auth can-i --as=<kimlik> --namespace=<ns> her zaman yanındadır - ve --list ile o kimliğin tüm yetki defterini tek seferde görebilirsin.
Troubleshooting RBAC#
RBAC shows up as a symptom, rarely as an error message that says “your YAML is wrong”. The usual shapes:
Pod logunda forbidden: User "system:serviceaccount:x:y" cannot get resource "secrets" — the app is missing a permission. Don’t edit the app; find the gap:
kubectl auth can-i --list --as=system:serviceaccount:x:y -n xYazdın, apply oldu ama hâlâ izin yok — in order of how often it bites:
- You created the
Rolebut never theRoleBinding. A Role alone grants nothing. subjects[].namespaceis missing or wrong for a ServiceAccount. The binding’s namespace does not substitute for it.- You bound the right role to the wrong subject - the pod is running as
default, not the SA you wired up. Checkkubectl get pod <name> -o jsonpath='{.spec.serviceAccountName}'. - You’re missing
watch. A controller that lists-and-watches works forlistand then hangs forever onwatch. - You tested it with
kubectl auth can-iwithout--as, so you tested your own admin identity instead.
Bir anda herkes her şeyi yapabiliyor — look for a ClusterRoleBinding to cluster-admin or edit. kubectl get clusterrolebindings and grep for it. This is why ClusterRole + RoleBinding is the safer default: same role, blast radius of one namespace.
Bir şeyi değiştirmeye çalışıyorsun ama olmadı — roleRef is immutable. Delete and recreate the binding.
Assignment#
Our crawler team at SynergyChat should be able to watch their own pods and read their logs - and absolutely nothing else.
- Create a file called
crawler-role.yamlwith aRolein thecrawlernamespace:
metadata/name:crawler-pod-readermetadata/namespace:crawlerrules/apiGroups:[""]rules/resources:["pods", "pods/log"]rules/verbs:["get", "list", "watch"]
- Create a file called
crawler-rolebinding.yaml:
kind:RoleBindingmetadata/namespace:crawlerroleRef:Rolenamedcrawler-pod-readersubjects: thecrawler-saServiceAccount (remember thenamespacefield)
- Create the ServiceAccount if you haven’t already, and wire the crawler deployment to it:
kubectl create serviceaccount crawler-sa -n crawlerspec:
serviceAccountName: crawler-sa- Apply everything and test the boundaries:
kubectl apply -f crawler-role.yaml -f crawler-rolebinding.yaml
kubectl auth can-i list pods -n crawler --as=system:serviceaccount:crawler:crawler-sa
kubectl auth can-i delete pods -n crawler --as=system:serviceaccount:crawler:crawler-sa
kubectl auth can-i get secrets -n crawler --as=system:serviceaccount:crawler:crawler-saYou want yes, no, no. That asymmetry is the entire point of RBAC - the crawler can see itself, but can’t touch its neighbors or read the secrets it doesn’t need.
- Now for the part exams actually ask. The ops team (user
alice) needs togetandlistnodes - cluster-wide, because nodes aren’t in a namespace. Writenode-readeras aClusterRole, bind it toalicewith aClusterRoleBinding, and prove it:
kubectl auth can-i list nodes --as=alice # yes
kubectl auth can-i delete nodes --as=alice # no- Bonus, and the pattern worth keeping: the crawler team wants to read
configmapsin their own namespace using aClusterRoleas a template. Create aClusterRolenamedcm-readerthat allowsget/listonconfigmaps, then bind it tocrawler-sawith aRoleBinding(not aClusterRoleBinding) incrawler. Prove that the same SA still cannot read configmaps inkube-system. That gap is exactly what makes row two of the combinations table worth knowing.
Dikkat:
kubectl auth can-iile test ettiğin kimlik,--asvermezsen kendi kimliğin oldugundan “ben görebiliyorum ama pod göremiyor” probleminin ana sebebidir - her zaman pod’un ServiceAccount’ı ile test et.
for more jobs-cronjobs