Pod Security Admission Label Synchronization のソースコード読んでみたかった

OpenShift

はじめに

みなさんこんにちは、最近パンを焼いている者です。

これは「OpenShift Advent Calendar 2022」 17日目の記事です。OpenShiftの他に、KubernetesとAnsibleのAdvent Calendarにも参加したので、よければご覧ください〜。

今回はOpenShift特有の機能 PSA Label Synchronization がよくわからなかったのでソースコード読んでみました。なお、以下に出てくるドキュメントやソースコードは、OpenShift 4.11のものです。

Pod Security Standards (PSS) と Pod Security Admission (PSA)

Kubernetes 1.25では、従来のPod Security Policy (PSP) が削除され、 Pod Security Admission (PSA) という新しいAdmission Controllerに置き換えられました。詳細はKEP参照。

また以下の動画もわかりやすかったです。

Pod Security Standards (PSS)

Pod Security Standards
A detailed look at the different policy levels defined in the Pod Security Standards.

一応もう少し説明すると、Pod Security Standards (PSS) には3つのpolicy level (Privileged, Baseline, Restricted)が予め定義されています。ドキュメント内の表を引用すると、それぞれ概要は以下の通りです。とてもシンプル。

Profile Description
Privileged Unrestricted policy, providing the widest possible level of permissions. This policy allows for known privilege escalations.
Baseline Minimally restrictive policy which prevents known privilege escalations. Allows the default (minimally specified) Pod configuration.
Restricted Heavily restricted policy, following current Pod hardening best practices.

引用元: https://kubernetes.io/docs/concepts/security/pod-security-standards/

Pod Security Admission (PSA)

Pod Security Admission
An overview of the Pod Security Admission Controller, which can enforce the Pod Security Standards.

Pod Security Admission (PSA)は、Pod Security Standards (PSS)を適用するための新しいAdmission Controllerです。適用時には3つのmodeが用意されています。最も厳しいenforce modeの場合、Podの実行自体が拒否されます。

Mode Description
enforce Policy violations will cause the pod to be rejected.
audit Policy violations will trigger the addition of an audit annotation to the event recorded in the audit log, but are otherwise allowed.
warn Policy violations will trigger a user-facing warning, but are otherwise allowed.

引用元: https://kubernetes.io/docs/concepts/security/pod-security-admission/

基本的にはNamespace単位で pod-security.kubernetes.io/<MODE>: <LEVEL> みたいなlabelを付与することで、適用するPSS levelとそのmodeを指定できます。

OpenShift 4.11におけるPSA Label

新規作成したProject/Namespaceを見ると、restricted policyがaudit/warn modeで有効化されているのがわかります。

SCC (Security Context Constraints) への影響

OpenShift としてはPSPではなくSCC (Security Context Constraints)を利用していましたが、今回の変更の影響は少なからずあったようです。詳細は、Important OpenShift changes to Pod Security Standards という記事にも載っています。

具体的には、OpenShift 4.11では、新しいSCC policy (restricted-v2, nonroot-v2, hostnetwork-v2)が導入されています。これは、PSSに従ってワークロードを認可するためのものです。

restricted/restricted-v2を少しみてみます。yamlのannitationに、v1からの変更点が書いてあり親切でした。手元で見えたSCCをコピペしますので興味あればdiffとってみてください。

restricted-v2 denies access to all host features and requires pods to be run with a UID, and SELinux context that are allocated to the namespace. This is the most restrictive SCC and it is used by default for authenticated users. On top of the legacy ‘restricted’ SCC, it also requires to drop ALL capabilities and does not allow privilege escalation binaries. It will also default the seccomp profile to runtime/default if unset, otherwise this seccomp profile is required.

Pod Security Admission Label Synchronization

前置きが長すぎましたが、いよいよ今回調べてみたかったPod Security Admission Label Synchronizationのお話です。

OpenShiftには、Service Accountに付与されているSCCの設定にあわせて、Namespaceに付与するPSAのラベルを調整する機能 Pod Security Admission Label Synchronization があります。これによりSCCが適切に設定されていれば、適切なPSAラベルがNamespaceに自動で付与され、基本的にPodの実行が拒否されたりWarningが表示されることはなくなります。

こいつの仕組みについてよくわからないので、PSA Label Synchronization Controllerのソースコードを眺めて完全に理解したいと思います。

cluster-policy-controller/pkg/psalabelsyncer at release-4.11 · openshift/cluster-policy-controller
Controllers required to maintain policy resources to create pods. - openshift/cluster-policy-controller

pkg/cmd/controller/psalabelsyncer.go

まず、controllerがどのように呼び出されているか見てみます。runPodSecurityAdmissionLabelSynchronizationController() の実装を見てみると、cluster-policy-controller/pkg/psalabelsyncer パッケージのNewPodSecurityAdmissionLabelSynchronizationController() でインスタンス化したあと、goroutineとして controllerを起動(Run())しています

pkg/psalabelsyncer/podsecurity_label_sync_controller.go

PodSecurityAdmissionLabelSynchronizationController構造体

PodSecurityAdmissionLabelSynchronizationController 構造体は以下のように定義されています。コメントに記載があるように、Namespaceに"security.openshift.io/scc.podSecurityLabelSync: <true or false>" ラベルをつけることで、Label Synchronization自体を有効/無効化できるようです。

メンバーはご覧の通りで、client-goのclientや各種lister、Work queueが含まれます。この辺の登場人物は、Sample Controllerのドキュメントがわかりやすいかもしれません。


引用元: https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md

またKubernetesのCustom Controllerについては、わかりやすい書籍があります。

NewPodSecurityAdmissionLabelSynchronizationController()

PodSecurityAdmissionLabelSynchronizationController を生成する関数です。

まず、RoleBinding、ClusterRoleBindingのIndexerを追加しています。saToSCCCacheに使用されるようです。saToSCCCacheは、RoleとRoleBindingをキャッシュし、それに基づいてクラスタに存在するSCCをキャッシュする構造体で、これにより与えられたServiceAccountとSCCのセットを取得することができます。

次にcontrolledNamespacesLabelSelectorSyncContext を生成しています。controlledNamespacesLabelSelectorPodSecurityAdmissionLabelSynchronizationController で使用するラベルセレクターを返し、SyncContextSync() 関数に渡されるコンテキストを表します。workQueue には、SyncContext.Queue() で返されるキューを指定するようです。

そして、saToSCCCachePodSecurityAdmissionLabelSynchronizationController を生成して、Controllerを返します。最後のControllerを返すところでWithSync(c.sync) を指定しているので、後ほど出てくる *PodSecurityAdmissionLabelSynchronizationController.sync() で同期処理が行われているのがわかります。

*PodSecurityAdmissionLabelSynchronizationController.sync()

同期処理の実装はここに書いています。

まずWorkQueueからキーを取り出し、キーをもとにNamespaceを取得します。そして、PodSecurityAdmissionLabelSynchronizationControllerの管理対象のNamespaceでかつNamespaceのStatusがTerminatingでない場合のみ、syncNamespace() を実行します。

特にif !isNSControlled(ns) のところで、対象Namespaceが管理対象のものか判定します。isNSControlled() 関数はこんな感じで、OpenShift/Kubernetesシステムに関するNamespaceにはfalseを返し、他のNamespaceはついてはsecurity.openshift.io/scc.podSecurityLabelSync ラベルの値を見ています。

*PodSecurityAdmissionLabelSynchronizationController.syncNamespace()

一番重要そうな syncNamespace() メソッドに辿り着きました。ちょっと長いです。

最初のところでは、対象のNamespace内のServiceAccountを全て取得し、各ServiceAccountが与えられたNamespace内でPodを実行する時に使用可能なSCCを取得し、nsSCCsInsertしています。

次に対象Namespaceの現在のSCCの状況がわかったのでPSA levelを求めます。nsSCCs に入れておいたSCCを順に見ていき、convertSCCToPSALevel() で変換しています。currentNSLevel としては最も緩いものを採用します。

現状のNamespaceのラベルが、先ほど求めたPSA levelや定数で定義されているPSA versionと異なる場合にラベルを更新します。

以上により、Pod Security Admission Label Synchronization Controllerくんは無事にSCCからPSA levelを算出し、NamespaceのPSAラベルとして適用することができました。めでたしめでたし。

pkg/psalabelsyncer/scctopsamapping.go

convertSCCToPSALevel()

あまり深入りしませんが、どんなSCCがどんなPSA Levelに変換されるかは、こちらに実装されています。SCCの各フィールドを見ていき、内容に応じて、privileged / baseline / restricted を返します。

PSA Label Synchronizationの Enhancement の資料の中に、SCC to PSa Level Transformation という章があったので、これを読むのがわかりやすそうです (なお私は読んでません)。

さいごに

2022年ありがとうございました。2023年もよろしくお願いいたします。それでは、良いお年を〜

References

コメント