OPA Gatekeeperを使って特定のIAMユーザだけ特定のNamespaceでのリソース作成を禁止する

1つのEKSクラスタを複数のIAMユーザで使いまわしている時に、Admin的なユーザ以外にはkube-systemでリソースを作成して欲しくないケースがある。（レアケースだとは思うが。。。）
RBACで出来なくもなさそうだが、特定のNamespaceだけ除外する、というのがあまりキレイに出来ないので、OPA Gatekeeperを使って実装した時のメモ。

今回は以下の条件で作成した。
- kube-systemでのPodの作成を抑止する
- IAMユーザのARNをConstraint側で渡せるようにする
- NamespaceもConstraint側で記載するようにし、Template側で縛らないようにする

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

### ConstraintTemplateの作成
OPA Gatekeeperで与える制約の元となるテンプレートを作成する。
作成したテンプレートは以下となる。
```yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdenyarn
spec:
  crd:
    spec:
      names:
        kind: K8sDenyArn
      validation:
        openAPIV3Schema:
          type: object
          properties:
            denyArns:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenyarn

        violation[{"msg": msg}] {
          denyArns := input.parameters.denyArns[_]
          namespace := input.review.namespace
          operation := input.review.operation
          userInfo := input.review.userInfo

          userInfo.extra.arn[_] == denyArns
          operation == "CREATE"
          msg := sprintf("denied namespace: %v operation: %v arn: %v", [namespace, operation, userInfo.extra.arn])
        }
```
以下解説する。
```yaml
      names:
        kind: K8sDenyArn
      validation:
        openAPIV3Schema:
          type: object
          properties:
            denyArns:
              type: array
              items:
                type: string
```
上記の箇所ではカスタムリソースを生成するとともに、パラメータとして`denyArns`というものを用意した。これにより、ARNを可変値として渡せるようにし、`K8sDenyArn`リソース作成時に決まるものとした。
```go
          denyArns := input.parameters.denyArns[_]
```
上記箇所で`K8sDenyArn`リソース作成時に設定したARNを取得している。
```go
          operation := input.review.operation
```
上記箇所でPodに対するオペレーション(CREATE, UPDATE等)を取得している。なお、`input.review`がどのような値を持っているかは[こちら](https://open-policy-agent.github.io/gatekeeper/website/docs/howto/#input-review)の公式の説明を確認するとよい。
```go
          userInfo := input.review.userInfo
```
上記箇所でユーザ情報を取得している。
この中身はEKSで実行した場合は以下となっていた。
```json
{
  "extra": {
    "accessKeyId": [
      "xxxxxxx"
    ],
    "arn": [
      "arn:aws:iam::xxxx:user/username"
    ],
    "canonicalArn": [
      "arn:aws:iam::xxxx:user/username"
    ],
    "sessionName": [
      ""
    ]
  },
  "groups": [
    "system:masters",
    "system:authenticated"
  ],
  "uid": "aws-iam-authenticator:xxxx:xxxx",
  "username": "kubernetes-admin"
}
```
見て分かるようにextraのところにAWSに関連する情報が含まれているため、これを利用してリソース作成を抑制する。
```go
          userInfo.extra.arn[_] == denyArns
          operation == "CREATE"
```
上記箇所はエラーとする条件を記載している。violation内の評価式は全てがTrueだとリソース操作を抑止するため、ここでは
- 指定したARNがリソース作成時のパラメータに含まれているか
- リソース操作がCREATEになっているか

をチェックして、条件が一致する場合は抑止するようにしている。

### Constraintの作成
次にContraintとなる、先程定義した`kind: K8sDenyArn`を作成する。
```yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyArn
metadata:
  name: deny-arn
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "kube-system"
  parameters:
    denyArns:
    - "arn:aws:iam::xxxxx:user/username"
```
namespaceはparametersで渡すことも出来るのだが、matchで絞った方が早いのでmatch部分に書くようにした。

### 動作確認
それぞれのリソースを適用した状態で、実際にPodを作成してみる。
最初に、kube-systemとdefaultにそれぞれ作成してみる。
```sh
$ kubectl run nginx --image nginx -n kube-system
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [deny-arn] denied namespace: kube-system operation: CREATE arn: ["arn:aws:iam::xxxx:user/username"]
$ kubectl run nginx --image nginx -n default
pod/nginx created
```
kube-systemのみ失敗した。
次に、K8sDenyArnのARNを変更して実行する。
```sh
$ kubectl run nginx --image nginx -n kube-system
pod/nginx created
$ kubectl run nginx --image nginx -n default
pod/nginx created
```
どちらも成功した。ARNによる制御が上手く行っていることが確認できた。