RBAC และ Service Account
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”RBAC แยกการตัดสินใจเรื่องสิทธิ์ออกเป็นสองส่วน คือ “อะไรที่อนุญาต” (Role หรือ ClusterRole) กับ “ใครได้สิทธิ์นั้น” (RoleBinding หรือ ClusterRoleBinding) และทุก Pod ทำงานผ่านตัวตนแบบ ServiceAccount ไม่ว่าจะตั้งค่าไว้เองหรือไม่ก็ตาม
Role กับ ClusterRole: อะไรที่อนุญาต
หัวข้อที่มีชื่อว่า “Role กับ ClusterRole: อะไรที่อนุญาต”RBAC (Role-Based Access Control) อยู่ภายใต้ API group rbac.authorization.k8s.io/v1 Role กำหนดชุดสิทธิ์ที่จำกัดอยู่แค่ namespace เดียว เป็น verb อย่าง get, list, watch, create, update, delete ที่ใช้กับ resource ที่ระบุไว้
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: namespace: checkout name: pod-readerrules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]ClusterRole คือแนวคิดเดียวกันแต่ scope ระดับ cluster ใช้ได้ทั้งกรณีให้สิทธิ์กับ resource ที่เป็น cluster-scoped (เช่น nodes หรือ namespaces) หรือให้สิทธิ์กับ resource แบบ namespaced ในลักษณะที่นำไปใช้ซ้ำได้ในหลาย namespace ผ่าน RoleBinding หลายตัว
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: secret-readerrules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list"]ทั้งสองแบบนี้ไม่ได้ให้สิทธิ์อะไรด้วยตัวเองเลย Role หรือ ClusterRole เป็นแค่คำอธิบายสิทธิ์ที่ยังไม่ผูกกับใคร จนกว่าจะมีอะไรมา bind เข้ากับ subject
RoleBinding กับ ClusterRoleBinding: ใครได้สิทธิ์นั้น
หัวข้อที่มีชื่อว่า “RoleBinding กับ ClusterRoleBinding: ใครได้สิทธิ์นั้น”RoleBinding เชื่อม Role (หรือ ClusterRole ที่นำมาใช้ซ้ำใน namespace เดียว) เข้ากับ subject เช่น User, Group, หรือ ServiceAccount ภายใน namespace เดียว
apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: read-pods namespace: checkoutsubjects: - kind: ServiceAccount name: checkout-app namespace: checkoutroleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.ioClusterRoleBinding ทำแบบเดียวกันแต่ให้สิทธิ์ของ ClusterRole ทั่วทั้ง cluster ไม่ใช่แค่ namespace เดียว
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: read-all-secretssubjects: - kind: Group name: platform-team apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.io# Check what a given identity can actually dokubectl auth can-i list pods --namespace checkout --as system:serviceaccount:checkout:checkout-appkubectl get rolebindings,clusterrolebindings -A -o wideServiceAccount: ตัวตนของสิ่งที่รันอยู่ข้างใน Pod
หัวข้อที่มีชื่อว่า “ServiceAccount: ตัวตนของสิ่งที่รันอยู่ข้างใน Pod”Kubernetes ไม่ได้จัดการ human user เองโดยตรง กลุ่มนั้นต้อง authenticate ผ่าน identity provider ภายนอก เช่น OIDC, cloud IAM integration, หรือ client certificate ที่ authentication layer ของ API server สิ่งที่ Kubernetes จัดการเองโดยตรงคือ ServiceAccount ที่เป็นตัวตนของ process ที่รันอยู่ข้างใน Pod
ทุก Pod รันภายใต้ ServiceAccount ตัวใดตัวหนึ่งเสมอ ถ้าไม่ได้ระบุไว้ จะรันเป็น ServiceAccount ชื่อ default ของ namespace นั้น
apiVersion: v1kind: Podmetadata: name: checkout namespace: checkoutspec: serviceAccountName: checkout-app containers: - name: checkout image: checkout:2.3.0โดย default token ของ ServiceAccount จะถูก mount เข้าไปใน filesystem ของ Pod อัตโนมัติ เพื่อให้แอปข้างในใช้ authenticate กับ API server ในฐานะตัวตนนั้นได้ ถ้า workload ไม่เคยคุยกับ Kubernetes API เลย token ที่ mount มาให้อัตโนมัตินี้ก็เป็น attack surface ที่ไม่จำเป็น ควรปิดไปตรง ๆ
apiVersion: v1kind: ServiceAccountmetadata: name: checkout-app namespace: checkoutautomountServiceAccountToken: falseLeast privilege ในทางปฏิบัติ
หัวข้อที่มีชื่อว่า “Least privilege ในทางปฏิบัติ”ประเด็นทั้งหมดของการแยก Role/ClusterRole ออกจาก RoleBinding/ClusterRoleBinding ก็เพื่อให้จำกัดสิทธิ์ได้แคบ ๆ ง่าย ๆ
- ให้เฉพาะ verb ที่ workload ต้องใช้จริง ๆ (
get/list/watchสำหรับ controller ที่อ่านอย่างเดียว ไม่ใช่create/update/delete) - ให้สิทธิ์กับ resource ที่ระบุไว้ชัดเจน และถ้าเป็นไปได้ก็ระบุถึงระดับ
resourceNamesแทนที่จะให้ทั้ง resource type - เลือกใช้ Role แบบ namespaced คู่กับ RoleBinding แทน ClusterRole คู่กับ ClusterRoleBinding เมื่อ workload ต้องการสิทธิ์แค่ใน namespace เดียว
- หลีกเลี่ยงการ bind ServiceAccount ของ workload เข้ากับ ClusterRole ในตัวอย่าง
cluster-adminเพราะให้สิทธิ์ไม่จำกัดกับทุก resource ใน cluster และแทบไม่มี workload ไหนที่จำเป็นต้องใช้ระดับนั้นจริง ๆ
flowchart LR pod["Pod\n(identity: ServiceAccount)"] --> req["Request to API server"] 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"]