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

RBAC และ Service Account

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

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/v1
kind: Role
metadata:
namespace: checkout
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]

ClusterRole คือแนวคิดเดียวกันแต่ scope ระดับ cluster ใช้ได้ทั้งกรณีให้สิทธิ์กับ resource ที่เป็น cluster-scoped (เช่น nodes หรือ namespaces) หรือให้สิทธิ์กับ resource แบบ namespaced ในลักษณะที่นำไปใช้ซ้ำได้ในหลาย 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 ที่นำมาใช้ซ้ำใน namespace เดียว) เข้ากับ subject เช่น User, Group, หรือ ServiceAccount ภายใน namespace เดียว

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: checkout
subjects:
- kind: ServiceAccount
name: checkout-app
namespace: checkout
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

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
Terminal window
# Check what a given identity can actually do
kubectl auth can-i list pods --namespace checkout --as system:serviceaccount:checkout:checkout-app
kubectl get rolebindings,clusterrolebindings -A -o wide

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: v1
kind: Pod
metadata:
name: checkout
namespace: checkout
spec:
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: v1
kind: ServiceAccount
metadata:
name: checkout-app
namespace: checkout
automountServiceAccountToken: false

ประเด็นทั้งหมดของการแยก 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"]
ServiceAccount ที่ bind ผ่าน RoleBinding เข้ากับ Role แล้วถูกตรวจสอบโดย API server ทุกครั้งที่มี request
ความต่างสำคัญระหว่าง Role กับ ClusterRole คืออะไร
RoleBinding เชื่อมอะไรเข้ากับอะไรจริง ๆ
ทำไม Pod ถึงมีตัวตนแบบ RBAC ทั้งที่ไม่เคยตั้งค่า ServiceAccount ให้เองเลย
หลัก least privilege ในบริบทของ RBAC หมายความว่ายังไง