Kyvernoで kubernetesクラスターのStorage Class利用を制限してみる

# Kyvernoで kubernetesクラスターのStorage Class利用を制限してみる
Kubernetes環境をマルチテナントで利用している環境では、管理者さんは 各テナントユーザ向けに リソースの利用を制限する必要が発生するケースがあるかと思います。その際 標準の認可機能だけでは要件にあった制限の実装が難しいといったことがあるかと思います。

[**Kyverno**](https://kyverno.io/)は専用の言語なしで利用できる Kubernetes専用の**Policyエンジン**です。
**2022/7に インキュベーションプロジェクト** に移動しています。 比較的 シンプルに ポリシー定義を実装でき、Validate以外に Mutate, Generate, Cleanup などの機能が 提供されているのも特徴かと思います。

今回は この Kyvernoの Validate 機能を利用して 特定のNamespaceで 利用可能なStorageClassを制限する設定を試してみたのでその内容を紹介します。


## 前提
以下の環境で試しています

- kyverno 1.7.5
- kubespray release-2.18
- Kubernetes 1.22.6
- CentOS 7.7
- Astra Trident 22.10.0
- ONTAP 9.9.1

Kubernetes環境の Persistent Volumes利用のための準備（ CSI関連設定、Backend/Storage Class作成など）は 実施済みの前提で記載しています。 Astra Tridentを利用する場合は[こちら](https://docs.netapp.com/ja-jp/trident/index.html)にドキュメント等も公開されていますので参考にしてください。


## やってみること
1. Kyverno Install
2. Kyverno Cluster Policyを作成
3. Policyでの制御ができていることを確認（PVCを作成してみる)
4. 参考： Preconditionsで変数を利用した制御

## 1. Kyverno Install
最初にKyvernoのインストールを実施します。

インストールはHelmチャートを利用して実施しています。
Kyvernoは専用の名前空間にインストールが必要で、他のリソースとの混在はできません。

```shell:
$ helm repo add kyverno https://kyverno.github.io/kyverno/
"kyverno" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "kyverno" chart repository
Update Complete. ⎈Happy Helming!⎈
$ helm install kyverno kyverno/kyverno -n kyverno --create-namespace --version v2.5.5

NAME: kyverno
LAST DEPLOYED: Thu Mar  2 09:59:14 2023
NAMESPACE: kyverno
STATUS: deployed
REVISION: 1
NOTES:
Chart version: v2.5.5
Kyverno version: v1.7.5

Thank you for installing kyverno! Your release is named kyverno.
⚠️  WARNING: Setting replicas count below 3 means Kyverno is not running in high availability mode.

💡 Note: There is a trade-off when deciding which approach to take regarding Namespace exclusions. Please see the documentation at https://kyverno.io/docs/installation/#security-vs-operability to understand the risks.
$ kubectl -n kyverno get pod
NAME                       READY   STATUS    RESTARTS   AGE
kyverno-7f8bc655c9-4wvq5   1/1     Running   0          6m41s
$
```
Kyvernoは バージョン毎にサポートしている Kubernetesクラスター バージョンが異なります。
今回は試した環境が Kubernetes v1.22.6なので オプションで Kyvernoのバージョンを 1.7.5で指定してインストールしています。
(利用できるバージョンは "helm search repo kyverno -l" で 確認できます)

また、検証用ということで High Availabilityモード 用の レプリカ数の指定をせずにインストールしているため、 その旨の警告がでています。
本番環境に適用する場合は " --set replicaCount=3"をつけて HAモードとし 冗長性を高めることが可能です。

今回はシンプルなインストール手順で試していますが、インストールのオプション等については [Kyvernoのドキュメント](!https://kyverno.io/docs/installation/)に 記述があります。


## 2. Kyverno Cluster Policyを作成
最初に ClusterPolicyを作成します。
ここでは、特定のNamespaceに PVC(PersistentVolumeClaim)を作成する時に、Storage Class名が指定したものかをチェックするClusterPolicyを作成します。 

KyvernoのValidate機能で、 特定のリソースを作成する際の パラメータ を チェックすることが可能です。
この機能を利用して PVC(PersistentVolumeClaim)作成時に 指定されている Storage Class名をチェックできます。

なお、許可されたStorage Classを指定していない(ルールが守られていない)場合に、リソース作成を ブロックするのか、レポートに記録するだけにするのかを指定可能です。今回はブロックするように指定します。(PVC作成を拒否する)

以下のように適用対象と、適用ルールの内容を記述したYAMLファイルを作成し、applyします。

```shell:
$ cat << EOF > cpol.yaml
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: cpol11
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: cpolicy01
    match:
      any:
      - resources:
          kinds:
          - PersistentVolumeClaim
          namespaces:
          - "nstest01"
          - "nstest11"
    validate:
      message: "Hello!!! This is kyverno feature test!!! Thanks!"
      pattern:
        spec:
          storageClassName: ontap-nas-app1
EOF
$ kubectl apply -f cpol.yaml
clusterpolicy.kyverno.io/cpol11 created 
$ kubectl get cpol
NAME     BACKGROUND   ACTION    READY
cpol11   false        enforce   true
$
```
Cluster Policyを作成できました。 


なお、今回のPolicyの記述内容 詳細を以下に記述していますが、長くなるので折りたたんでいます。

<details><summary>ポリシーの記述内容の説明</summary><div>

***

#### ポリシーの記述内容
ポリシーのYAMLファイルに記述している内容について、主要な項目は以下となります。

```plaintext:
---
apiVersion: kyverno.io/v1
# The `ClusterPolicy` kind applies to the entire cluster.
kind: ClusterPolicy
metadata:
  name: cpol11
spec:
  validationFailureAction: enforce  ・・・ ①
  background: false
  rules:
  - name: cpolicy01
    match:　　　　　　　　　　　　　　　　　　　　　　　　　　  　　　　　　　　　  　　　　　　　　　・・・ ②
      any:　　　　　　　　　　　　　　　　　　 　　　　　　　　　  　　　　　　　　　  　　　　　　　　　  　　　　　・・・ ②-1
      - resources:　 　　　　　　　　　  　　　　　　　　　  　　　　　　　　　  　　　　　　・・・ ②-2
          kinds:
          - PersistentVolumeClaim
          namespaces:
          - "nstest01"
          - "nstest11"
    validate:　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　・・・ ③
      message: "Hello!!! This is kyverno feature test!!! Thanks!"  ・・・ ③-1
      pattern:                       　　　　　　 ・・・ ③-2
        spec:
          storageClassName: ontap-nas-app1
```

##### ①： リクエスト内容を検証した後のアクション指定 (validationFailureAction)
　・ルールに適合しなかった場合のアクションの指定。
　・validationFailureAction=enforceで、ルールに適合しなかった時のアクション=ブロック
　・validationFailureAction=auditに変更すると、ルールに適合しなかった時のアクション＝監査ログを出力するのみ。 
　・[validationFailureActionOverrides](!https://kyverno.io/docs/writing-policies/validate/)を利用するとNamespace毎に個別にアクションを指定可能
　・validationFailureAction以外のその他の共通設定も[ドキュメント](!https://kyverno.io/docs/writing-policies/policy-settings/)で紹介されています。

##### ②： ルール適用対象の記述(match)
　・match欄で　ルールを適用する対象 を指定しています。
　・今回の記述では、ルール適用対象 は、"リソースの種類: PersistentVolumeClaim(PVC) 、 NameSpace : nstest01 or nstest11" となります。
　・matchのかわりにexcludeと記述するとルールから除外する対象を指定可能
　・②-1では any / allを指定可能。 論理or(any) / 論理and(all) いずれかを指定します。複数リソースを記述をした場合に or / and で リソースが選択されるようになります。
　・②-2では、 ルールを適用する対象リソースを記述しています。 他に Subjects(User/User Gropu/Service Accout)、Role/ClusteRoleの指定を記述することも可能です

##### ③ 検証内容の記述(validate)
　・Validate欄でリソース構成をチェックして、ポリシーに準拠しているかどうかを確認できます。
　・リクエスト内容で 指定されている パラメータ の検証を行うことが可能です。（リクエストが ルール通りのパラメータを指定してしているかをチェック）
　・今回の記述では "spec.storageClassName" が "ontap-nas-app1"となっているかをチェックしています。
　・③-1で、ルールに適合しなかった場合に出力するメッセージ内容を記述しています。
　・③-2で、適用するルールを記述しています。(spec.storageclass名が"ontap-nas-app1"であることをチェック) 

</div></details>
  

## 3. Policyでの制御ができていることを確認（PVCを作成してみる)
PVCを作成コマンドを実行して、ポリシーで制御できていることを確認してみます。

#### 3-1. 事前準備 
最初にテスト用のNamespace作成と、PVC作成用YAMLファイルの準備をします。

```shell:
$ kubectl create ns nstest01
$ mkdir work
$ cd work
$ cat << EOF >> pvc11.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-ontapnas11
  namespace: nstest01
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ontap-nas-app1
  resources:
    requests:
        storage: 1Gi
EOF
$ cat << EOF >> pvc12.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-ontapnas12
  namespace: nstest01
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ontap-nas-app2
  resources:
    requests:
        storage: 1Gi
EOF
$ 
```
２つのPVCのYAMLファイル(pvc11.yaml,pvc12.yaml)を準備しています。
それぞれ、Storage Classについて、 ontap-nas-app1 、 ontap-nas-app2を利用して Namespace =nstest01にPVCを作成するYAMLファイルとなります。

事前にStorage Classがあることを確認しておきます。

```shell:
$ kubectl get storageclass
NAME                  PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
ontap-nas (default)   csi.trident.netapp.io   Delete          Immediate           false                  111d
ontap-nas-app1        csi.trident.netapp.io   Delete          Immediate           false                  28d
ontap-nas-app2        csi.trident.netapp.io   Delete          Immediate           false                  28d
$ 
```

#### 3-2. PVCを作成して ポリシーが適用されることを確認
準備ができたので、PVC作成コマンドを実行して ポリシーが適用されることを確認してみます。

```shell:
$ kubectl apply -f pvc11.yaml
persistentvolumeclaim/pvc-ontapnas11 created
$ kubectl apply -f pvc12.yaml
Error from server: error when creating "pvc12.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:

resource PersistentVolumeClaim/nstest01/pvc-ontapnas12 was blocked due to the following policies

cpol11:
  cpolicy01: 'validation error: Hello!!! This is kyverno feature test!!! Thanks!.
    Rule cpolicy01 failed at path /spec/storageClassName/'
$ kubectl get pvc -n nstest01
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS     AGE
pvc-ontapnas11   Bound    pvc-49dedc52-3c53-4478-b68b-fab61e1ab487   1Gi        RWO            ontap-nas-app1   8s
$
```

Storage Class が ルールで記述されている "ontap-nas-app1"を指定した場合は、PVC作成に成功、
Storage Class が ルールに記述されていない"ontap-nas-app2"を指定した場合、PVC作成に失敗 していることが確認できます。

また、失敗時は 適用されたルール名と validateで指定したmessageの内容が出力されているのが確認できます。


### 参考： Preconditionsで変数を利用した制御
Preconditios を利用すると 変数を利用した よりカスタマイズしたルールの記述ができます。（長いので折りたたみ）
<details><summary>ポリシーの記述内容の説明、実行例</summary><div>

Preconditions を利用して、特定のNamespaceで 指定したStorage Classの利用を 拒否 するポリシーを記述。

#### ポリシーの記述例　2

```plaintext:
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: cpol12
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: cpolicy02
    match:　　　　　　　　　　　　　　　　　　　
      any:        
      - resources:    
          kinds:
          - PersistentVolumeClaim
          namespaces:
          - "nstest01"
          - "nstest11"
    preconditions:　　　　　　　　　　　　　　　　　　　　　　　　　　　・・・ ①
      any:
      - key: "{{request.object.spec.storageClassName}}"　　　　　　　　　　　　　　　　　　　　　　　・・・ ①-1
        operator: NotEquals　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　・・・ ①-2
        value: "ontap-nas-app2"
    validate:　　　　　　　　　　　　　　　　　　　　　　　　　　　・・・ ②
      message: "Hello!!! This is kyverno feature test!!! Thanks!"　　　
      deny: {}                 ・・・ ②-1
```
上記の記述内容は説明は以下となります。
##### ① プレコンディション
 ・変数を利用したルール実行制御が可能。matchルールよりもよりカスタマイズしたフィルターを定義可能。
 ・"PVC作成時に指定されたStorage Class名が"ontap-nas-app2"と同一ではない(NotEquals)" という チェックをしている
 ・①-1: kyvernoの記述で利用できる[変数](!https://kyverno.io/docs/writing-policies/variables/)。ここでは 作成リソースのリクエスト内で指定されているStorage Class名が自動設定。(PVC作成のコマンドを実行した場合に YAMLファイル内などで指定したStorage Class名)
 ・①-2: "kye" の値と "value"の値を 評価する演算子。 ここでは "同一ではない" という評価を実施。[その他演算子](!https://kyverno.io/docs/writing-policies/preconditions/#operators)も利用可能

##### ② 検証内容
・match、preconditionにより特的された適用対象リソースへのリクエストがきたら拒否する動作となるように記述している


上記ポリシーを利用すると以下のような実行結果になります。
（事前にcpol01,pvc-ontapnas11は削除しておく必要があります）
```shell:
$ kubectl apply -f pvc11.yaml
Error from server: error when creating "pvc11.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:

resource PersistentVolumeClaim/nstest01/pvc-ontapnas11 was blocked due to the following policies

cpol12:
  cpolicy02: Hello!!! This is kyverno feature test!!! Thanks!
$ kubectl apply -f pvc12.yaml
persistentvolumeclaim/pvc-ontapnas12 created
$ 
```

</div></details>

## まとめ
Kyvernoを利用してStorae Classの利用を制限する機能を試してみました。
YAMLファイルにルールを記述でき、また、変数を利用してより柔軟なルール記述をすることも可能です。専用の言語を必要としないので利用開始のハードルもそこまで高くないのではないかと思います。

kubernetesクラスターをマルチテナント環境で利用する場合に、クラスタースコープのリソースを認可機能だけで制御するのに不足が生じた場合等にも有用な機能かと思います。
Kyvernoには Validate以外にも色々機能が提供されているので、そういった機能を試してみるのも面白そうです。