😺

gatekeeper で ValidatingAdmissionPolicy を扱う

に公開

Validating Admission Policy について

Kubernetes 1.30 で Validating Admission Policy が GA になりました。

Kubernetes にはリソース作成前にバリデーションをかける方法として Validating Admission Webhook を使う方法が存在していますが、Validating Admission Policy (以後 VAP) を使用することで、外部の webhook のサーバーへのアクセスや証明書の設定を行うことなく kube-apiserver 内部でバリデーションを完結することができます。

この辺りの利点は他の方が書かれた記事でも記載されていたのでこれ以上は割愛します。

私の所属するチームでは Validating Admission Policy を使用して、ユーザーに課したい Policy があったのでこの機能の検証を行っていました。

いざクラスタに適用しようと思った際にチームメンバーから「テストなどの諸々を考えるのであれば Gatekeeper の機能を使えないか試したい」という話を受けて、最終的にそちらを使用する形に落ち着いたので紹介します。

Gatekeeper の VAP Integration

Gatekeeper は v3.18 から VAP の CEL 記法に対応しています

詳細は gatekeeper の用意してくれている demo を見るのが良いのですが、ConstraintTemplate の generateVAP(spec.targets[].code[].source.generateVAP) を true に設定することによって validatinons(spec.targets[].code[].source.validations) に書かれた CEL 式をもとにした VAP を作成することができます。

k8srequiredlabels_template_uservap.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          type: object
          properties:
            message:
              type: string
            labels:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  allowedRegex:
                    type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      code:
        - engine: K8sNativeValidation
          source:
            generateVAP: true
            validations:
            - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))'
              messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
            - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))'
              message: "regex mismatch"

また VAP を適用する場合は Validating Admission Policy Binding (以後 VAPB) を作成する必要があります。
そのためには上記の ConstraintTemplates によって生成された CRD(K8sRequiredLabels) である Constraint Resource で

enforcementAction(spec.enforcementAction) を scoped にし scopedEnforcementActions(spec.scopedEnforcementActions) に vap.k8s.io を含める必要があります。

owner_must_be_provided_uservap.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: all-must-have-owner
spec:
  enforcementAction: scoped
  scopedEnforcementActions:
  - action: deny
    enforcementPoints:
    - name: validation.gatekeeper.sh
    - name: audit.gatekeeper.sh
  - action: warn
    enforcementPoints:
    - name: vap.k8s.io
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    message: "All namespaces must have an `owner` label that points to your company username"
    labels:
      - key: owner
        allowedRegex: "^[a-zA-Z]+.agilebank.demo$"

これで VAP と VAPB が作成されました。

gator を用いて VAP と VAPB のテストを行う

Gatekeeper では gator と呼ばれる CLI ツールが提供されており、実際のクラスタに適用せずとも CLI で動作を確認することが可能です。
gator でテストを行うためには Suite の CRD にそって Constraint と Template そして、それぞれのテストケースで使用する k8s のリソースの manifest を用意することで実行することができます。

今回は gatekeeper チームの用意してくれている demo に suite とテスト用の manifest を追加してみます。

まず gator を使用するためには Constraint の enforcementPoints(spec.scopedEnforcementActions[].enforcementPoints) に - name: gator.gatekeeper.sh を追加する必要があります、この記述がないと gator の対象として指定した Constraint が認識されません。

demo/scoped-enforcement-actions/owner_must_be_provided.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: all-must-have-owner
spec:
  enforcementAction: scoped
  scopedEnforcementActions:
  - action: deny
    enforcementPoints:
    - name: gator.gatekeeper.sh # gator を追加する
    - name: validation.gatekeeper.sh
    - name: audit.gatekeeper.sh
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    message: "All namespaces must have an `owner` label that points to your company username"
    labels:
      - key: owner
        allowedRegex: "^[a-zA-Z]+.agilebank.demo$"

demo の VAP / VAPB は Namespace が特定の label を所持しているかを確認するものになっています。
error-ns では label が指定されていません。

demo/scoped-enforcement-actions/suite.yaml
apiVersion: test.gatekeeper.sh/v1alpha1
kind: Suite
metadata:
  name: all-must-have-owner
tests:
  - name: all-must-have-owner
    template: k8srequiredlabels_template_usevap.yaml
    constraint: owner_must_be_provided_usevap.yaml
    cases:
      - name: success-ns
        object: testdata/success-ns.yaml
        assertions:
          - violations: 0
      - name: error-ns
        object: testdata/error-ns.yaml
        assertions:
          - violations: 2
demo/scoped-enforcement-actions/testdata/error-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: error
demo/scoped-enforcement-actions/testdata/success-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: success
  labels:
    "owner": "test.agilebank.demo"

そして以下のような gator のコマンドを実行すると定義した Suite にそって実行され、正しく定義されているかを確認することができます。

$ gator verify -v demo/scoped-enforcement-actions/suite.yaml
=== RUN   all-must-have-owner
    === RUN   allowed-ns
    --- PASS: allowed-ns        (0.007s)
    === RUN   error-ns
    --- PASS: error-ns  (0.004s)
--- PASS: all-must-have-owner   (0.026s)
ok      demo/scoped-enforcement-actions/suite.yaml 0.026s
PASS

実際に CI などで実行したい場合は以下のような make コマンドを実行すると ${PWD}/target に対して gator での test を実行することができます。

GATOR_VERSION ?= v3.18.2
GATOR_CMD ?= docker run -v ${PWD}/target:/target --rm ghcr.io/open-policy-agent/gator:$(GATOR_VERSION)
GATOR_TARGET ?= /target/...

vap-test:
	$(GATOR_CMD) verify -v $(GATOR_TARGET)

まとめ

今回は VAP / VAPB を gatekeeper の機能を使って定義、作成し、gator という CLI でテストを行う方法を紹介しました。
ただ manifest を定義してクラスタに反映し、クラスタ上でテストを行っても良いですが、 gator を使用することでより簡潔にテストを行うことができ CI でも確認することができます。
ぜひ使ってみてください!

Discussion