OPA GatekeeperのGatorを使ってManifestにPolicy違反がないか検証する


ここでは[Gator](https://github.com/open-policy-agent/gatekeeper/blob/master/website/docs/gator.md)というOPAのPolicyチェックツールについて簡単に紹介する。なお、触り程度の解説なので、verify機能や[`kind: Suite`](https://github.com/open-policy-agent/gatekeeper/blob/master/website/docs/gator.md#suites)には触れていない。

## OPA Gatekeeper利用時の課題
OPA Gatekeeperを使っていると、Manifest作成時にそのManifestがPolicy違反になっていないかを確認したくなることがある。
特にGitOpsでManifestをGitに登録する場合、Policy違反が判明するのはArgoCD等デプロイツールが反応したタイミングであり、CI/CDのスピード感を殺すものとなっている。\
[conftest](https://www.conftest.dev/)で事前に刈り取ることも出来るが、conftestで書くRegoとGatekeeperのManifestは一元管理することが出来ず、Gatekeeper側のPolicy変更が入った場合、conftest側のRegoのメンテが漏れてCI/CDが止まってしまうことも考えられる。

この問題を解決するアプローチとして、以下が考えられる。
- RegoからGatekeeperのManifestを生成する
- GatekeeperのManifestをPolicyチェックに使う

前者については[Konstraint](https://github.com/plexsystems/konstraint)というOSSを用いて実現することが出来る。\
後者については本日紹介する[Gator](https://github.com/open-policy-agent/gatekeeper/blob/master/website/docs/gator.md)にて解決することが出来る。

## Gatorとは
[Gator](https://github.com/open-policy-agent/gatekeeper/blob/master/website/docs/gator.md)は[OPA Gatekeeper](https://github.com/open-policy-agent/gatekeeper)で提供されているツールで、Gatekeeper用Manifest（ConstraintTemplateとConstraint）を元に、ManifestがPolicy違反を起こしていないかをチェックしてくれるツールである。

## Gatorで利用するGatekeeper用Manifestを生成する
ここは[Tanzu Mission Control](https://tanzu.vmware.com/jp/mission-control)のPolicy作成機能を利用してGatekeeper用ManifestであるConstraintTemplateリソースとConstraintリソースを作成する。\
Tanzu Mission Controlが利用できない人は、Gatekeeperの[example](https://github.com/open-policy-agent/gatekeeper/tree/master/example)や[demo](https://github.com/open-policy-agent/gatekeeper/tree/master/demo)、konstraintの[examples](https://github.com/plexsystems/konstraint/tree/main/examples)あたりを参考に作成するとよい。特にkonstraintのexamplesはかなりサンプルが豊富なのでオススメ。

また、末尾の付録に今回作ったManifestを掲載しているので、それを活用してもらってもよい。

以下、Tanzu Mission Controlを使ってGatekeeper用Manifestを作成していく。
Tanzu Mission Controlにログインし、左サイドバーの`Policies`->`Assignments`から自身のクラスタを選択し、`CREATE SECURITY POLICY`をクリックする。
![1666153785401.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9cccaf44-20cb-d8ac-831a-37f6c50d7c4e.png)

ここではBaselineを選択し、Policy nameに適当な名前を与えて`CREATE POLICY`をクリックしてPolicyを作成する。
![1666153875510.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/a0d47400-cea0-faaf-1502-a12d6383afe7.png)

上記の画面を見てもらうと分かるが、Privilegedなコンテナ作成やhostNetworkの利用などがこれによりNGとなる。
実行後、クラスタでConstraintTemplateリソースが生成されていることが分かる（Gatekeeperもこの時にインストールされる）。
```
$ kubectl get constrainttemplate
NAME                                              AGE
vmware-system-tmc-allowed-host-paths-v1           48s
vmware-system-tmc-allowed-procmount-types-v1      53s
vmware-system-tmc-allowed-users-v1                56s
vmware-system-tmc-allowed-volumes-v1              50s
vmware-system-tmc-block-host-namespace-v1         47s
vmware-system-tmc-block-privilege-escalation-v1   52s
vmware-system-tmc-block-privileged-container-v1   49s
vmware-system-tmc-enforce-host-networking-v1      46s
vmware-system-tmc-forbidden-sysctls-v1            54s
vmware-system-tmc-linux-capabilities-v1           55s
```
Privilegedに関するPolicyを確認する。
```yaml
$ kubectl get constrainttemplate vmware-system-tmc-block-privileged-container-v1 -o yaml
：（省略）
  crd:
    spec:
      names:
        kind: vmware-system-tmc-block-privileged-container-v1
      validation:
        legacySchema: true
  targets:
  - rego: |-
      package k8spspprivileged
      violation[{"msg": msg, "details": {}}] {
          c := input_containers[_]
          c.securityContext.privileged
          msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
      }
      input_containers[c] {
          c := input.review.object.spec.containers[_]
      }
      input_containers[c] {
          c := input.review.object.spec.initContainers[_]
      }
    target: admission.k8s.gatekeeper.sh
```
これより、以下が分かる。
- ConstaintのCRD名は`vmware-system-tmc-block-privileged-container-v1`(ConstraintTemplateと同名)
- Policy違反時は`Privileged container is not allowed: %v, securityContext: %v`でコンテナ名とセキュリティコンテキストが表示される。

続いて、Constraintの方を確認する。
```
$ kubectl get vmware-system-tmc-block-privileged-container-v1
NAME                       AGE
tmc.cp.opa-baseline-test   5m28s
```
```yaml
$ kubectl get vmware-system-tmc-block-privileged-container-v1 -o yaml tmc.cp.opa-baseline-test
：（省略）
  match:
    excludedNamespaces:
    - kube-*
    - vmware-*
    - tanzu-*
    - gatekeeper-system
    - tkg-system
    - istio-system
    - velero
    - wavefront
    - kube-dns
    - kube-node-lease
    - kube-proxy
    - kube-public
    - kube-system
    - tanzu-observability-saas
    - tanzu-package-repo-global
    - tanzu-system
    - tanzu-system-registry
    - tanzu-system-auth
    - tanzu-system-ingress
    - tanzu-system-logging
    - vmware-system-auth
    - vmware-system-cloud-provider
    - vmware-system-csi
    - vmware-system-tmc
    - vmware-system-tsm
    kinds:
    - apiGroups:
      - ""
      kinds:
      - Pod
    namespaceSelector:
      matchExpressions:
      - key: e2e-run
        operator: DoesNotExist
```
特定のNamespaceは例外とし、Podを対象としていることが分かる。
以下のサンプルを試しにapplyすると、期待していたエラーが表示された。
```yaml
cat <<EOF > ./testpod-privileged.yaml
kind: Pod
apiVersion: v1
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true
```
```
$ kubectl apply -f testpod-privileged.yaml -n default
Error from server ([tmc.cp.opa-baseline-test] Privileged container is not allowed: nginx, securityContext: {"privileged": true}): error when creating "testpod-privileged.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [tmc.cp.opa-baseline-test] Privileged container is not allowed: nginx, securityContext: {"privileged": true}
```

これで適切なConstraint,ConstraintTemplateがk8sリソースとして存在しているのが確認できたので、これをファイルとして取り出す。
```
mkdir privileged-policy/
kubectl get vmware-system-tmc-block-privileged-container-v1 -o yaml tmc.cp.opa-baseline-test > privileged-policy/constraint.yaml
kubectl get constrainttemplate -o yaml vmware-system-tmc-block-privileged-container-v1 > privileged-policy/constrainttemplate.yaml 
```

なお、`status`フィールドを残して出力しているが、特に残っていても問題ないようであり、動作確認時も`status`のフィールドを残して確認した。
ただ、実利用時は保守面等から[kubectl neat](https://github.com/itaysk/kubectl-neat)などでキレイにしたものを使った方がよいと思われる。

### Manifestの検証
作成したManifestを利用してさっそく検証する。
今回、検証として先程作った以下のManifestを利用する。
- 検証対象のManifest：testpod-privileged.yaml
- Policy定義：constraint.yaml、constrainttemplate.yaml

Gatorをインストールしていない人は、CLIをインストールする。goによるインストールとbrewによるインストールが提供されており、今回はbrewでインストールした。
```
brew install gator
```

インストールが終われば、早速動作確認する。
```
$ cat testpod-privileged.yaml | gator test -f privileged-policy/
Message: "Privileged container is not allowed: nginx, securityContext: {\"privileged\": true}"
```
適切にPolicy違反が検出できた。なお、戻り値は以下のようになっており、パイプラインに組み込んで処理を止めることも容易となる。
```
$ echo $?
1
```

## 付録：利用したManifest
ディレクトリ構造は以下となる。
```
.
├── privileged-policy
│   ├── constraint.yaml
│   └── constrainttemplate.yaml
└── testpod-privileged.yaml
```
それぞれのファイルの中身は以下となる。([kubectl neat](https://github.com/itaysk/kubectl-neat)で整形済み)
```yaml:constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: vmware-system-tmc-block-privileged-container-v1
metadata:
  labels:
    tmc.cloud.vmware.com/crd-type: tmc-managed
    tmc.cloud.vmware.com/managed: "true"
  name: tmc.cp.opa-baseline-test
spec:
  match:
    excludedNamespaces:
    - kube-*
    - vmware-*
    - tanzu-*
    - gatekeeper-system
    - tkg-system
    - istio-system
    - velero
    - wavefront
    - kube-dns
    - kube-node-lease
    - kube-proxy
    - kube-public
    - kube-system
    - tanzu-observability-saas
    - tanzu-package-repo-global
    - tanzu-system
    - tanzu-system-registry
    - tanzu-system-auth
    - tanzu-system-ingress
    - tanzu-system-logging
    - vmware-system-auth
    - vmware-system-cloud-provider
    - vmware-system-csi
    - vmware-system-tmc
    - vmware-system-tsm
    kinds:
    - apiGroups:
      - ""
      kinds:
      - Pod
    namespaceSelector:
      matchExpressions:
      - key: e2e-run
        operator: DoesNotExist
```
```yaml:constrainttemplate.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  labels:
    tmc.cloud.vmware.com/crd-type: tmc-managed
    tmc.cloud.vmware.com/managed: "true"
  name: vmware-system-tmc-block-privileged-container-v1
spec:
  crd:
    spec:
      names:
        kind: vmware-system-tmc-block-privileged-container-v1
      validation:
        legacySchema: true
  targets:
  - rego: |-
      package k8spspprivileged
      violation[{"msg": msg, "details": {}}] {
          c := input_containers[_]
          c.securityContext.privileged
          msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
      }
      input_containers[c] {
          c := input.review.object.spec.containers[_]
      }
      input_containers[c] {
          c := input.review.object.spec.initContainers[_]
      }
    target: admission.k8s.gatekeeper.sh
```
```yaml:testpod-privileged.yaml
kind: Pod
apiVersion: v1
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true
```