EKS上でOpenPolicyAgentのワークショップ資料を使ってOPA Gatekeeperの振る舞いを確認する

[EKSのOPAのワークショップ](https://www.eksworkshop.com/intermediate/310_opa_gatekeeper/)の内容を確認した時のメモ。\
ワークショップの内容だけだと理解が難しいため、実施している内容にコメントをつけて補足した。\
言葉の定義などの理解が怪しいので、ご指摘あればコメント欄にて是非。


### インストール
[こちら](https://github.com/open-policy-agent/gatekeeper)より最新版のOPA Gatekeeperのバージョンを確認し、インストールする。

```
export OPA_GK_VERSION=3.9
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-${OPA_GK_VERSION}/deploy/gatekeeper.yaml 
```
Podがあがっていることを確認する。
```
$ kubectl get pod -n gatekeeper-system
NAME                                             READY   STATUS    RESTARTS   AGE
gatekeeper-audit-779ffb6c54-hshsd                1/1     Running   0          21s
gatekeeper-controller-manager-7b4c8d7b97-57d77   1/1     Running   0          21s
gatekeeper-controller-manager-7b4c8d7b97-pmqmv   1/1     Running   0          21s
gatekeeper-controller-manager-7b4c8d7b97-zkqn9   1/1     Running   0          20s
```

### 制約の実施
`kind: ConstraintTemplate`というカスタムリソースを利用し、Kubernetesリソースに対して制約を強制するためのテンプレートを作成することができる。

以下の例はprivileged=trueが含まれるとリクエストを拒否するテンプレートとなる。

```yaml
cat > /tmp/constrainttemplate.yaml <<EOF
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8spspprivilegedcontainer
spec:
  crd:
    spec:
      names:
        kind: K8sPSPPrivilegedContainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      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[_]
        }
EOF
```
Manifestを1つ1つ読み解いていく。

以下の部分はCRDを新しく作っている。
```
  crd:
    spec:
      names:
        kind: K8sPSPPrivilegedContainer
```
K8sPSPPrivilegedContainerというカスタムリソースを作成する時に上記のテンプレートが適用されることになる。制約発動の条件をCRDで定義したと思うと良さそう。

以下はRegoによって制約を定義している箇所になる。
```
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
```
targets.targetは説明が見当たらなかったため、一旦おまじないとして考えておく。

以下からRego本体になる。Regoを読み解くのには[OPA/Rego入門](https://zenn.dev/mizutani/books/d2f1440cfbba94)と[公式ドキュメント](https://www.openpolicyagent.org/docs/latest/)を参考にした。
```
        package k8spspprivileged
```
`package k8spspprivileged`はこのregoの名前のようなもので、必ず定義する必要がある。

```
        violation[{"msg": msg, "details": {}}] {
            c := input_containers[_]
            c.securityContext.privileged
            msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
        }
```
`violation`ではルール違反を定義する。`[]`内はルール違反時に出力する文字列を設定している。\
後述のinput_containersを呼び出し、k8sリソース内のspecの`securityContext.privileged`が`true`かどうかを確認している。\
msg変数にはメッセージを設定する。\
カッコ内の各行はそれぞれが評価式となり、AND条件で評価されるため、全てがTrueであればルール違反として判定される動きとなる。

以下はinput_containersの定義となる。
```
        input_containers[c] {
            c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
            c := input.review.object.spec.initContainers[_]
        }
```
それぞれ、入力元(`input.review.object`)のspec.containersおよびinitContainersを取得している。複数定義した場合、上書きするのではなく、それぞれで値を引っ張ってくる模様。なので、ここではinput_containersはk8sリソースのspec.containersとspec.initContainersを取得することになる。\
なお、`[_]`で複数取得(リストとかシーケンスの取得)ができるようになっている。

次に上記のテンプレートを利用して制約を適用する。制約を適用するには、先程作成したCRDを使う。
```yaml
cat > /tmp/constraint.yaml <<EOF
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
EOF
```
ここの記載方法の詳細は[こちらの公式ドキュメント](https://open-policy-agent.github.io/gatekeeper/website/docs/howto)が参考になる。

なお、作成したリソースは自分で設定したkindもしくは`kind: Constraint`を参照することで確認できる。

```
$ kubectl get K8sPSPPrivilegedContainer
NAME                       ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
psp-privileged-container                        46
```
```
$ kubectl get constraint
NAME                       ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
psp-privileged-container                        46
```
describeで見ると、statusのところでVIOLATIONSの詳細として、どのPodがviolationとなったか、またRegoで定義したメッセージが確認できる。
```
$ kubectl describe constraint psp-privileged-container 
：（省略）
  Total Violations:  46
  Violations:
    Enforcement Action:  deny
    Group:
    Kind:                Pod
    Message:             Privileged container is not allowed: calico-node, securityContext: {"privileged": true}
    Name:                calico-node-2l6cl
    Namespace:           calico-system
    Version:             v1
    Enforcement Action:  deny
    Group:
    Kind:                Pod
    Message:             Privileged container is not allowed: flexvol-driver, securityContext: {"privileged": true}
    Name:                calico-node-2l6cl
    Namespace:           calico-system
    Version:             v1
：（省略）
```

この状態で`privileged: true`を含むPodを作成する。
```yaml
cat > /tmp/example.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: bad-nginx
  labels:
    app: bad-nginx
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true
EOF
kubectl create -f /tmp/example.yaml
```
実行結果として、以下のエラーが出力され、Podが作成できなかったことが分かる。
```
Error from server (Forbidden): error when creating "/tmp/example.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [psp-privileged-container] Privileged container is not allowed: nginx, securityContext: {"privileged": true}
```

チュートリアルにはなかったが、せっかくなのでInit Containerに対する制約も試してみる。
```yaml
cat > /tmp/example.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: bad-nginx
  labels:
    app: bad-nginx
spec:
  initContainers:
  - name: nginx-init
    image: nginx
    securityContext:
      privileged: true
  containers:
  - name: nginx
    image: nginx
EOF
kubectl create -f /tmp/example.yaml
```
結果は先ほどと同じくPodの作成に失敗した。
```
Error from server (Forbidden): error when creating "/tmp/example.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [psp-privileged-container] Privileged container is not allowed: nginx-init, securityContext: {"privileged": true}
```
エラーメッセージより、Init Containerのコンテナであるnginx-initで失敗したことが分かる。