Certified Kubernetes Application Developer (CKAD) #14 ServiceAccount と RBAC (アプリ視点)
#13 ConfigMap と Secret の深掘り でアプリに設定値や秘密値を注入する方法を身につけました。ところが、アプリが単に値を受け取るだけでなく、Pod の中から直接 Kubernetes API を呼び出す 必要がある場合があります。コントローラーパターンで他のリソースを読んだり、自分の名前空間の Pod 一覧を取得したり、ConfigMap を更新したりするアプリがそうです。
このとき「その呼び出しが誰の権限で起きるのか」を決めるのが ServiceAccount (SA) と RBAC です。人が kubectl でクラスターを操作するときに使う身元がユーザーアカウントだとすれば、Pod の中のアプリが API を呼び出すときに使う身元が ServiceAccount です。CKAD は運用者試験の CKA ほど RBAC を深くは問いませんが、アプリに適切な SA を付けて最小権限を与える レベルは試験範囲です。今回の記事はそのアプリ視点に集中します。
Pod の中のアプリが API を呼び出す図 #
まず大きな絵をつかみます。アプリが Kubernetes API を呼び出すと、API サーバーは 2 つの段階を経ます。
- 認証 (Authentication): このリクエストを送った身元は誰か。Pod の中のアプリは ServiceAccount トークンで自分の身元を証明します。
- 認可 (Authorization): その身元はこの動作を行う権限があるか。RBAC (Role-Based Access Control) が「どの身元がどのリソースにどの動作を行えるか」をルールで判定します。
ServiceAccount は 1 段階目の身元を提供し、RBAC は 2 段階目の権限を定義します。両者は RoleBinding でつながります。つまり SA を作り、Role で権限を定義し、RoleBinding でその 2 つを束ねる という流れです。この流れをそのまま手に馴染ませれば試験問題の大半が解けます。Kubernetes の認証・認可のより広い背景は K8s 中級 #7 で一緒に整理しました。
ServiceAccount #
名前空間ごとにある default SA #
ServiceAccount は名前空間に属するリソースです。名前空間を作ると default という名前の ServiceAccount が自動で 1 つ生成されます。Pod spec に serviceAccountName を明示しなければ、Pod は自分の名前空間の default SA で実行されます。
# 現在の名前空間の SA 一覧
k get serviceaccount
k get sa # 短縮形
# default SA の詳細
k get sa default -o yamldefault SA には基本的に何の権限も付与されていません。そのため default SA で動くアプリが API を呼び出すと、大半は Forbidden で拒否されます。権限が必要なアプリには 専用 SA を別に作って 最小権限だけを与えるのが定石です。
SA の作成 #
専用 SA は命令型で 1 行で作ります。
# app-sa という SA を作成
k create serviceaccount app-sa
# dry-run でマニフェストの骨格だけ取り出す
k create sa app-sa $do > sa.yaml生成されたマニフェストは kind: ServiceAccount に名前と名前空間だけが入った非常にシンプルな形です。
Pod に SA を付ける #
Pod がどの SA で実行されるかは spec の serviceAccountName で指定します。
apiVersion: v1
kind: Pod
metadata:
name: api-client
spec:
serviceAccountName: app-sa
containers:
- name: app
image: bitnami/kubectl
command: ["sleep", "3600"]実行中の Deployment の SA を変える必要があるときは、spec を直接編集するか set コマンドを使います。
# Deployment の SA を入れ替える
k set serviceaccount deployment web app-saserviceAccountName は Pod 生成時点にのみ適用されるため、既存 Pod の SA を変えるには Pod を作り直す必要があります。Deployment なら上のコマンドが新しい ReplicaSet へのロールアウトを起こし、自動で入れ替わります。
トークンマウント #
自動マウントされる SA トークン #
Pod が起動すると、Kubernetes はその SA のトークンをコンテナ内の決まったパスに自動でマウントします。
/var/run/secrets/kubernetes.io/serviceaccount/
├── token # SA の JWT トークン
├── ca.crt # API サーバー検証用の CA 証明書
└── namespace # Pod が属する名前空間の名前アプリやクライアントライブラリ (in-cluster config) はこのパスのトークンを読んで API サーバーに認証します。コンテナの中で直接確認してみると、トークンがマウントされているのが見えます。
k exec api-client -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
k exec api-client -- ls /var/run/secrets/kubernetes.io/serviceaccount/自動マウントを切る #
API を呼び出さないアプリにまでトークンをマウントするのは不要な露出です。最小権限の原則に従い、トークンが要らない Pod は自動マウントを切るのが安全です。automountServiceAccountToken: false で切ります。
apiVersion: v1
kind: Pod
metadata:
name: no-token
spec:
automountServiceAccountToken: false
containers:
- name: app
image: nginxこの設定は ServiceAccount 側にも置けます。SA に掛けるとその SA を使うすべての Pod に既定で適用され、Pod spec の設定が SA 設定より優先されます。
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
automountServiceAccountToken: falseprojected token: audience と expiration #
最近の Kubernetes は自動マウントされるトークンを projected volume で注入します。この方式はトークンに 寿命 (expiration) と 対象 (audience) を指定できるため、無期限に有効なトークンより安全です。自動マウントされる既定のトークンはクラスターがこの形で入れてくれますが、特定の audience や有効期限が必要なら直接 projected volume で明示します。
apiVersion: v1
kind: Pod
metadata:
name: projected-token
spec:
serviceAccountName: app-sa
containers:
- name: app
image: nginx
volumeMounts:
- name: token
mountPath: /var/run/secrets/tokens
readOnly: true
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
audience: my-api # トークンを受け取る対象の識別子
expirationSeconds: 3600 # 1 時間後に期限切れexpirationSeconds でトークンの寿命を制限し、audience でそのトークンがどのサービスに向けたものかを固定します。kubelet は期限が近づくとトークンを自動で更新して再マウントします。
RBAC: アプリに権限を与える #
SA を付けるだけでは権限は生まれません。default SA がそうであるように、新しく作った SA も最初は何もできません。権限を与えるには Role で権限を定義 し、RoleBinding でその権限を SA に接続 する必要があります。
Role: 名前空間スコープの権限の束 #
Role は「どのリソース (resources) にどの動作 (verbs) を許可するか」を 1 つの名前空間の中で定義します。動作は get、list、watch、create、update、patch、delete のような API 動詞です。
# pod に対する get/list/watch 権限を持つ Role を作成
k create role pod-reader \
--verb=get,list,watch \
--resource=pods
# dry-run でマニフェストを確認
k create role pod-reader --verb=get,list,watch --resource=pods $do生成された Role マニフェストは rules の下に apiGroups・resources・verbs の 3 つが入ります。core API グループ (pod、service、configmap など) の apiGroups は空文字列 "" だという点がよく間違える箇所です。全体の形は後の全体例で確認します。
RoleBinding: Role を SA に接続 #
RoleBinding は Role が定義した権限を特定の主体 (subject) に付与します。主体はユーザーでも、グループでも、ServiceAccount でもあり得ます。アプリ視点では SA が主体です。
# pod-reader Role を dev 名前空間の app-sa SA に付与
k create rolebinding app-sa-pod-reader \
--role=pod-reader \
--serviceaccount=dev:app-sa--serviceaccount の値は 名前空間:SA 名 の形式です。生成された RoleBinding は roleRef (どの権限を与えるか) と subjects (誰に与えるか) の 2 つの部分から成ります。これで app-sa で動く Pod は dev 名前空間で Pod を取得できます。完成した形は後の全体例で確認します。
権限の検証: auth can-i #
権限が正しく付いたかは kubectl auth can-i で確認します。--as フラグで特定の身元になりすまして検査するのが肝です。SA の身元文字列は system:serviceaccount:名前空間:SA 名 の形式です。
# app-sa が dev で pod を get できるか
k auth can-i get pods \
--as=system:serviceaccount:dev:app-sa \
-n dev
# -> yes
# app-sa が pod を delete できるか (Role にない)
k auth can-i delete pods \
--as=system:serviceaccount:dev:app-sa \
-n dev
# -> noyes または no で即座に答えが出るため、RBAC 問題を解いた後に検算する最速の方法です。試験で権限を付与したら、必ずこのコマンドで一度確認する習慣をつけるのが安全です。
ClusterRole と ClusterRoleBinding #
Role と RoleBinding が 1 つの名前空間の中でのみ有効なのに対し、ClusterRole と ClusterRoleBinding はクラスター全体スコープです。ノードのように名前空間に属さないリソースや、複数の名前空間にまたがる権限が必要なときに使います。アプリ視点では大半が名前空間スコープで十分なので Role・RoleBinding を優先して使い、ClusterRole の詳しい活用は運用者試験の CKA 領域でより深く扱います。
全体例: SA + Role + RoleBinding + Pod #
ここまでの断片を 1 つに合わせると、専用 SA で動くアプリに Pod 取得権限だけを最小で与える完成したマニフェストになります。
# ServiceAccount: アプリ専用の身元
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: dev
---
# Role: dev 名前空間で pod を読む権限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: dev
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
# RoleBinding: pod-reader 権限を app-sa に付与
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-sa-pod-reader
namespace: dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-reader
subjects:
- kind: ServiceAccount
name: app-sa
namespace: dev
---
# Pod: app-sa で実行されるアプリ
apiVersion: v1
kind: Pod
metadata:
name: api-client
namespace: dev
spec:
serviceAccountName: app-sa
containers:
- name: app
image: bitnami/kubectl
command: ["sleep", "3600"]適用した後、Pod の中で直接権限を確認してみます。SA トークンが自動マウントされているので、コンテナ内の kubectl はそのトークンで認証します。
k apply -f app-rbac.yaml
# 権限が付いたかの最も信頼度が高い検証: 実際の Pod の中から呼び出す
k exec -n dev api-client -- kubectl get pods
# -> dev 名前空間の Pod 一覧が出力されれば成功
k exec -n dev api-client -- kubectl get secrets
# -> Forbidden。Role に secret 権限がないので拒否される最後の 2 つのコマンドが示すように、付与した権限の中でのみ動作し、それ以外は拒否されます。これが最小権限の原則が実際に働く姿です。
試験ポイント #
- SA は名前空間リソース です。明示しなければ Pod は
defaultSA で実行され、default SA には権限がありません。 - Pod に SA を付けるフィールドは
spec.serviceAccountNameです。実行中の Deployment はk set serviceaccount deploy <名前> <sa>で入れ替えます。 - SA トークンは
/var/run/secrets/kubernetes.io/serviceaccount/に自動マウントされます。要らなければautomountServiceAccountToken: falseで切ります (Pod spec が SA 設定より優先)。 - 権限付与は Role (権限定義) + RoleBinding (SA に接続) の 2 段階です。
k create roleとk create rolebinding --serviceaccount=ns:saを 1 行で作るのを手に馴染ませると時間が節約できます。 - 検算は
k auth can-i <verb> <resource> --as=system:serviceaccount:<ns>:<sa> -n <ns>です。yes/noで即座に確認できます。 - ClusterRole・ClusterRoleBinding はクラスタースコープです。アプリ視点では Role・RoleBinding を優先して使います。
- Role の
rulesで core API グループ (pod、service、configmap など) のapiGroupsは空文字列""です。この点をよく間違えます。
まとめ #
この記事で押さえたこと:
- ServiceAccount は Pod の中のアプリが API を呼び出すときに使う身元です。専用 SA を作って最小権限だけを与えるのが定石です。
- トークン自動マウント は
automountServiceAccountTokenで制御し、projected token で audience と有効期限を指定できます。 - RBAC は Role で権限を定義し、RoleBinding で SA に接続する 2 段階です。
- 検証 は
k auth can-i ... --as=system:serviceaccount:...で即座に確認します。 - 権限は常に 必要な分だけ 与えます。これが次の記事のコンテナ権限制限とともに、アプリセキュリティの 2 つの軸を成します。
次へ: SecurityContext と Capabilities #
この記事はアプリが API サーバーに対して 何ができるかを RBAC で制限しました。次の記事は視線を一段内側に移し、コンテナが 自分が動くノードとカーネルに対して 何ができるかを制限します。
#15 SecurityContext と Capabilities では、runAsUser と runAsNonRoot でコンテナを非ルートで実行する方法、fsGroup でボリューム所有権を合わせる方法、readOnlyRootFilesystem でルートファイルシステムを読み取り専用にロックする方法、そして Linux capabilities を add/drop で精密に調整する方法を直接作りながら整理します。