🏫 The School›🛡️ Security›☸️ धडा 10 — Kubernetes RBAC: फक्त त्यांच्यावर लिहिलेल्याच खोल्या उघडणारी keycards
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

☸️ धडा 10 — Kubernetes RBAC: फक्त त्यांच्यावर लिहिलेल्याच खोल्या उघडणारी keycards

📍 तुम्ही इथे आहात: 16 पैकी धडा 10 · मागील: lesson-09-encryption · पुढील: lesson-11-k8s-secrets-network


📦 या ब्रँचमध्ये काय आहे

धडे 01–09, आणि पहिले Kubernetes नियंत्रण: RBAC (role-based access control). Kubernetes API कडे येणारी प्रत्येक request — व्यक्तीकडून, CI कडून, pod मधल्या app कडून — ती Roles आणि bindings विरुद्ध तपासली जाते. तुम्ही ServiceAccounts, Role विरुद्ध ClusterRole, RoleBinding विरुद्ध ClusterRoleBinding, RBAC additive का आहे आणि default ने नकार का देते, आणि kubectl auth can-i ने permission कशी test करायची हे शिकता.

🧒 5 वर्षांच्या मुलाला समजावल्यासारखे

शाळेच्या परिसरात अनेक खोल्या आहेत. तिथे काम करणाऱ्या प्रत्येकाला एक keycard 🪪 मिळते.

म्हणून निकाल-मदतनीस School इमारतीच्या सूचना-खोलीत पाहू शकते. ती passwords असलेली तिजोरी उघडू शकते का? नाही — नोंदवहीतली कोणतीही ओळ तसे सांगत नाही. ती मुख्य कार्यालयाच्या इमारतीतल्या सूचना-खोलीत पाहू शकते का? नाही — तिची ओळ फक्त School इमारत म्हणते.

काही नियम प्रत्येक इमारतीसाठी असतात (परिसराचा नकाशा). ते कागद म्हणजे ClusterRoles, आणि एखाद्याला प्रत्येक इमारतीत असा नियम देणारी ओळ म्हणजे ClusterRoleBinding. दीपिका अशा ओळी फार कमी लिहिते.

आणि आणखी एक नियम: ज्या मदतनीसला keycard ची कधीच गरज नाही, तिने ते बाळगूच नये. ते हरवले तर कोणीही ते वापरू शकत नाही.

🗺️ आकृती

flowchart LR
    sa1["🤖 ServiceAccount<br/>results-api"] -->|"RoleBinding (ns school)"| r1["📜 Role read-grades<br/>get, list · pods, configmaps"]
    sa2["🤖 ServiceAccount<br/>ci"] -->|"RoleBinding (ns school)"| r2["📜 Role deployer<br/>get … patch · deployments, services"]
    g["👥 group auditors"] -->|"ClusterRoleBinding (all namespaces)"| cr["📜 ClusterRole view"]
    api{"☸️ API server<br/>any rule allows? yes → allow<br/>none → 403 (deny by default)"}
    r1 --> api
    r2 --> api
    cr --> api
    x["❌ results-api get secrets → denied<br/>❌ ci update deployments in kube-system → denied"]

🗺️ काढलेली आकृती + एक lab: https://school-edh.pages.dev/security/lesson-diagrams.html#l10

❓ काय

🤔 का

कारण Kubernetes API म्हणजेच cluster. जो pods तयार करू शकतो, Secrets वाचू शकतो किंवा Deployments बदलू शकतो, तो cluster चालवू शकेल असा कोणताही code चालवू शकतो आणि त्यातला कोणताही password वाचू शकतो. Apps आणि CI pipelines हा आत शिरण्याचा सर्वात नेहमीचा मार्ग आहे: एका web app मधला bug attacker ला त्या pod चा ServiceAccount token देतो. तो ServiceAccount cluster-admin ला bound असेल — एक नेहमीचा shortcut — तर एक web bug म्हणजे पूर्ण cluster. प्रत्येक app साठी एक छोटा ServiceAccount, फक्त त्याच्या स्वतःच्या namespace मध्ये लागेल तेवढ्यालाच bound, असला तर तोच bug छोटाच राहतो.

🔧 कसे (या repo मध्ये)

sec/cloud.py मधले rbac_can(bindings, roles, subject, verb, resource, namespace) bindings मधून फिरते. त्या namespace मधल्या (किंवा namespace "*" असलेल्या, जी model ची ClusterRoleBinding लिहिण्याची पद्धत आहे) त्या subject च्या प्रत्येक binding साठी, ते bound role चा प्रत्येक नियम पाहते. Verb आणि resource दोन्ही एखाद्या नियमात असतील, तर ते (True, "<role> allows <verb> <resource>") परत करते. कोणताही नियम जुळला नाही, तर ते (False, "no binding allows it (RBAC denies by default)") परत करते. तपासायला कोणताही deny नियम नाही — अगदी खऱ्या RBAC सारखे.

sec/demo.py मधले k8s_rbac() दोन roles (read-grades, deployer) आणि दोन bindings (sa:results-api, sa:ci, दोन्ही namespace school मध्ये) वापरते.

🧪 करून पाहा

python3 sec/demo.py k8s_rbac
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import rbac_can
from demo import ROLES, BINDINGS
roles = {**ROLES, "secret-reader": [dict(verbs={"get"}, resources={"secrets"})],
         "view-all": [dict(verbs={"get", "list", "watch"}, resources={"pods", "services"})]}
bindings = BINDINGS + [dict(subject="sa:results-api", role="secret-reader", namespace="school"),
                       dict(subject="group:auditors", role="view-all", namespace="*")]
for s, v, r, ns in (("sa:results-api", "get", "secrets", "school"),       # new RoleBinding
                    ("sa:results-api", "list", "secrets", "school"),      # only 'get' was granted
                    ("sa:results-api", "get", "secrets", "kube-system"),  # a RoleBinding stays in its namespace
                    ("sa:results-api", "delete", "pods", "school"),       # nobody granted delete
                    ("group:auditors", "list", "pods", "kube-system"),    # a ClusterRoleBinding reaches every namespace
                    ("sa:notices-api", "get", "pods", "school")):         # no binding at all
    print(f"{s:<16} {v:<6} {r:<8} in {ns:<11} → {rbac_can(bindings, roles, s, v, r, ns)}")
EOF
python3 sec/test_sec.py

✅ तपासा — तुम्हाला काय दिसायला हवे

k8s_rbac हे छापते:

═══ k8s_rbac ═══
── who may do what in the cluster (RBAC denies by default)
   sa:results-api   get    pods         in school      → (True, 'read-grades allows get pods')
   sa:results-api   get    secrets      in school      → (False, 'no binding allows it (RBAC denies by default)')
   sa:ci            update deployments  in school      → (True, 'deployer allows update deployments')
   sa:ci            update deployments  in kube-system → (False, 'no binding allows it (RBAC denies by default)')
   one ServiceAccount per app · no cluster-admin for apps or CI · automountServiceAccountToken: false when unused

✅ done — every door checked

तुमचा snippet हे छापतो:

sa:results-api   get    secrets  in school      → (True, 'secret-reader allows get secrets')
sa:results-api   list   secrets  in school      → (False, 'no binding allows it (RBAC denies by default)')
sa:results-api   get    secrets  in kube-system → (False, 'no binding allows it (RBAC denies by default)')
sa:results-api   delete pods     in school      → (False, 'no binding allows it (RBAC denies by default)')
group:auditors   list   pods     in kube-system → (True, 'view-all allows list pods')
sa:notices-api   get    pods     in school      → (False, 'no binding allows it (RBAC denies by default)')

Tests मध्ये ✅ L10 Kubernetes RBAC denies what no binding allows आहे आणि शेवट 16/16 passed ने होतो.

🏁 तुम्ही आत्ताच काय सिद्ध केले

Permissions फक्त बेरीज होतात: नव्या binding ने get secrets दिले, आणि बाकी काहीच बदलले नाही. list हे वेगळे verb आहे, म्हणून ते denied राहिले. Binding school मध्ये राहते, म्हणून kube-system बंदच राहिले. कोणतेही binding नसलेला subject — sa:notices-api — काहीच करू शकत नाही. आणि एका cluster-व्यापी binding ने (group:auditors) असा namespace गाठला ज्याचे नाव कोणीच घेतले नव्हते — म्हणूनच ClusterRoleBindings ना सगळ्यात जास्त काळजी लागते. खऱ्या cluster मध्ये तुम्ही secret-reader सुद्धा resourceNames ने एकाच Secret पुरते मर्यादित कराल.

⚠️ नेहमीच्या चुका

🏭 प्रत्यक्ष वापरात

खऱ्या account वर — प्रत्येक app साठी एक ServiceAccount, token mount न करता, एक नाव दिलेले Secret आणि एक ConfigMap वाचू शकणारा Role, आणि app च्या namespace मध्ये एक RoleBinding:

apiVersion: v1
kind: ServiceAccount
metadata: { name: results-api, namespace: school }
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: results-api, namespace: school }
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["results-settings"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["results-db"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: results-api, namespace: school }
subjects:
  - kind: ServiceAccount
    name: results-api
    namespace: school
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: results-api

Deployment मध्ये ServiceAccount निवडा आणि app API ला call करत नसेल तर token बंद ठेवा:

spec:
  template:
    spec:
      serviceAccountName: results-api
      automountServiceAccountToken: false

CI built-in edit ClusterRole सह एकाच namespace मध्ये deploy करते, जो RoleBinding ने bound आहे (म्हणजे "edit" फक्त school मध्ये लागू होते):

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: ci-deployer, namespace: school }
subjects:
  - kind: ServiceAccount
    name: ci
    namespace: ci-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit

विश्वास ठेवण्याआधी test करा — आणि जे denied व्हायला हवे तेसुद्धा test करा:

kubectl auth can-i get secrets/results-db -n school --as=system:serviceaccount:school:results-api    # yes
kubectl auth can-i list secrets -n school --as=system:serviceaccount:school:results-api              # no
kubectl auth can-i create deployments -n kube-system --as=system:serviceaccount:ci-system:ci        # no
kubectl auth can-i --list -n school --as=system:serviceaccount:school:results-api

धोकादायक bindings शोधा — cluster-व्यापी cluster-admin कोणाकडे आहे:

kubectl get clusterrolebindings -o json \
  | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name + " → " + ([.subjects[]? | .kind + ":" + .name] | join(", "))'

EKS वर, IAM मधली माणसे आणि roles access entries आणि access policies (किंवा तुम्ही स्वतः bind केलेले Kubernetes groups) मधून cluster मध्ये येतात, आणि pods ना AWS permissions EKS Pod Identity किंवा IRSA मधून मिळतात — हा एक वेगळा, AWS-बाजूचा least privilege (IAM शाळा त्याबद्दल खोलवर शिकवते).

🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: API server चा audit log चालू करा (EKS वर: audit साठी control plane logging) आणि 403 responses आणि प्रत्येक नवे ClusterRoleBinding तपासा. App कडून आलेला 403 बहुधा bug असतो; एकाच ServiceAccount कडून आलेले भरपूर 403 बहुधा कोणीतरी दारे चाचपून पाहत असल्याचे लक्षण असते.

⏭️ पुढे

Secret कोण वाचू शकतो हे RBAC ठरवते. पण Secret च्या आत काय असते, आणि network वरून pod पर्यंत कोण पोहोचू शकतो? पुढे: Secrets फक्त base64 असतात, आणि pods सगळ्यांशी बोलतात — जोपर्यंत तुम्ही वेगळे सांगत नाही.

git checkout lesson-11-k8s-secrets-network

☸️ Lesson 10 — Kubernetes RBAC: keycards that open only the rooms written on them

📍 You are here: Lesson 10 of 16 · Previous: lesson-09-encryption · Next: lesson-11-k8s-secrets-network


📦 What's in this branch

Lessons 01–09, plus the first Kubernetes control: RBAC (role-based access control). Every request to the Kubernetes API — from a person, from CI, from an app inside a pod — is checked against Roles and bindings. You learn ServiceAccounts, Role vs ClusterRole, RoleBinding vs ClusterRoleBinding, why RBAC is additive and denies by default, and how to test a permission with kubectl auth can-i.

🧒 Explain like I'm 5

The school campus has many rooms. Everyone who works there gets a keycard 🪪.

So the results helper can look in the notice room of building School. Can she open the safe with the passwords? No — no line in the register says so. Can she look in the notice room of the main office building? No — her line says building School only.

A few rules are for every building (the campus map). Those sheets are ClusterRoles, and a line that gives one to someone in every building is a ClusterRoleBinding. Dipika writes very few of those.

And one more rule: a helper who never needs the keycard should not carry one. If she loses it, nobody can use it.

🗺️ Diagram

flowchart LR
    sa1["🤖 ServiceAccount<br/>results-api"] -->|"RoleBinding (ns school)"| r1["📜 Role read-grades<br/>get, list · pods, configmaps"]
    sa2["🤖 ServiceAccount<br/>ci"] -->|"RoleBinding (ns school)"| r2["📜 Role deployer<br/>get … patch · deployments, services"]
    g["👥 group auditors"] -->|"ClusterRoleBinding (all namespaces)"| cr["📜 ClusterRole view"]
    api{"☸️ API server<br/>any rule allows? yes → allow<br/>none → 403 (deny by default)"}
    r1 --> api
    r2 --> api
    cr --> api
    x["❌ results-api get secrets → denied<br/>❌ ci update deployments in kube-system → denied"]

🗺️ Drawn version + a lab: https://school-edh.pages.dev/security/lesson-diagrams.html#l10

❓ What

🤔 Why

Because the Kubernetes API is the cluster. Whoever can create pods, read Secrets or edit Deployments can run any code the cluster can run and read any password it holds. Apps and CI pipelines are the most common way in: a bug in one web app gives an attacker that pod's ServiceAccount token. If that ServiceAccount is bound to cluster-admin — a common shortcut — one web bug becomes the whole cluster. With one small ServiceAccount per app, bound only to what it needs in its own namespace, the same bug stays a small bug.

🔧 How (in this repo)

rbac_can(bindings, roles, subject, verb, resource, namespace) in sec/cloud.py walks the bindings. For each binding of that subject in that namespace (or with namespace "*", the model's way of writing a ClusterRoleBinding), it looks at every rule of the bound role. If the verb and the resource are both in a rule, it returns (True, "<role> allows <verb> <resource>"). If no rule matches, it returns (False, "no binding allows it (RBAC denies by default)"). There is no deny rule to check — just like real RBAC.

k8s_rbac() in sec/demo.py uses two roles (read-grades, deployer) and two bindings (sa:results-api, sa:ci, both in namespace school).

🧪 Try it

python3 sec/demo.py k8s_rbac
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import rbac_can
from demo import ROLES, BINDINGS
roles = {**ROLES, "secret-reader": [dict(verbs={"get"}, resources={"secrets"})],
         "view-all": [dict(verbs={"get", "list", "watch"}, resources={"pods", "services"})]}
bindings = BINDINGS + [dict(subject="sa:results-api", role="secret-reader", namespace="school"),
                       dict(subject="group:auditors", role="view-all", namespace="*")]
for s, v, r, ns in (("sa:results-api", "get", "secrets", "school"),       # new RoleBinding
                    ("sa:results-api", "list", "secrets", "school"),      # only 'get' was granted
                    ("sa:results-api", "get", "secrets", "kube-system"),  # a RoleBinding stays in its namespace
                    ("sa:results-api", "delete", "pods", "school"),       # nobody granted delete
                    ("group:auditors", "list", "pods", "kube-system"),    # a ClusterRoleBinding reaches every namespace
                    ("sa:notices-api", "get", "pods", "school")):         # no binding at all
    print(f"{s:<16} {v:<6} {r:<8} in {ns:<11} → {rbac_can(bindings, roles, s, v, r, ns)}")
EOF
python3 sec/test_sec.py

✅ Verify — what you should see

k8s_rbac prints:

═══ k8s_rbac ═══
── who may do what in the cluster (RBAC denies by default)
   sa:results-api   get    pods         in school      → (True, 'read-grades allows get pods')
   sa:results-api   get    secrets      in school      → (False, 'no binding allows it (RBAC denies by default)')
   sa:ci            update deployments  in school      → (True, 'deployer allows update deployments')
   sa:ci            update deployments  in kube-system → (False, 'no binding allows it (RBAC denies by default)')
   one ServiceAccount per app · no cluster-admin for apps or CI · automountServiceAccountToken: false when unused

✅ done — every door checked

Your snippet prints:

sa:results-api   get    secrets  in school      → (True, 'secret-reader allows get secrets')
sa:results-api   list   secrets  in school      → (False, 'no binding allows it (RBAC denies by default)')
sa:results-api   get    secrets  in kube-system → (False, 'no binding allows it (RBAC denies by default)')
sa:results-api   delete pods     in school      → (False, 'no binding allows it (RBAC denies by default)')
group:auditors   list   pods     in kube-system → (True, 'view-all allows list pods')
sa:notices-api   get    pods     in school      → (False, 'no binding allows it (RBAC denies by default)')

The tests include ✅ L10 Kubernetes RBAC denies what no binding allows and end with 16/16 passed.

🏁 What you just proved

Permissions only add up: the new binding gave get secrets, and nothing else changed. list is a different verb, so it stayed denied. The binding lives in school, so kube-system stayed closed. A subject with no binding — sa:notices-api — can do nothing at all. And a single cluster-wide binding (group:auditors) reached a namespace nobody named — which is exactly why ClusterRoleBindings need the most care. In a real cluster you would also narrow secret-reader to one Secret with resourceNames.

⚠️ Common mistakes

🏭 In production

On a real account — one ServiceAccount per app, with no token mounted, a Role that can read one named Secret and a ConfigMap, and a RoleBinding in the app's namespace:

apiVersion: v1
kind: ServiceAccount
metadata: { name: results-api, namespace: school }
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: results-api, namespace: school }
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["results-settings"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["results-db"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: results-api, namespace: school }
subjects:
  - kind: ServiceAccount
    name: results-api
    namespace: school
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: results-api

In the Deployment, choose the ServiceAccount and keep the token off unless the app calls the API:

spec:
  template:
    spec:
      serviceAccountName: results-api
      automountServiceAccountToken: false

CI deploys to one namespace with the built-in edit ClusterRole, bound by a RoleBinding (so "edit" applies only in school):

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: ci-deployer, namespace: school }
subjects:
  - kind: ServiceAccount
    name: ci
    namespace: ci-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit

Test before you trust — and test what should be denied, too:

kubectl auth can-i get secrets/results-db -n school --as=system:serviceaccount:school:results-api    # yes
kubectl auth can-i list secrets -n school --as=system:serviceaccount:school:results-api              # no
kubectl auth can-i create deployments -n kube-system --as=system:serviceaccount:ci-system:ci        # no
kubectl auth can-i --list -n school --as=system:serviceaccount:school:results-api

Find the dangerous bindings — who has cluster-admin, cluster-wide:

kubectl get clusterrolebindings -o json \
  | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name + " → " + ([.subjects[]? | .kind + ":" + .name] | join(", "))'

On EKS, people and roles from IAM get into the cluster through access entries and access policies (or Kubernetes groups you bind yourself), and pods get AWS permissions through EKS Pod Identity or IRSA — a separate, AWS-side least privilege (the IAM school goes deep on that).

🏭 Why this matters in production: turn on the API server audit log (on EKS: control plane logging for audit) and review 403 responses and every new ClusterRoleBinding. A 403 from an app is often a bug; a burst of 403s from one ServiceAccount is often someone trying doors.

⏭️ Next

RBAC decides who may read a Secret. But what is inside a Secret, and who can reach a pod over the network? Next: Secrets are only base64, and pods talk to everyone — until you say otherwise.

git checkout lesson-11-k8s-secrets-network
← PreviousencryptionNext →k8s secrets network

This page is the lesson's README from the lesson-10-k8s-rbac branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.