Gatekeeperを使ってみた

# Gatekeeperとは

- [Gatekeeper](https://github.com/open-policy-agent/gatekeeper)とは[Open Policy Agent](https://www.openpolicyagent.org/)のポリシーエンジンの一種であり、Kubernetesクラスタ内でのリソース作成時にポリシーを課すことができ、ポリシーを満たさないリソースの作成を防止してくれるツールです。例えば、「Deploymentにはappタグが必要である」というポリシーを課すことで、そのポリシーを満たさないDeploymentがクラスタに作成されることを防いでくれます。

- これはAdmission WebhooksのValidationを行う際にポリシーを満たしているかのチェックが行われることで実現されています。そのため、`kubectl`や`helm install`、CRD(Custom Resource Definition)からの作成など、あらゆるデプロイ方法に適用されます。

- 他の代表的なポリシーエンジンとして、[Conftest](https://github.com/open-policy-agent/conftest)が知られています。こちらはコマンドラインからマニフェストファイルがポリシーを満たしているかをチェックするツールであり、主にCIで使用されています。ただし、ConftestだけだとCRDがクラスタ内部で作成するリソースなど、明示的にマニフェストに明記されていないリソースまでチェックすることができません。そのため、ConftestとGatekeeperを両方とも使用してリソースにポリシーを課す運用がベターだと思います。なお、どちらのツールでも[Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)という言語を使用してポリシーを記述します。

# インストール

- [Helm](https://helm.sh/)でインストールする方法が簡単です。

```
$ helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
$ helm install gatekeeper/gatekeeper --generate-name
```

- これにより必要なリソースと、いくつかのCRDが定義されます。

# ポリシーの作成
- 実際にポリシーを作ってみます。ここでは、Nodeが行う排出処理が成功するよう、pdb(PodDisruptionBudget)のmaxUnavailableに0より大きい値が設定されていることを課すポリシーを作成してみます。主に必要なリソースは以下の2つです。
- ConstraintTemplate
    - constraintの雛形であり、Regoで記載したポリシーを併せて定義します。これを作成することで、`spec.crd.spec.names.kind`で定義したCRDが作成されます。
    - targets.regoにポリシーが記載されています。詳細は省略しますが、ポイントは以下の通りです。
        - ルールの名前は`violation`である必要があります。
        - `input.review`に適用されるマニフェストの内容が設定されています。ここの値はポリシーを課すリソースによって変更する必要があります。設定の内容については後述。
        - `:=`は代入を意味し、ここでは`maxUnavailable`という変数に`maxUnavailable`の値を代入しています。
        - `maxUnavailable`の数値を評価して結果を`ok`という変数に代入し、それを配列`satisfied`の要素としています。
        - `not all`が評価を行っている箇所になります。`all`は全ての要素がtrueだとtrueを返す関数であり、そこにnotが付与されているため、条件を満たさない場合にtrueを返すことになります。
        - `msg`はtrueのとき(必要なポリシーを満たしていないとき)に出力するメッセージであり、必須のパラメータです。

```constraintTemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: pdbmaxunavailable
spec:
  crd:
    spec:
      names:
        kind: PdbMaxUnavailable
      validation:
        openAPIV3Schema:
          properties:
            maxUnavailable:
              type: integer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package pdbmaxunavailable
       
        violation[{"msg": msg, "details": {}}] {
          maxUnavailable := input.review.object.spec.maxUnavailable
          satisfied := [ok | ok := maxUnavailable > 0]
          not all(satisfied)
          msg := "maxUnavailable must be bigger than 0."
        }  
```

- PdbMaxUnavailable
    - 上記のConstraintTemplateで定義したConstraintです。
    - `spec.match.kinds.kinds`で対象のリソースを、`spec.match.kinds.apiGroups`でリソースのAPI Groupを指定します。

```pdbMaxUnavailable.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: PdbMaxUnavailable
metadata:
  name: maxunavailable-must-be-bigger-than-zero
spec:
  match:
    kinds:
      - apiGroups: ["policy"]
        kinds: ["PodDisruptionBudget"]
```

# 稼働確認
- 上記の`constraintTemplate.yaml`と`pdbMaxUnavailable.yaml`をapplyし、pdbを作成します。
- その後、以下のようにポリシーを満たさないpdbの作成を試みます。

```yaml:fail/pdb.yaml
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: sample-app
spec:
  maxUnavailable: 0
  selector:
    matchLabels:
      app: sample-app
```

- applyするとConstraintTemplateで定義したメッセージが出力され、リソースの作成が失敗します。

```
Error from server ([denied by maxunavailable-must-be-bigger-than-zero] maxUnavailable must be bigger than 0.): error when creating "fail/pdb.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by maxunavailable-must-be-larger-than-zero] maxUnavailable must be bigger than 0.
```

# その他
## デバッグ
- 上述の通り、`ConstraintTemplate`を作成する際に`input.review`内の要素を指定する必要がありますが、リソース毎に構成が異なるため、設定前に確認する必要があります。これは`msg`の出力に`input.review`を指定したConstraintTemplateを作成することで確認できます。特に条件を指定していないため、全てのpdbの作成時にメッセージが出力されます。

```yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sdenyall
spec:
  crd:
    spec:
      names:
        kind: K8sDenyAll
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenyall

        violation[{"msg": msg}] {
          msg := sprintf("REVIEW OBJECT: %v", [input.review])
        }
```

```yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyAll
metadata:
  name: deny-all-pdb
spec:
  match:
    kinds:
      - apiGroups: ["policy"]
        kinds: ["PodDisruptionBudget"]
```

- 適当なpdbを作成しようとすると、以下のように`input.review`以下の要素が出力され、この内容を基に評価対象のオブジェクトをRegoで記載しましょう。やや面倒な手順ですが、READMEにも記載されている方法ですので、現状はこの方法しかないようです。

```json:inupt.review(抜粋)
  "object": {
    "apiVersion": "policy/v1beta1",
    "kind": "PodDisruptionBudget",
    "metadata": {
      "name": "myapp",
      "namespace": "default",
      "uid": "da6b703c-83d6-4488-910c-c7492343eddc",
      "generation": 1,
    "spec": {
      "selector": {
        "matchLabels": {
          "run": "myapp"
        }
      },
      "maxUnavailable": 0
    },
  }
```

## リソース作成が失敗した際のログ
- デフォルトの設定だと、Gatekeeperによってポリシーを満たさないリソース作成が阻止されてもログが出力されません。出力するためには`gatekeeper-controller-manager`というDeploymentで、コンテナのargsに`--log-denies`を指定する必要があります。
- リソースの作成が阻止されると以下のようなログが確認できます。

```
{"level":"info","ts":1611475124.4214969,"logger":"webhook","msg":"denied admission","process":"admission","event_type":"violation","constraint_name":"maxunavailable-must-be-bigger-than-zero","constraint_group":"constraints.gatekeeper.sh","constraint_api_version":"v1beta1","constraint_kind":"PdbMaxUnavailable","constraint_action":"deny","resource_group":"policy","resource_api_version":"v1beta1","resource_kind":"PodDisruptionBudget","resource_namespace":"default","resource_name":"sample-app","request_username":xxxxx"}
```
# まとめ
- Kubernetesクラスタ内でリソースにポリシーを課し、それを満たさない場合にはリソースの作成を阻止するGatekeeperについてpdbを例として記載しました。
- ConstraintTemplateにてRegoでポリシーを記載し、Constraintで対象のリソースを指定することで、クラスタ内のリソース作成に関するポリシーを適用することができます。


# 参考
- [open-policy-agent/gatekeeper](https://github.com/open-policy-agent/gatekeeper)
- [open-policy-agent/frameworks](https://github.com/open-policy-agent/frameworks/tree/master/constraint)
- [Open Policy Agent | Policy Language](Policy Language)