Skip to content

Kubernetes RBAC 帳號管理

在 AKS 叢集建立兩種用途的受限 ServiceAccount kubeconfig:Power User(全域維運但禁刪 Node/Namespace)與 Restricted(特定 Namespace 僅讀 Pod Log),並附完整憑證輪替 SOP。

概述

直接使用叢集管理員 kubeconfig 進行日常維運,一旦操作失誤(誤刪 Namespace、誤刪 Node)後果可能是災難性且難以復原的。透過 Kubernetes RBAC(Role-Based Access Control)建立最小權限帳號,可以在不影響維運效率的前提下有效隔離風險。

本文涵蓋兩種典型受限帳號模式:

  • Power User:具備全域維運能力(可刪除工作負載),但明確禁止刪除 nodesnamespaces,適合日常部署、Debug、事故處理
  • Restricted:僅限特定 Namespace 的 Pod 讀取與 Log 串流,適合提供給外部支援人員或監控工具

兩種帳號採用相同設計模式:ServiceAccount + ClusterRole/RoleBinding + 靜態 Token Secret + 自動化 kubeconfig 生成腳本,以及完整的憑證輪替 SOP。

核心內容

設計原則

最小權限(Least Privilege):Power User 能刪除工作負載(Pods、Deployments)但不能刪除底層基礎設施(Node、Namespace);Restricted 帳號只能讀取,完全不能修改任何資源。

K8s 1.24+ 靜態 Token:Kubernetes 1.24 後,ServiceAccount 不再自動產生永久 Token。需要明確建立 type: kubernetes.io/service-account-token 的 Secret,Kubernetes 才會注入長期 Token。長期 Token 沒有過期日,須搭配憑證輪替 SOP 管理。

憑證輪替策略:刪除並重建 ServiceAccount(而非只刪除 Secret),因為 K8s Token 的唯一性綁定於 ServiceAccount 的 UID。重建 SA 後 UID 改變,所有舊 Token 立即全部失效,是最可靠的停權方式。

Power User 帳號設計

使用 ClusterRoleBinding(全域作用),結合兩條 RBAC rule:

  1. 對所有資源開放 get/list/watch/create/update/patch
  2. 對工作負載資源(Pods、Deployments 等,明確列舉)開放 delete
  3. nodesnamespaces 僅開放 get/list/watch/create/update/patch(明確排除 delete

Restricted 帳號設計

使用 ClusterRole(作為可重用模板)+ RoleBinding(限定特定 Namespace):

  • pods 的 get/list/watch
  • pods/log 的 get

RoleBinding 將 ClusterRole 效力限制在指定 Namespace,帳號無法存取其他 Namespace 的任何資源。

驗證方式

使用 kubectl auth can-i 對新 kubeconfig 做權限模擬,直接向 API Server 確認是否允許特定操作。在交付前就能確認權限範圍符合預期,比實際執行操作更安全。

關鍵要點

  • K8s 1.24+ 必須手動建立 kubernetes.io/service-account-token Secret 才能取得長期 Token
  • 憑證輪替應刪除並重建 ServiceAccount(不只刪 Secret),確保舊 UID 完全失效
  • Power User 使用 ClusterRoleBinding(全域);Restricted 使用 RoleBinding(限 Namespace)
  • kubectl auth can-i 是交付前的必要驗證步驟
  • 產出的 kubeconfig 為長期憑證,需妥善保管並在人員異動時立即輪替

實際應用

Power User kubeconfig 提供給需要全域維運能力的維運人員,用於日常部署、Debug、事故處理。人員離職時立即執行憑證輪替 SOP 停權。

Restricted kubeconfig 提供給外部協作廠商、監控整合(如 Grafana 讀取 Pod Log)、或新進人員的受限存取。範例中帳號範圍鎖定在 ingress-basic namespace,無法對其他 namespace 進行任何操作。

部署設定參考

Power User RBAC YAML

yaml
# power-user-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: power-user-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: power-user-role
rules:
  # 1. 允許對所有資源進行讀取、建立與更新
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  # 2. 允許刪除工作負載(明確列舉,不含 nodes 和 namespaces)
  - apiGroups: ["*"]
    resources:
      - "pods"
      - "deployments"
      - "statefulsets"
      - "daemonsets"
      - "jobs"
      - "cronjobs"
      - "services"
      - "ingresses"
      - "configmaps"
      - "secrets"
      - "persistentvolumeclaims"
      - "events"
    verbs: ["delete"]
  # 3. nodes 和 namespaces 僅給基礎管理權限,明確排除 delete
  - apiGroups: [""]
    resources: ["nodes", "namespaces"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: power-user-global-binding
subjects:
  - kind: ServiceAccount
    name: power-user-sa
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: power-user-role
  apiGroup: rbac.authorization.k8s.io

Power User Token Secret

yaml
# power-user-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: power-user-token
  namespace: kube-system
  annotations:
    kubernetes.io/service-account.name: power-user-sa
type: kubernetes.io/service-account-token

Power User Kubeconfig 生成腳本

bash
#!/bin/bash
# generate_power_user_config.sh

SA_SECRET_NAME="power-user-token"
NAMESPACE="kube-system"
USER_NAME="power-user-sa"
CLUSTER_NAME="k8s-power-cluster"
KUBECONFIG_FILE="power-user.kubeconfig"
CONTEXT_NAME="power-user-context"

APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA_CRT=$(kubectl get secret $SA_SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.ca\.crt}')
TOKEN=$(kubectl get secret $SA_SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.token}' | base64 --decode)

kubectl config set-cluster $CLUSTER_NAME \
  --server=$APISERVER \
  --embed-certs=true \
  --certificate-authority=<(echo "$CA_CRT" | base64 --decode) \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config set-credentials $USER_NAME \
  --token=$TOKEN \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config set-context $CONTEXT_NAME \
  --cluster=$CLUSTER_NAME \
  --user=$USER_NAME \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config use-context $CONTEXT_NAME --kubeconfig=$KUBECONFIG_FILE
echo "Success: $KUBECONFIG_FILE 已產生(全域維運,禁刪 Node/Namespace)"

Power User 驗證

bash
CHECK_KUBECONFIG="power-user.kubeconfig"
printf "全域讀取 Pods:   [%s]\n" "$(kubectl --kubeconfig=$CHECK_KUBECONFIG auth can-i list pods --all-namespaces)"
printf "刪除 Pods:       [%s]\n" "$(kubectl --kubeconfig=$CHECK_KUBECONFIG auth can-i delete pods)"
printf "建立 Namespace:  [%s]\n" "$(kubectl --kubeconfig=$CHECK_KUBECONFIG auth can-i create namespaces)"
printf "刪除 Namespace:  [%s] (預期 no)\n" "$(kubectl --kubeconfig=$CHECK_KUBECONFIG auth can-i delete namespaces)"
printf "刪除 Node:       [%s] (預期 no)\n" "$(kubectl --kubeconfig=$CHECK_KUBECONFIG auth can-i delete nodes)"
測試項目預期
全域讀取 Pods✅ YES
刪除 Pods✅ YES
建立 Namespace✅ YES
刪除 Namespace❌ NO
刪除 Node❌ NO
完全管理員("*" "*"❌ NO

Restricted(Pod Log Viewer)RBAC YAML

yaml
# read-logs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-log-viewer-sa
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-log-reader   # ClusterRole 作為可重用模板
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-log-viewer-binding
  namespace: ingress-basic   # 權限僅在此 Namespace 生效
subjects:
  - kind: ServiceAccount
    name: pod-log-viewer-sa
    namespace: ingress-basic
roleRef:
  kind: ClusterRole
  name: pod-log-reader
  apiGroup: rbac.authorization.k8s.io

Restricted Token Secret

yaml
# sa-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: pod-log-viewer-token
  namespace: ingress-basic   # 務必與 SA 同一個 Namespace
  annotations:
    kubernetes.io/service-account.name: pod-log-viewer-sa
type: kubernetes.io/service-account-token

Restricted Kubeconfig 生成腳本

bash
#!/bin/bash
# generate_kubeconfig.sh

SA_SECRET_NAME="pod-log-viewer-token"
NAMESPACE="ingress-basic"
USER_NAME="pod-log-viewer-sa"
CLUSTER_NAME="k8s-restricted-cluster"
KUBECONFIG_FILE="restricted.kubeconfig"

APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA_CRT=$(kubectl get secret $SA_SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.ca\.crt}')
TOKEN=$(kubectl get secret $SA_SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.token}' | base64 --decode)

if [ -z "$TOKEN" ]; then
  echo "錯誤:抓不到 Token,請確認 Secret 是否存在於 $NAMESPACE"
  exit 1
fi

kubectl config set-cluster $CLUSTER_NAME \
  --server=$APISERVER \
  --embed-certs=true \
  --certificate-authority=<(echo "$CA_CRT" | base64 --decode) \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config set-credentials $USER_NAME \
  --token=$TOKEN \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config set-context $NAMESPACE-context \
  --cluster=$CLUSTER_NAME \
  --user=$USER_NAME \
  --namespace=$NAMESPACE \
  --kubeconfig=$KUBECONFIG_FILE

kubectl config use-context $NAMESPACE-context --kubeconfig=$KUBECONFIG_FILE
echo "Success: $KUBECONFIG_FILE 已產生(僅 $NAMESPACE namespace 的 Pod 讀取/Log 權限)"

Restricted 驗證

測試項目預期指令
讀取 Pod 列表✅ YESkubectl --kubeconfig=restricted.kubeconfig auth can-i get pods
讀取 Pod Log✅ YESkubectl --kubeconfig=restricted.kubeconfig auth can-i get pods/log
跨 Namespace 讀取❌ NOkubectl --kubeconfig=restricted.kubeconfig auth can-i get pods -n kube-system
刪除 Pod❌ NOkubectl --kubeconfig=restricted.kubeconfig auth can-i delete pods

憑證輪替 SOP(兩種帳號通用)

bash
# 步驟 A:立即停權(刪除舊 ServiceAccount)
kubectl delete -f <rbac-yaml>
kubectl delete -f <token-secret-yaml>   # 若未隨 SA 一起刪除

# 步驟 B:重新核發新身分
kubectl apply -f <rbac-yaml>
kubectl apply -f <token-secret-yaml>

# 步驟 C:重新產生 kubeconfig
./generate_<type>_config.sh

為什麼刪除並重建 ServiceAccount? Token 的唯一性綁定於 SA 的 UID,重建後 UID 改變,所有基於舊 SA 的 Token 立即全部失效。只刪 Secret 不能確保舊 Token 完全無效。

相關概念

來源