在 K8S 裡,不論是建立 Pod、修改 Deployment,還是讀取 Secret 等資源,大多都需要與 API Server 進行互動。
API Server 可以想成 K8S Control Plane 的主要入口。當我們送出建立或修改資源的請求後,API Server 會處理請求,並將 K8S 的資源狀態保存到 etcd。接著,Scheduler、Controller 等元件會持續觀察這些資源的狀態,讓實際狀態逐漸符合我們所宣告的期望狀態。
但這時候就出現一個很重要的問題:
既然 K8S API 可以控制 Cluster 裡的資源,那 API Server 怎麼知道「現在是誰在呼叫我」?
總不能任何人只要找到 API Server,就可以隨意建立 Pod、讀取 Secret,甚至修改整個 Cluster。
因此,在存取 K8S API 時,「身分」就變得非常重要。
人類使用者需要有自己的身分,而執行在 K8S 裡面的 Pod / Workload,同樣需要一個可以被辨識的身分。
這時候就要認識今天的主角:
ServiceAccount。
一起先來看看 K8S 在官方網站是如何介紹 ServiceAccount:
K8S官方文件:Service Accounts
官方文件將 ServiceAccount 定義為一種非人類帳號(non-human account),用來提供 Kubernetes 叢集中的身分。Pod 中的應用程式或其他自動化程式,可以使用它的憑證向 API Server 驗證身分。
ServiceAccount 具有以下三個特性:
了解 ServiceAccount 的基本特性後,接著透過實作觀察它的憑證與身分。
先進入 Pod 裡面使用以下指令:
ls -l /var/run/secrets/kubernetes.io/serviceaccount/

先來介紹一下 /var/run/secrets/kubernetes.io/serviceaccount/ 這個路徑,一樣可以在 K8S 的官方文件看到對這個路徑的說明:
預設情況下,每一個 Pod 都會關聯到一個 ServiceAccount,而該 ServiceAccount 的憑證(Token)會被放置到 Pod 中每一個 Container 的檔案系統內,路徑就是 /var/run/secrets/kubernetes.io/serviceaccount/
若 Pod 或 ServiceAccount 停用了自動掛載,例如設定 automountServiceAccountToken: false,就不能假設容器內一定存在這個 Token 檔案。
在這邊看到的三個檔案分別的作用是:
當攻擊者取得 Pod 內的執行能力後,掛載其中的 ServiceAccount Token 可能成為優先檢查的目標之一。
接著來看一下現在這個 Pod 的 Namespace 跟 Token 的身分資訊,使用以下指令查看:
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace; echo
cut -d. -f2 /var/run/secrets/kubernetes.io/serviceaccount/token | tr '_-' '/+' | base64 -d 2>/dev/null; echo

小提醒:JWT 的 Payload 使用不含補齊符號 = 的 Base64URL 編碼。使用一般 base64 -d 解碼時,部分工具需要先轉換字元並補齊 =,否則可能出現錯誤或輸出不完整。以下指令會先完成這些處理,再進行解碼。
payload=$(cut -d. -f2 /var/run/secrets/kubernetes.io/serviceaccount/token |
tr '_-' '/+')
case $((${#payload} % 4)) in
2) payload="${payload}==" ;;
3) payload="${payload}=" ;;
esac
printf '%s' "$payload" | base64 -d
目前 Pod 所在的 Namespace 為 lab20,下面的輸出稍微整理一下格式:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1822547973,
"iat": 1791011973,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "f2d06360-3656-4124-98ef-d28360925dac",
"kubernetes.io": {
"namespace": "lab20",
"node": {
"name": "css-leon-12-183-demo",
"uid": "4f52641f-2831-49a9-a8fb-f991a5871854"
},
"pod": {
"name": "victim",
"uid": "b3ded702-556a-401f-b9d0-139b87d11e4f"
},
"serviceaccount": {
"name": "default",
"uid": "c15b52c3-6c0c-4b0a-9305-85357282e2bf"
},
"warnafter": 1791015580
},
"nbf": 1791011973,
"sub": "system:serviceaccount:lab20:default"
}
從這個 JSON 可以獲得幾個重要訊息:
前面的解碼只能查看 Token 中的宣告,不能證明它目前有效。接著使用這個 Token 呼叫 SelfSubjectReview,確認 API Server 實際辨識到的呼叫者身分:
SA=/var/run/secrets/kubernetes.io/serviceaccount
curl -s --cacert $SA/ca.crt -H "Authorization: Bearer $(cat $SA/token)" \
-H 'Content-Type: application/json' -X POST \
https://kubernetes.default.svc/apis/authentication.k8s.io/v1/selfsubjectreviews \
-d '{"apiVersion":"authentication.k8s.io/v1","kind":"SelfSubjectReview"}'

這邊補上完整的 JSON 內容:
{
"kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1",
"metadata": {
"creationTimestamp": "2026-10-03T14:10:51Z"
},
"status": {
"userInfo": {
"username": "system:serviceaccount:lab20:default",
"uid": "c15b52c3-6c0c-4b0a-9305-85357282e2bf",
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:lab20",
"system:authenticated"
],
"extra": {
"authentication.kubernetes.io/credential-id": [
"JTI=03581abb-85d6-4660-835f-b5dd5c0bd3f7"
],
"authentication.kubernetes.io/node-name": [
"css-leon-12-183-demo"
],
"authentication.kubernetes.io/node-uid": [
"4f52641f-2831-49a9-a8fb-f991a5871854"
],
"authentication.kubernetes.io/pod-name": [
"victim"
],
"authentication.kubernetes.io/pod-uid": [
"b3ded702-556a-401f-b9d0-139b87d11e4f"
]
}
}
}
}
前者可以查看 Token 中宣告的身分與時間資訊,後者則透過 SelfSubjectReview,確認 API Server 實際辨識到的呼叫者身分。兩者搭配,有助於理解 Token 的內容,以及它被用來呼叫 API 時的身分辨識結果。
接著測試目前身分是否有權列出 lab20 Namespace 中的 Secret。
curl -s -o /dev/null -w "list secrets -> HTTP %{http_code}\n" --cacert $SA/ca.crt \
-H "Authorization: Bearer $(cat $SA/token)" \
https://kubernetes.default.svc/api/v1/namespaces/lab20/secrets

前面的 SelfSubjectReview 已確認 API Server 辨識到的 ServiceAccount 身分,但列出 lab20 命名空間內 Secret 的請求仍被拒絕。這說明「身分驗證成功」不等於「具有操作資源的權限」,後者還需要經過授權判斷。
ServiceAccount 是身分,ServiceAccount Token 則是證明該身分的憑證。本次透過 Bearer Token 向 API Server 驗證身分,但通過驗證後能操作哪些資源,仍取決於授權設定,例如下一篇要介紹的 RBAC。
感謝大家今天的閱讀,這個系列已經來到第 20 天了。無論你是第一次讀到我的文章,還是一路追蹤到現在,都很謝謝你的支持。明天接著討論 K8s 的 RBAC,我們明天見!