ข้ามไปยังเนื้อหา

RBAC และ Service Accounts

RBAC แยกการตัดสินใจเรื่องสิทธิ์ทุกครั้งออกเป็นสองส่วน คือ “อะไรที่อนุญาต” (Role หรือ ClusterRole) กับ “ใครได้สิทธิ์นั้น” (RoleBinding หรือ ClusterRoleBinding) และทุก Pod ทำงานผ่านตัวตนแบบ ServiceAccount — หนุนหลังด้วย token อายุสั้นแบบ bound ไม่ใช่ Secret แบบเก่าที่อยู่ยืน — ไม่ว่าคุณจะตั้งไว้เองหรือไม่ก็ตาม

RBAC (Role-Based Access Control) อยู่ใน API group rbac.authorization.k8s.io/v1 Role กำหนดชุดสิทธิ์ที่ scope อยู่ใน namespace เดียว — verb อย่าง get, list, watch, create, update, delete ที่ apply กับ resource เฉพาะ

k8s/rbac-and-service-accounts/pod-reader-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: rbac-demo
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]

ClusterRole คือไอเดียเดียวกันแต่ scope ระดับ cluster — มันให้สิทธิ์เข้าถึง resource ระดับ cluster (อย่าง nodes หรือ namespaces) หรือให้สิทธิ์เข้าถึง resource ที่ namespace ในแบบที่ reuse ได้ในหลาย namespace ผ่าน RoleBinding หลายตัว

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]

ทั้งสองอย่างไม่ได้ให้สิทธิ์อะไรด้วยตัวเอง — Role หรือ ClusterRole เป็นแค่คำอธิบายสิทธิ์ที่ลอยอยู่เฉย ๆ จนกว่าจะมีอะไรมา bind มันเข้ากับ subject

RoleBinding เชื่อม Role (หรือ ClusterRole ที่ reuse ในหนึ่ง namespace) เข้ากับ subject — User, Group, หรือ ServiceAccount — ภายใน namespace เดียว

k8s/rbac-and-service-accounts/pod-reader-binding.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-reader-sa
namespace: rbac-demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: rbac-demo
subjects:
- kind: ServiceAccount
name: pod-reader-sa
namespace: rbac-demo
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

RoleBinding อ้างถึง ClusterRole ได้ด้วยในขณะที่ตัวเองยัง scope เป็น namespace — นี่คือวิธีมาตรฐานในการ reuse ชุดสิทธิ์ระดับ cluster หนึ่งชุด (เช่น view ClusterRole ที่ built-in) ในหลาย namespace โดยไม่ต้อง copy-paste rule ของ Role เดิมซ้ำ ๆ — binding ต่างหากที่ยัง namespace-scoped ไม่ใช่ role

ClusterRoleBinding ให้สิทธิ์ของ ClusterRole ทั่วทั้ง cluster ไม่ใช่แค่ namespace เดียว

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-all-secrets
subjects:
- kind: Group
name: platform-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io

คำสั่งสองตัวตอบว่า “ฉันคือใคร” และ “ฉันทำอะไรได้จริง” กับ API server ที่รันอยู่จริง ไม่ใช่แค่อ่าน rule บนกระดาษ

$ kubectl auth whoami
ATTRIBUTE VALUE
Username kubernetes-admin
Groups [kubeadm:cluster-admins system:authenticated]
$ kubectl auth whoami --as=system:serviceaccount:rbac-demo:pod-reader-sa
ATTRIBUTE VALUE
Username system:serviceaccount:rbac-demo:pod-reader-sa
Groups [system:serviceaccounts system:serviceaccounts:rbac-demo system:authenticated]

kubectl auth whoami เรียก API SelfSubjectReview และรายงานตัวตนกับ group membership ที่ API server สรุปให้ผู้เรียกจริง ๆ — รวมกับ --as มันยังยืนยันว่า impersonated identity resolve ออกมาเป็นอะไรแน่ ๆ ซึ่งมีประโยชน์ตอนที่ group ของ ServiceAccount (system:serviceaccounts:<namespace> โดยเฉพาะ) มีผลต่อ binding

$ kubectl auth can-i --list --as=system:serviceaccount:rbac-demo:pod-reader-sa -n rbac-demo
Resources Non-Resource URLs Resource Names Verbs
selfsubjectreviews.authentication.k8s.io [] [] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
pods [] [] [get list watch]

--list พิมพ์ชุดกฎที่มีผลจริง ทั้งหมด ของตัวตนนั้นในครั้งเดียว — pods: [get list watch] ตรงนี้คือ rule ของ pod-reader Role เป๊ะ ๆ บวกกับ default ของ API server บางส่วนที่ทุกตัวตนที่ authenticated แล้วได้ (API selfsubject* ที่ kubectl auth whoami/can-i เองก็พึ่งพา)

$ kubectl delete pod nonexistent -n rbac-demo --as=system:serviceaccount:rbac-demo:pod-reader-sa
Error from server (Forbidden): pods "nonexistent" is forbidden: User "system:serviceaccount:rbac-demo:pod-reader-sa" cannot delete resource "pods" in API group "" in the namespace "rbac-demo"
$ kubectl auth can-i delete pods --as=system:serviceaccount:rbac-demo:pod-reader-sa -n rbac-demo
no

API server reject request ด้วย 403 Forbidden จริง และระบุ verb, resource, API group, และ namespace ที่มันเช็คตรง ๆ — pod-reader-sa ลบ Pod จริง ๆ ไม่ได้ เพราะ Role ของมันให้แค่ get/list/watch พิสูจน์แล้วด้านบน

Kubernetes ไม่ได้จัดการ human user แบบ native — คนจะ authenticate ผ่าน identity provider ภายนอก (OIDC, cloud IAM integration, client certificate ฯลฯ) ที่ชั้น authentication ของ API server สิ่งที่ Kubernetes จัดการแบบ native จริง ๆ คือ ServiceAccount ตัวตนที่ process ข้างใน Pod ใช้

ทุก Pod รันในฐานะ ServiceAccount บางตัวเสมอ ถ้าไม่ระบุเลยมันรันเป็น ServiceAccount default ของ namespace ตั้งแต่ Kubernetes 1.24 การสร้าง ServiceAccount ไม่ auto-create Secret แบบ kubernetes.io/service-account-token ที่อายุยืนคู่กันอีกต่อไป — พิสูจน์: ServiceAccount ที่เพิ่งสร้างไม่มี Secret ติดมาเลย

$ kubectl create serviceaccount pod-reader-sa -n rbac-demo
serviceaccount/pod-reader-sa created
$ kubectl get secrets -n rbac-demo
No resources found in rbac-demo namespace.

แทนที่แบบนั้น token ทุกตัวที่ mount ถูก issue ผ่าน API TokenRequest เป็น bound service account token: อายุสั้น, scope ตาม audience, และผูกกับ object Pod ที่มันถูก issue ให้เจาะจง — API server invalidate มันได้ทันทีที่ Pod นั้นถูกลบ ซึ่ง token แบบ Secret ที่อยู่ยืนทำแบบนั้นไม่ได้เลย kubectl create token เรียก API เดียวกันนี้โดยตรง

$ kubectl create token pod-reader-sa -n rbac-demo --duration=10m --audience=custom-aud
eyJhbGciOiJSUzI1NiIs...
$ # decoding the JWT payload:
{
"aud": ["custom-aud"],
"exp": 1790418726,
"iat": 1790418126,
"kubernetes.io": {
"namespace": "rbac-demo",
"serviceaccount": {"name": "pod-reader-sa", "uid": "d373193f-..."}
},
"sub": "system:serviceaccount:rbac-demo:pod-reader-sa"
}

exp เท่ากับ iat บวก --duration ที่ขอไว้โดยประมาณ (อายุจริง ไม่ใช่ “ตลอดไป”) และ aud scope ตรงกับ audience ที่ขอเป๊ะ ๆ — token ที่ mint มาสำหรับ custom-aud ใช้กับ API server (หรือ service อื่นใด) ที่คาด audience อื่นไม่ได้ token ที่ auto-mount ให้ Pod (นอกจาก automountServiceAccountToken: false จะปิดไว้ พูดถึงในบท Pod and Cluster Security) ก็เป็นกลไกเดียวกันนี้ project เข้าไปเป็น volume

$ kubectl get pod default-mount -n psa-demo -o jsonpath='{.spec.volumes}'
[{"name":"kube-api-access-47t24","projected":{"defaultMode":420,"sources":[{"serviceAccountToken":{"expirationSeconds":3607,"path":"token"}}, ...]}}]

expirationSeconds: 3607 — ประมาณหนึ่งชั่วโมง — และ kubelet refresh มันให้อัตโนมัติเมื่อใกล้หมดอายุ โดยไม่ต้องเขียน credential อายุยืนลง etcd เป็น Secret เลย

apiVersion: v1
kind: Pod
metadata:
name: checkout
namespace: checkout
spec:
serviceAccountName: checkout-app
containers:
- name: checkout
image: checkout:2.3.0

aggregationRule ให้ ClusterRole หนึ่งตัวดูดซับ rule ของ ClusterRole อื่นทุกตัวที่ match label selector เข้ามาอัตโนมัติ — rules: [] ของ aggregate role เริ่มว่างเปล่าแล้ว control plane เติมให้เอง

k8s/rbac-and-service-accounts/aggregated.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-aggregate
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac-demo/aggregate-to-monitoring: "true"
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-pods-part
labels:
rbac-demo/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-nodes-part
labels:
rbac-demo/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list"]
$ kubectl get clusterrole monitoring-aggregate -o jsonpath='{.rules}'
[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list"]},{"apiGroups":[""],"resources":["pods"],"verbs":["get","list"]}]

rule ของ ClusterRole อีกสองตัวปรากฏใน monitoring-aggregate อัตโนมัติ มาจาก label ที่แชร์กันล้วน ๆ — ClusterRole ตัวที่สามที่สร้างทีหลังด้วย label เดียวกันจะเข้าร่วม aggregate ได้โดยไม่ต้องแก้ monitoring-aggregate เองเลย ClusterRole built-in ของ Kubernetes เองอย่าง admin/edit/view ก็ aggregate แบบนี้เป๊ะ ๆ ซึ่งเป็นวิธีที่ operator ของ CRD ขยาย edit/view ให้ครอบคลุม custom resource ของตัวเองได้แค่ label ClusterRole ของตัวเอง

verb impersonate ให้ตัวตนหนึ่งทำ request ในนามของ อีกตัวตนได้ — กลไกที่ --as เองก็พึ่งพา การให้สิทธิ์แบบแคบ ด้วย resourceNames scope ไปที่ ServiceAccount ตัวใดตัวหนึ่งเฉพาะ

k8s/rbac-and-service-accounts/impersonator.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sa-impersonator
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["impersonate"]
resourceNames: ["pod-reader-sa"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sa-impersonator-binding
subjects:
- kind: ServiceAccount
name: overprivileged-sa
namespace: rbac-demo
roleRef:
kind: ClusterRole
name: sa-impersonator
apiGroup: rbac.authorization.k8s.io

พิสูจน์ว่าการ scope ด้วยชื่อเป็นจริงและถูกบังคับใช้ ไม่ใช่แค่คำแนะนำ

$ kubectl auth can-i impersonate serviceaccounts/pod-reader-sa --as=system:serviceaccount:rbac-demo:overprivileged-sa -n rbac-demo
yes
$ kubectl auth can-i impersonate serviceaccounts/some-other-sa --as=system:serviceaccount:rbac-demo:overprivileged-sa -n rbac-demo
no

overprivileged-sa impersonate ได้แค่ ServiceAccount ตัวเดียวที่ระบุใน resourceNames และถูกปฏิเสธสำหรับตัวอื่นใด — impersonate เป็น verb ที่ authorize แยกต่างหากจริง ๆ ไม่ใช่ side effect ของสิทธิ์อื่น

cluster-admin เป็น ClusterRole built-in แบบ pre-aggregated ที่ให้ทุก verb บนทุก resource ทั่ว cluster การผูก ServiceAccount ของ workload เข้ากับมันเป็น RBAC anti-pattern ที่พบบ่อยที่สุดอย่างหนึ่ง เพราะมันให้สิทธิ์มากกว่าที่ workload ไหนควรต้องการมาก

$ kubectl auth can-i delete secrets --as=system:serviceaccount:rbac-demo:overprivileged-sa -A
yes
$ kubectl auth can-i delete nodes --as=system:serviceaccount:rbac-demo:overprivileged-sa
yes

ServiceAccount ที่ตั้งใจไว้แค่ให้อ่าน ConfigMap ใน namespace ของตัวเอง — พอผูกกับ cluster-admin แล้ว — ลบ Secret ไหนใน namespace ไหนก็ได้ และลบ Node ของ cluster ได้เลย container ที่ถูก compromise ตัวเดียวที่ใช้ตัวตนนั้นก็มีสิทธิ์เท่ากับ human cluster administrator วิธีแก้ก็เป็นหลักการเดียวกับที่พิสูจน์ตลอดทั้งบทนี้ คือ Role แบบ namespace (หรือ ClusterRole ที่ scope แคบ ๆ จำกัดด้วย resourceNames ถ้าทำได้) บวก binding ที่ให้แค่ verb ที่ workload เรียกใช้จริง

flowchart LR
  pod["Pod\n(identity: ServiceAccount)"] --> req["Request to API server\n(bound token, TokenRequest API)"]
  req --> authz["API server\nauthorization check"]
  sa["ServiceAccount"] --> rb["RoleBinding"]
  role["Role\n(verbs on resources)"] --> rb
  rb --> authz
  authz -- "allowed" --> ok["Request proceeds"]
  authz -- "denied" --> deny["403 Forbidden\n(proven above)"]
ServiceAccount ที่ผูกผ่าน RoleBinding เข้ากับ Role ซึ่งถูก API server เช็คทุก request

RBAC ตอบคำถามเดียวเสมอ: given ตัวตนนี้ (User, Group, หรือ ServiceAccount) มี binding สักตัว ที่ผูก Role/ClusterRole ให้กับมัน แล้ว role นั้นให้ verb นี้บน resource นี้ตรงเป๊ะไหม ทุกอย่างที่เหลือในบทนี้คือ variation ของการทำให้ lookup นั้นแคบลง (Role scope แค่ namespace เดียว, resourceNames scope แค่ object เดียว) หรือประกอบง่ายขึ้น (aggregationRule, reuse ClusterRole ผ่าน RoleBinding แบบ namespace) ServiceAccount ขยายคำถามเดียวกันนี้ไปให้ workload แทนที่จะเป็น human และตั้งแต่ 1.24 credential ที่หนุนหลังตัวตนนั้นคือ token แบบ bound อายุสั้น scope ตาม audience ที่ issue ตามที่ต้องการโดย API TokenRequest — ไม่เคยเป็น Secret แบบ static ที่นั่งอยู่ใน etcd ตลอดไป impersonate คือ RBAC เช็คตัวเอง มันก็แค่ verb อีกตัวบน resource type อีกแบบ อยู่ภายใต้กลไก Role/RoleBinding เดียวกันเป๊ะ

ทางเลือกข้อดีต้นทุน
Role แบบ namespace + RoleBindingscope แคบ พิสูจน์แล้วด้านบนด้วย 403 จริงสำหรับอะไรก็ตามนอก verb ของมันต้องทำซ้ำต่อ namespace ถ้าต้องการสิทธิ์เดียวกันในหลาย namespace
ClusterRole ที่ reuse ผ่าน RoleBinding แบบ namespaceพิสูจน์แล้วด้านบน: rule ชุดเดียว ผูกแคบต่อ namespace ไม่ต้อง duplicateมองไม่ชัดทันทีว่า namespace ไหนได้สิทธิ์นั้นจริง ๆ — ต้องเช็ค binding ไม่ใช่แค่ role
ClusterRoleBinding ไปที่ ClusterRoleง่าย object เดียวให้สิทธิ์ทั่ว clusterทั่ว cluster ตามชื่อเลย — พิสูจน์แล้วด้านบนด้วย cluster-admin นี่คือวิธีที่ binding เดียวที่กว้างเกินไปกลายเป็น blast radius ร้ายแรงได้
ClusterRole แบบ aggregatedพิสูจน์แล้วด้านบน: ประกอบได้ ขยายได้โดย component ไหนก็ label ClusterRole ของตัวเอง ไม่ต้องแก้ที่ศูนย์กลางaudit ยากขึ้นว่า “role นี้ให้อะไรจริง ๆ” ถ้าไม่หา role ที่ label ไว้ทุกตัวด้วย
bound SA token (TokenRequest, default ตั้งแต่ 1.24)พิสูจน์แล้วด้านบน: อายุสั้น scope ตาม audience ไม่มี Secret อายุยืนให้หลุดอะไรที่อ่าน token แบบ out-of-band (log, build step ยาว) ต้องรองรับการหมดอายุ ไม่เหมือน token แบบถาวรรุ่นเก่า
  • “Role กับ ClusterRole คือของเดียวกัน แค่ชื่อต่างกัน” พิสูจน์แล้วด้านบน: rule ของ Role มีผลแค่ใน namespace ของตัวเองเท่านั้น ส่วน ClusterRole ให้สิทธิ์ resource ระดับ cluster (nodes) ที่ Role ไหนก็เอ่ยถึงไม่ได้เลย
  • “ทุก ServiceAccount ได้ Secret token อายุยืนอัตโนมัติเหมือนสมัยก่อน” พิสูจน์แล้วด้านบน: ServiceAccount ที่เพิ่งสร้างไม่มี Secret เลย — ตั้งแต่ Kubernetes 1.24 token เป็นแบบ bound อายุสั้นและขอตามความต้องการผ่าน TokenRequest/kubectl create token เท่านั้น หรือ auto-mount เข้า Pod แบบเดียวกันนี้
  • “kubectl auth can-i --list โชว์แค่สิ่งที่ Role/ClusterRole ให้ตรง ๆ” พิสูจน์แล้วด้านบน: output มี default ของ API server บางส่วน (selfsubjectreviews, selfsubjectaccessreviews) ที่ทุกตัวตนที่ authenticated แล้วได้เสมอ ควบคู่กับสิ่งที่ binding จริงให้
  • “ผูก workload กับ cluster-admin ก็โอเคตราบใดที่ workload นั้นเชื่อถือได้” พิสูจน์แล้วด้านบน: cluster-admin ให้สิทธิ์ลบ Node และ Secret ทุก namespace ไกลเกินความต้องการจริงของ workload แทบทุกตัว — ความเสี่ยงอยู่ที่จะเกิดอะไรถ้า workload นั้นถูก compromise ไม่ใช่ว่ามันน่าเชื่อถือวันนี้ไหม
  • “verb impersonate แค่ทำให้ kubectl --as ใช้ได้กับทุกคน ตราบใดที่ RBAC ตั้งค่าไว้แล้ว” พิสูจน์แล้วด้านบนด้วย can-i check จริง: impersonation ถูก authorize (หรือ deny) ต่อตัวตน และ scope ได้ด้วย resourceNames ไปที่ subject เป้าหมายตัวใดตัวหนึ่งเฉพาะ เหมือน verb อื่นทุกตัว

ตอน review manifest RBAC หรือการตั้งค่า ServiceAccount ที่ AI สร้าง ให้เช็ค

  • ServiceAccount ของ workload ที่ผูกกับ cluster-admin “เพื่อเลี่ยง permission error” — พิสูจน์แล้วด้านบน มันให้สิทธิ์ลบ Node ของ cluster และ Secret ทุก namespace วิธีแก้คือ Role ที่ scope แคบ ระบุแค่ verb ที่ใช้จริง
  • manifest หรือ tutorial ที่สร้าง Secret แบบ kubernetes.io/service-account-token เองแล้ว mount มัน — พิสูจน์แล้วด้านบน นี่คือ pattern ก่อน 1.24 token แบบ bound ผ่าน projected volume default (หรือ kubectl create token สำหรับใช้นอก cluster) คือแนวทางปัจจุบันและหมดอายุเอง
  • ClusterRoleBinding ที่ใช้ในจุดที่ RoleBinding แบบ namespace (แม้จะอ้าง ClusterRole ก็ตาม) scope ให้แค่ namespace เดียวได้เพียงพอแล้ว แทนที่จะเป็นทั้ง cluster
  • Role/ClusterRole ที่ให้ verb "*" หรือ resource "*" ในจุดที่มีแค่ไม่กี่ verb เฉพาะที่ถูกเรียกใช้จริง — wildcard กว้าง ๆ ทำลายจุดประสงค์ของ least privilege ไปเลย และ audit ยากกว่า list ที่ระบุชัด ๆ มาก
  • verb impersonate ที่ให้โดยไม่มี resourceNames ทั้งที่มีแค่ subject เดียวเฉพาะที่ต้องถูก impersonate จริง ๆ — พิสูจน์แล้วด้านบนว่า scope ด้วยชื่อทำได้และถูกบังคับใช้จริง การให้แบบไม่ scope ทำให้ผู้ถือ impersonate ตัวตนไหนก็ได้ของประเภทนั้น

🐛 หา bug ในโค้ดนี้

k8s/rbac-and-service-accounts/checkout-binding.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout-app
namespace: checkout
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: checkout-app-binding
subjects:
- kind: ServiceAccount
name: checkout-app
namespace: checkout
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
เฉลย

checkout-app ต้องการแค่อ่าน Pod กับ ConfigMap ใน namespace ของตัวเอง แต่ตรงนี้ผูกมันกับ cluster-admin ทั่ว cluster พิสูจน์แล้วด้านบนด้วย binding แบบเดียวกัน: kubectl auth can-i delete secrets --as=system:serviceaccount:checkout:checkout-app -A จะได้ yes — ServiceAccount นี้ลบ Secret ไหนใน namespace ไหนก็ได้ หรือลบ Node ของ cluster ได้เลย ซึ่งไม่มีอันไหนที่ “อ่าน Pod กับ ConfigMap ของตัวเอง” ต้องการเลย วิธีแก้คือ Role แบบ namespace ใน checkout ที่ให้แค่ get/list/watch บน pods/configmaps ผูกด้วย RoleBinding ไม่ใช่ ClusterRoleBinding ไปที่ cluster-admin

ความแตกต่างสำคัญระหว่าง Role กับ ClusterRole คืออะไร
ทำไม request นี้ถึงถูก forbidden ทั้งที่ Pod ที่ระบุไม่มีอยู่จริง
$ kubectl delete pod nonexistent -n rbac-demo --as=system:serviceaccount:rbac-demo:pod-reader-sa
Error from server (Forbidden): pods "nonexistent" is forbidden: User "system:serviceaccount:rbac-demo:pod-reader-sa" cannot delete resource "pods" ...
ตั้งแต่ Kubernetes 1.24 เกิดอะไรขึ้นเมื่อคุณสร้าง ServiceAccount ใหม่
monitoring-aggregate ถูกสร้างด้วย rules: [] และ label selector ของ aggregationRule rule พวกนี้มาจากไหน
$ kubectl get clusterrole monitoring-aggregate -o jsonpath='{.rules}'
[{"apiGroups":[""],"resources":["nodes"],...},{"apiGroups":[""],"resources":["pods"],...}]
ผลลัพธ์คู่นี้พิสูจน์อะไรเกี่ยวกับ verb impersonate ที่ overprivileged-sa ได้รับ
$ kubectl auth can-i impersonate serviceaccounts/pod-reader-sa --as=system:serviceaccount:rbac-demo:overprivileged-sa -n rbac-demo
yes
$ kubectl auth can-i impersonate serviceaccounts/some-other-sa --as=system:serviceaccount:rbac-demo:overprivileged-sa -n rbac-demo
no
ทำไมการผูก ServiceAccount ของ workload กับ cluster-admin ถึงอันตราย แม้ว่า workload นั้นจะเชื่อถือได้ตอนนี้ก็ตาม