この記事はNTTコムウェア Advent Calendar 2021の21日目の記事です。
Kubernetes(k8s)には、Podによる他リソースへのアクセス権限をポリシーとして定義し制御できる機能として、Pod Security Policy(PSP)という機能がありました。
が、しかし、このPSPはk8s v1.21から非推奨となりました1。
そして非推奨となったPSPの後継機能として新たに**Pod Security Admission**という機能がk8s v1.22から提供されるようになりました。
本稿では、この新機能Pod Security Admission(PSA)について概要や具体例等を紹介します。
なお、本稿は、k8sの概要およびある程度の操作を習得している方を対象とします。
前提知識:Podセキュリティ標準とは
PSAを理解するための前提知識として「Podセキュリティ標準(Pod Security Standards)」というものを知っておく必要があるため、ここで紹介します。
k8sの公式サイトに「Podセキュリティ標準」というドキュメントがあります。このドキュメントでは、Podが他リソースへアクセスする際の権限のセットを以下の3つのレベルに大別し、Podに動作要件や目的に合致したレベルの権限のみを与えることで、k8sクラスタのセキュリティを確保しましょう、という内容が記載されています。
これは例えるなら、Linuxにおいて、rootユーザ、一般ユーザ、ゲストユーザ2を定義し、利用者ごとに適切なユーザ権限を与えましょう(全員にroot権限を与えないで)、という話に似ています。
「Podセキュリティ標準」で定義の3つの権限レベル
-
privileged(特権)
- 全く制限がかかっていないポリシー。あらゆる特権昇格を認める。
- Linuxに例えるとrootユーザ。
-
baseline
- privileged(特権)とrestricted(制限)の中間にあたり、最小限の制限を行うポリシー。
- 端的に言えば、Dockerのデフォルト設定でコンテナを動かす際の権限が許可されている状態。
- Linuxに例えると一般ユーザ。
-
restricted(制限)
- ほとんどの権限が厳しく制限されたポリシー。
- 理想的にはなるべくこのポリシーでPodを動作させるとよりセキュアになる。
- Linuxに例えるとゲストユーザ2。
PSAにはこの3つのレベルのポリシーが内部的に実装されており、上記のレベル名を指定するだけで、ポリシーを適用し権限を制限する(または制限しない)ことができます。
なお、上記3つのレベルで具体的にどんな権限が制限または許可されているか、についてはこちらの公式ドキュメントをご覧ください。
Pod Security Admissionとは
Pod Security Admission(PSA)は、PSPの後継として開発された、Podによる他リソースへのアクセス権限を制限するための機能です。
NamespaceごとにPodに与える/与えない権限のセット(=前章で紹介の3種類のレベル)を適用し、それに違反するPodは起動できないようにします。
k8s v1.22で初めてアルファ機能として提供が開始されました。
PSAはk8s v1.22ではまだアルファ機能であるためデフォルトでは無効化されています。有効化するには、feature gatesでPodSecurityをtrueとする必要があります。
k8s v1.23以上であればデフォルトで有効化されています。
PSAには、前章で紹介の3つのレベルのポリシーがあらかじめ内包されており、これをNamespaceに適用することで、そのNamespaceで起動できるPodの権限を制御します。
また、PSAではポリシーに違反した際の実際の挙動について、以下3つのモードにそれぞれ前述のレベルが選択できます。
-
enforce
- ポリシーに違反する場合、Podの起動は拒否されます。
-
warn
- ポリシーに違反する場合、コンソールへその旨の警告が出力されます。Podの起動は拒否されません。
-
audit
- ポリシーに違反する場合、監査ログへその旨の警告が出力されます。Podの起動は拒否されません。
enforceは、ポリシー違反の場合Podの起動が拒否されます。
warnとauditは違反してもPod起動の拒否はされませんがコンソールや監査ログに警告メッセージが出力されます。warnやauditは、警告を出力するだけなので、試験環境でPodの特権をチェックしたり、今後PSAを導入予定の環境で事前にユーザにポリシー準拠を促す目的などでも使用できます。
この3つのモードと、前述の3つのレベルを組み合わせ、Namespaceのラベルに記述することで、当該Namespace内の全Podに対しポリシーを適用します。モードとレベルの関係を図にするとこんな感じです。各モードが独立しそれぞれレベルを1つ選択できます。
以下は「このNamespaceにはbaselineレベルを満たさないPodは起動できない」、というポリシーを持ったNamespaceを定義するマニフェストの例です。
apiVersion: v1
kind: Namespace
metadata:
name: my-ns
labels:
pod-security.kubernetes.io/enforce: baseline
モードとレベルの組み合わせは複数指定することができます。
以下は、3つのポリシー/モードの組み合わせをNamespaceに定義しており、(1)~(3)の全ての挙動を実行します。
apiVersion: v1
kind: Namespace
metadata:
name: my-ns
labels:
pod-security.kubernetes.io/enforce: baseline # (1)
pod-security.kubernetes.io/warn: baseline # (2)
pod-security.kubernetes.io/audit: baseline # (3)
- (1):baselineレベルを満たさないPodは起動できない。
- (2):baselineレベルを満たさないPodが作成されようとした場合、コンソールに警告を出力する。
- (3):baselineレベルを満たさないPodが作成されようとした場合、監査ログに警告を出力する。
なお、以下コマンドですでに作成済みのNamespaceに後からPSA用のラベルを設定することもできます。
この場合、すでに起動済みのPodは仮に新たなポリシーに違反していてもそのまま動き続けます。が、Pod再起動時などは新たなポリシーが適用され、起動できなくなるので注意が必要です。
# kubectl label --overwrite namespace my-ns \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
PSAの概要紹介は以上です。
PSAの具体例
PSAの挙動については、習うより慣れろ、ということでここからは具体的な例を挙げながら実際の挙動を紹介していきます。
筆者環境
筆者は動作確認に以下の環境を使用しました。
重要なのはKubernetesのバージョンがv1.22以上であることであり、ホストOSやディストリビューションは参考までにとどめていただければOKです。
- ホストOS:Rocky Linux 8.4
- Kubernetes: v1.22
実行例
以下のマニフェストでNamespaceを作成します。
baselineレベルを満たさないPodは起動を拒否するポリシーを持ちます。
apiVersion: v1
kind: Namespace
metadata:
name: my-ns
labels:
pod-security.kubernetes.io/enforce: baseline
# kubectl apply -f my-ns.yaml
# kubectl get ns my-ns --show-labels
NAME STATUS AGE LABELS
my-ns Active 18s kubernetes.io/metadata.name=my-ns,pod-security.kubernetes.io/enforce=baseline
このNamespaceに、以下2つのDeploymentを定義しPodを作成してみます。
1つ目のDeploymentは、nginx公式イメージをごく普通に起動するものです。
2つ目もnginx公式イメージを使用していますが、(本来不要ですがあえて)privileged(特権)を使用する設定を加えています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-priv
spec:
replicas: 1
selector:
matchLabels:
app: nginx-priv
template:
metadata:
labels:
app: nginx-priv
spec:
containers:
- name: nginx-priv
image: nginx:latest
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
securityContext:
privileged: true #privileged(特権)を使用
以下コマンドで、上記2つのDeploymentを作成します。
コマンドは特にエラー等無く"created"と表示されます。
# kubectl -n my-ns apply -f deployment.yaml -f deployment-priv.yaml
deployment.apps/nginx created
deployment.apps/nginx-priv created
各リソースがちゃんと作成されたか確認すると、Podが1つしかありません。DeploymentおよびReplicaSetのREADY欄を見ると、2つ目のDeployment「nginx-priv」(privileged(特権)への昇格を要求した方)が0になっており、このPodが作成されていないようです。
# kubectl -n my-ns get all
NAME READY STATUS RESTARTS AGE
pod/nginx-75b69bd684-j4crg 1/1 Running 0 24s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 25s
deployment.apps/nginx-priv 0/1 0 0 21s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-75b69bd684 1 1 1 25s
replicaset.apps/nginx-priv-6db9787dc9 1 0 0 21s
「nginx-priv」の方のReplicaSetの詳細をkubectl describeコマンドで確認してみます。
Events:欄にprivilegedがtrueであってはならない旨のエラーが表示されています。
# kubectl -n my-ns describe replicaset -l app=nginx-priv
・・・
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreate 30s (x14 over 71s) replicaset-controller Error creating: privileged (container "nginx-priv" must not set securityContext.privileged=true)
PSAにより、privileged(特権)への昇格を要求した方のPodの起動が制限されることを確認できました。
これで、不用意にPodに特権を与えてしまい、Podの中からノードや他のリソースを好き勝手にいじられる、といった被害を防ぐことができます。
皆様も、是非PSAを活用し、よりセキュアにk8sを運用し、心安らかにクリスマス&年末をお過ごしください。
※本稿に記載されている製品名、サービス名は、各団体の商標または登録商標です。
-
こちらのk8s公式ブログに非推奨に至る経緯等が詳しく記載されています。 ↩
-
Linuxに明確にゲストユーザという概念はありませんが、例えばユーザは存在し一部サービスへのアクセスは出来るが、ssh等でのログインは禁止されている、など、権限が一般ユーザよりさらに制限されているという状況をイメージしてください。 ↩ ↩2
-
k3sの場合、通常のk3s起動コマンドに以下4つの引数を追加します。
--kube-apiserver-arg=feature-gates=PodSecurity=true
--kube-scheduler-arg=feature-gates=PodSecurity=true
--kube-controller-manager-arg=feature-gates=PodSecurity=true
--kubelet-arg=feature-gates=PodSecurity=true ↩