EKSにOPA GatekeeperをHelmでインストールしてポリシー強制をしてみる

OPA Gatekeeperとは

この記事に辿ってこれた人ならあえて説明は不要だと思いますので省略で。Kubernetesにポリシーを強制できます。

認識齟齬無いように、リンクはこれです。 github.com

作業環境

  • EKS v18
  • kubectl v1.19.2
  • Helm v3.4.0
  • gatekeeper-3.2.1

インストール

Helmを使えば簡単にインストール可能です。

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

インストール時のオプションはこの辺りが参考になります。

gatekeeper/README.md at master · open-policy-agent/gatekeeper · GitHub

gatekeeper-system という名前空間が作成されています。

$ kubectl get ns
NAME                STATUS   AGE
default             Active   45m
gatekeeper-system   Active   10s
kube-node-lease     Active   45m
kube-public         Active   45m
kube-system         Active   45m

Podが動き出しているか確認してみます。

$ kubectl get all -n gatekeeper-system 
NAME                                                READY   STATUS    RESTARTS   AGE
pod/gatekeeper-audit-6b6986d74-dxsft                1/1     Running   0          28s
pod/gatekeeper-controller-manager-bfbbfdf6f-2tcb5   1/1     Running   0          28s
pod/gatekeeper-controller-manager-bfbbfdf6f-5p8rt   1/1     Running   0          28s
pod/gatekeeper-controller-manager-bfbbfdf6f-8rl5c   1/1     Running   0          28s

NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/gatekeeper-webhook-service   ClusterIP   172.20.74.16   <none>        443/TCP   28s

NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/gatekeeper-audit                1/1     1            1           28s
deployment.apps/gatekeeper-controller-manager   3/3     3            3           28s

NAME                                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/gatekeeper-audit-6b6986d74                1         1         1       28s
replicaset.apps/gatekeeper-controller-manager-bfbbfdf6f   3         3         3       28s

上記コマンドで取得できないものも作成されています。例えば以下のリソースなど。

$ kubectl get validatingwebhookconfigurations/gatekeeper-validating-webhook-configuration -oyaml
$ kubectl api-resources  | grep  .gatekeeper.sh
configs                           config       config.gatekeeper.sh           true         Config
k8srequiredlabels                              constraints.gatekeeper.sh      false        K8sRequiredLabels
constraintpodstatuses                          status.gatekeeper.sh           true         ConstraintPodStatus
constrainttemplatepodstatuses                  status.gatekeeper.sh           true         ConstraintTemplatePodStatus
constrainttemplates                            templates.gatekeeper.sh        false        ConstraintTemplate

必須Labelを強制する

早速使ってみます。

ポリシーテンプレートの作成(ラベル強制)

まずはシンプルにLabelを強制するポリシーを作ってみます。

metadata.name は以下の制約がありますので、Kindを小文字にしたものにします。

DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.'

cat <<EOF > mylabelconstraints_template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: mylabelconstraints
spec:
  crd:
    spec:
      names:
        kind: MyLabelConstraints
      validation:
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package mylabelconstraints
        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("you must provide labels: %v", [missing])
        }
EOF

では、適用します。

$ kubectl apply -f mylabelconstraints_template.yaml
constrainttemplate.templates.gatekeeper.sh/mylabelconstraints created

制約の作成・適用(ラベル強制)

次に先程作成したテンプレートを使って名前空間の作成にラベルを強制します。

cat <<EOF > mylabelconstraints_ns_musthave_mylabel.yml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: MyLabelConstraints
metadata:
  name: ns-must-have-mylabels
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["mylabel","mysecond_label"]
EOF

以下のように、テンプレート側でパラメータ化したものに、

required := {label | label := input.parameters.labels[_]}

制約側でパラメータ渡します。ここが便利ポイントですね。

  parameters:
    labels: ["mylabel","mysecond_label"]

では制約も適用します。

$ kubectl apply -f mylabelconstraints_ns_musthave_mylabel.yml
mylabelconstraints.constraints.gatekeeper.sh/ns-must-have-mylabels created

ポリシー違反のテスト

Label強制の設定ができたので、Label無しで名前空間を作成してみるとエラーが出ました。制約の強制ができてそうです。

$ kubectl create ns mytest-ns
Error from server ([denied by ns-must-have-mylabels] you must provide labels: {"mylabel", "mysecond_label"}): admission webhook "validation.gatekeeper.sh" denied the request: [denied by ns-must-have-mylabels] you must provide labels: {"mylabel", "mysecond_label"}

ポリシー違反のテスト(正常系)

次は強制したLabel付きで名前空間作ってみます。今度はエラー無く作成可能です。

$ kubectl apply -f - <<EOF
kind: Namespace
apiVersion: v1
metadata:
  name: mytest-ns
  labels:
    mylabel: aaa
    mysecond_label: bbb
EOF
namespace/mytest-ns created

掃除

掃除しておきます。

kubectl delete ns mytest-ns
kubectl delete -f mylabelconstraints_ns_musthave_mylabel.yml
kubectl delete -f mylabelconstraints_template.yaml

Container Registryを強制する

次はレジストリを自身のアカウントのECRに制限してみようと思います。

以下のようなnginxのPod定義を利用します。

cat > nginx.yml <<EOF
kind: Pod
apiVersion: v1
metadata:
  namespace: default
  name: nginx-app
  labels:
    app: nginx
spec:
  containers:
  - name: nginx-app
    image: nginx
EOF

当然、現時点では普通に適用できます。このままだとDockerhubから取ってくるので、指定したECRからの取得に制限してみます。

$ kubectl apply -f nginx.yml 
pod/nginx created
$ kubectl delete -f nginx.yml 
pod "nginx" deleted

ポリシーテンプレートの作成(レジストリ強制)

先程より少し複雑になってますが、全体の構成としてはほとんど変わって無いです。

propertiesを registries という配列で受け取ることにして、regoもそれに合わせて書き直しています。

cat <<EOF > myregistriesconstraints_template.yml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: myregistriesconstraints
spec:
  crd:
    spec:
      names:
        kind: MyRegistriesConstraints
      validation:
        openAPIV3Schema:
          properties:
            registries:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package myregistriesconstraints
        violation[{"msg": msg, "details": {"invalid_registry": image}}] {
          input.review.kind.kind == "Pod"
          image := input.review.object.spec.containers[_].image
          name := input.review.object.metadata.name
          not registry_whitelisted(image,whitelisted_registries)
          msg := sprintf("pod %q has invalid registry %q. valid registries [%v]", [name, image,whitelisted_registries])
        }

        whitelisted_registries := {
          registries | registries := input.parameters.registries[_]
        }

        registry_whitelisted(str, patterns) {
          registry_matches(str, patterns[_])
        }

        registry_matches(str, pattern) {
            contains(str, pattern)
        }
EOF

適用します。

$ kubectl apply -f myregistriesconstraints_template.yml 
constrainttemplate.templates.gatekeeper.sh/myregistriesconstraints created

制約の作成・適用(レジストリ強制)

自身のアカウントと、AWSの提供しているECRの二つを許可するように設定してみます。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) 
AWS_REGION=ap-northeast-1
cat <<EOF > myregistriesconstraints_constraints.yml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: MyRegistriesConstraints
metadata:
  name: my-registry-constraints
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    registries: 
      - "602401143452.dkr.ecr.${AWS_REGION}.amazonaws.com"
      - "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
EOF

適用します。

kubectl apply -f myregistriesconstraints_constraints.yml

ポリシー違反テスト

最初にapplyできたnginxのPodが作成できなくなっています。

$ kubectl apply -f nginx.yml 
Error from server ([denied by my-registry-constraints] pod "nginx-app" has invalid registry "nginx". valid registries [{"602401143452.dkr.ecr.ap-northeast-1.amazonaws.com", "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com"}]): error when creating "nginx.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by my-registry-constraints] pod "nginx-app" has invalid registry "nginx". valid registries [{"602401143452.dkr.ecr.ap-northeast-1.amazonaws.com", "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com"}]

ポリシー違反テスト(正常系)

自身のECRにnginxをアップロードして、以下のようにnginxのPodのimage取得先を変えてみるとPodを作成できるようになります。

cat nginx.yml
#省略
spec:
  containers:
  - name: nginx-app
    image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx
    #image: nginx

掃除

$ kubectl delete -f nginx.yml
$ kubectl delete -f myregistriesconstraints_constraints.yml
$ kubectl delete -f myregistriesconstraints_template.yml

Tips

Regoのエラー検知

Regoのエラーはapply時にチェックしてくれるものもあるみたいですが、applyは正常だったのに動かない場合もあります。その場合は以下のコマンドで出力される項目のeventにエラーが出てる時がありますのでチェックしてみてください。

エラーが無ければ Events: <none> となるはずです。

全て正常にapplyできているはずなのに、 no matches for kind のエラーが出て少しハマりました。以下のコマンドで見たら リソースはCREATEされていたのですが、Regoでエラーが出ていると、このエラーになるようでした。

kubectl describe constrainttemplates.templates.gatekeeper.sh myregistriesconstraints

サンプル集

REGOのサンプルはここに沢山置いてあります。

github.com

参考URl

aws.amazon.com